initial commit
							
								
								
									
										34
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						| @@ -1,26 +1,32 @@ | ||||
| <template> | ||||
|   <img alt="Vue logo" src="./assets/logo.png"> | ||||
|   <HelloWorld msg="Welcome to Your Vue.js App"/> | ||||
|   <Main /> | ||||
|   <StopInfo /> | ||||
|   <CarrierInfo /> | ||||
|   <WeatherInfo /> | ||||
|   <RouteInfo /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import HelloWorld from './components/HelloWorld.vue' | ||||
| import Main from "./components/main.vue"; | ||||
| import CarrierInfo from "./components/carrierinfo.vue"; | ||||
| import StopInfo from "./components/stopinfo.vue"; | ||||
| import WeatherInfo from "./components/weatherinfo.vue"; | ||||
| import RouteInfo from "./components/routeinfo.vue"; | ||||
|  | ||||
| export default { | ||||
|   name: 'App', | ||||
|   name: "App", | ||||
|   components: { | ||||
|     HelloWorld | ||||
|   } | ||||
| } | ||||
|     Main, | ||||
|     StopInfo, | ||||
|     CarrierInfo, | ||||
|     WeatherInfo, | ||||
|     RouteInfo, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style> | ||||
| #app { | ||||
|   font-family: Avenir, Helvetica, Arial, sans-serif; | ||||
|   -webkit-font-smoothing: antialiased; | ||||
|   -moz-osx-font-smoothing: grayscale; | ||||
|   text-align: center; | ||||
|   color: #2c3e50; | ||||
|   margin-top: 60px; | ||||
| body { | ||||
|   margin: 0; | ||||
| } | ||||
| </style> | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/fonts/Roboto-VariableFont_wdth.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/bridge.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.3 KiB | 
							
								
								
									
										7
									
								
								src/assets/img/carrier.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										7
									
								
								src/assets/img/get_new.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/gos.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 40 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/house.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 13 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/shop.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/stop01.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 230 KiB | 
| Before Width: | Height: | Size: 6.7 KiB | 
							
								
								
									
										960
									
								
								src/assets/style/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,960 @@ | ||||
| :root { | ||||
|   --panel-offset: 320px; | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|   font-family: "Roboto"; | ||||
|   src: url(../fonts/Roboto-VariableFont_wdth.ttf) format("ttf"); | ||||
| } | ||||
|  | ||||
| body { | ||||
|   background: #000; | ||||
|   font-family: "Roboto", sans-serif; | ||||
|   overflow-y: hidden; | ||||
| } | ||||
|  | ||||
| * { | ||||
|   box-sizing: border-box; | ||||
|   user-select: none; | ||||
| } | ||||
|  | ||||
| .station-label-no-bg { | ||||
|   background: transparent !important; | ||||
|   border: none !important; | ||||
|   box-shadow: none !important; | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #fff; | ||||
|   /* max-width: 200px; */ | ||||
|   padding: 0 !important; | ||||
|   /* text-wrap: revert; */ | ||||
| } | ||||
|  | ||||
| .station-label-no-bg:before { | ||||
|   display: none !important; | ||||
| } | ||||
|  | ||||
| .stopinfo { | ||||
|   z-index: 500; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| .stopinfo .bg { | ||||
|   height: 100%; | ||||
|   width: 500px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
| } | ||||
|  | ||||
| .container { | ||||
|   margin: auto 25px 25px 25px; | ||||
|   /* height: 100%; */ | ||||
|   /* background: #806c58; */ | ||||
|   background: rgb(125, 110, 92); | ||||
|   background: linear-gradient( | ||||
|     0deg, | ||||
|     rgba(125, 110, 92, 1) 0%, | ||||
|     rgba(150, 138, 124, 1) 100% | ||||
|   ); | ||||
|   border-radius: 10px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   /* gap: 15px; */ | ||||
| } | ||||
|  | ||||
| .container .img { | ||||
|   border-radius: 10px 10px 0 0; | ||||
|   border: 3px solid #806c58; | ||||
|   width: 100%; | ||||
|   height: 300px; | ||||
|   background-position: center center; | ||||
|   background-size: cover; | ||||
|   background-repeat: no-repeat; | ||||
| } | ||||
|  | ||||
| .stopname { | ||||
|   font-size: 22px; | ||||
|   font-weight: 600; | ||||
|   padding: 15px; | ||||
|   color: #fff; | ||||
|   background: rgb(187, 179, 170); | ||||
|   background: linear-gradient( | ||||
|     180deg, | ||||
|     rgba(187, 179, 170, 1) 0%, | ||||
|     rgba(159, 148, 135, 1) 100% | ||||
|   ); | ||||
|   margin-bottom: 15px; | ||||
| } | ||||
|  | ||||
| .stopdescription { | ||||
|   font-size: 16px; | ||||
|   font-weight: 400; | ||||
|   padding: 0 15px 15px 15px; | ||||
|   color: #fff; | ||||
| } | ||||
|  | ||||
| .landmarks { | ||||
|   background: #806c58; | ||||
|   border-radius: 10px 10px 0 0; | ||||
|   height: 50px; | ||||
|   margin: 0 25px; | ||||
|   color: #fff; | ||||
|   font-size: 18px; | ||||
|   font-weight: 600; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding: 0 25px; | ||||
|   justify-content: center; | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| .landmarks-arrow { | ||||
|   position: absolute; | ||||
|   left: 25px; | ||||
|   top: 18px; | ||||
| } | ||||
|  | ||||
| .stoparticles { | ||||
|   height: 50px; | ||||
|   color: #fff; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   margin-top: auto; | ||||
|   gap: 45px; | ||||
|   background: rgb(187, 179, 170); | ||||
|   background: linear-gradient( | ||||
|     180deg, | ||||
|     rgba(187, 179, 170, 1) 0%, | ||||
|     rgba(159, 148, 135, 1) 100% | ||||
|   ); | ||||
|   border-radius: 0 0 10px 10px; | ||||
| } | ||||
|  | ||||
| .stoparticle-option { | ||||
|   color: #fff; | ||||
|   font-size: 18px; | ||||
| } | ||||
|  | ||||
| .description-button { | ||||
|   background: #000; | ||||
|   color: #fff; | ||||
|   border: none; | ||||
|   border-radius: 5px; | ||||
|   padding: 10px 15px; | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   cursor: pointer; | ||||
|   margin: 0 15px 15px 15px; | ||||
| } | ||||
|  | ||||
| .stop-buttons-container { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   /* gap: 20px; */ | ||||
| } | ||||
|  | ||||
| .stop-button { | ||||
|   border: none; | ||||
|   border-radius: 10px; | ||||
|   padding: 15px 15px; | ||||
|   font-size: 18px; | ||||
|   /* font-weight: 600; */ | ||||
|   cursor: pointer; | ||||
|   width: 100%; | ||||
|   margin-top: 15px; | ||||
| } | ||||
|  | ||||
| .white { | ||||
|   background: #fff; | ||||
|   color: #000; | ||||
| } | ||||
|  | ||||
| .yellow { | ||||
|   background: #fcd500; | ||||
|   color: #000; | ||||
| } | ||||
|  | ||||
| .stop-next-container { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 15px; | ||||
| } | ||||
|  | ||||
| .stop-next-header { | ||||
|   font-size: 22px; | ||||
|   font-weight: 600; | ||||
|   color: #fff; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .stop-next-preview-container { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .stop-next-preview { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 5px; | ||||
|   align-items: center; | ||||
|   width: 110px; | ||||
| } | ||||
|  | ||||
| .stop-next-preview-img { | ||||
|   width: auto; | ||||
|   max-height: 110px; | ||||
| } | ||||
|  | ||||
| .stop-next-preview-name { | ||||
|   font-size: 16px; | ||||
|   font-weight: 600; | ||||
|   color: #fff; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .carrierinfo { | ||||
|   z-index: 400; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   height: 100%; | ||||
|   width: 350px; | ||||
| } | ||||
|  | ||||
| .carrierinfo .bg { | ||||
|   /* background: #2b2b2b; */ | ||||
|   background: rgb(125, 110, 92); | ||||
|   background: linear-gradient( | ||||
|     0deg, | ||||
|     rgba(125, 110, 92, 1) 0%, | ||||
|     rgba(150, 138, 124, 1) 100% | ||||
|   ); | ||||
|   height: 100%; | ||||
|   width: 300px; | ||||
|   padding: 50px 25px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: space-between; | ||||
| } | ||||
|  | ||||
| .carrier-img { | ||||
|   width: 85%; | ||||
|   margin: 0 auto; | ||||
| } | ||||
|  | ||||
| .hashtag { | ||||
|   text-align: center; | ||||
|   font-size: 20px; | ||||
|   color: #fff; | ||||
| } | ||||
|  | ||||
| .gos-container { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 25px; | ||||
| } | ||||
|  | ||||
| .gos-logo { | ||||
|   width: auto; | ||||
|   max-height: 125px; | ||||
| } | ||||
|  | ||||
| .gos-name { | ||||
|   text-align: center; | ||||
|   font-size: 16px; | ||||
|   color: #fff; | ||||
| } | ||||
| .modal-overlay { | ||||
|   position: fixed; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 100vw; | ||||
|   height: 100vh; | ||||
|   background: rgba(0, 0, 0, 0.7); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   z-index: 1000; | ||||
| } | ||||
| .modal-image { | ||||
|   width: 80vw; | ||||
|   height: auto; | ||||
|   max-height: 80vh; | ||||
|   object-fit: contain; | ||||
| } | ||||
| .modal-close { | ||||
|   position: absolute; | ||||
|   top: 25px; | ||||
|   right: 25px; | ||||
|   font-size: 100px; | ||||
|   background: transparent; | ||||
|   border: none; | ||||
|   color: #fff; | ||||
|   cursor: pointer; | ||||
| } | ||||
| .modal-enter-active, | ||||
| .modal-leave-active { | ||||
|   transition: opacity 0.3s ease; | ||||
| } | ||||
| .modal-enter, | ||||
| .modal-leave-to { | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| .weatherinfo { | ||||
|   z-index: 450; | ||||
|   width: 225px; | ||||
|   padding: 10px 15px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   position: absolute; | ||||
|   top: 140px; | ||||
|   left: var(--panel-offset); | ||||
|   color: #fff; | ||||
|   background: rgb(73, 69, 65); | ||||
|   background: linear-gradient( | ||||
|     0deg, | ||||
|     rgba(73, 69, 65, 1) 50%, | ||||
|     rgba(103, 99, 94, 1) 100% | ||||
|   ); | ||||
|   border-radius: 10px; | ||||
|   border: 1px solid #ffffff2e; | ||||
|   transition: left 0.3s ease; | ||||
| } | ||||
|  | ||||
| .weatherinfo .time { | ||||
|   font-size: 48px; | ||||
|   font-weight: 600; | ||||
| } | ||||
|  | ||||
| .weatherinfo .date { | ||||
|   text-align: center; | ||||
|   font-size: 18px; | ||||
|   padding: 2px 0 8px 0; | ||||
|   width: 100%; | ||||
|   border-bottom: 1px solid #ffffff7a; | ||||
| } | ||||
|  | ||||
| .weatherinfo .current-weather img { | ||||
|   width: 75px; | ||||
| } | ||||
|  | ||||
| .current-weather div { | ||||
|   text-align: center; | ||||
|   margin-top: 5px; | ||||
| } | ||||
|  | ||||
| .temperature-celsius { | ||||
|   font-size: 35px; | ||||
| } | ||||
|  | ||||
| .forecast-day { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 7px; | ||||
| } | ||||
| .forecast-day img { | ||||
|   width: 20px; | ||||
| } | ||||
|  | ||||
| .weather-forecast { | ||||
|   width: 100%; | ||||
|   padding: 0 10px; | ||||
|   margin-top: 10px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: space-between; | ||||
| } | ||||
|  | ||||
| .forecast { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 7px; | ||||
| } | ||||
|  | ||||
| .additional-forecast { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   border-top: 1px solid #ffffff7a; | ||||
|   padding: 10px 0 0 7px; | ||||
|   gap: 7px; | ||||
| } | ||||
|  | ||||
| .additional-forecast .humidity, | ||||
| .wind { | ||||
|   display: flex; | ||||
|   gap: 10px; | ||||
| } | ||||
|  | ||||
| .routeinfo { | ||||
|   z-index: 450; | ||||
|   height: 100px; | ||||
|   /* padding: 15px; */ | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: absolute; | ||||
|   top: 20px; | ||||
|   left: var(--panel-offset); | ||||
|   color: #fff; | ||||
|   background: rgb(73, 69, 65); | ||||
|   background: linear-gradient( | ||||
|     0deg, | ||||
|     rgba(73, 69, 65, 1) 50%, | ||||
|     rgba(103, 99, 94, 1) 100% | ||||
|   ); | ||||
|   border-radius: 10px; | ||||
|   border: 1px solid #ffffff2e; | ||||
|   transition: left 0.3s ease; | ||||
| } | ||||
|  | ||||
| .route-names { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 15px; | ||||
|   padding: 0 10px; | ||||
| } | ||||
|  | ||||
| .route-name { | ||||
|   font-size: 22px; | ||||
|   font-weight: 600; | ||||
| } | ||||
|  | ||||
| .route-translate, | ||||
| .translate { | ||||
|   font-size: 14px; | ||||
|   font-weight: 400; | ||||
|   color: #ffffff91; | ||||
| } | ||||
|  | ||||
| .route-number { | ||||
|   background: #fcd500; | ||||
|   height: 100%; | ||||
|   padding: 25px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   color: #000; | ||||
|   font-size: 95px; | ||||
|   font-weight: 800; | ||||
|   border-radius: 10px; | ||||
| } | ||||
|  | ||||
| .dropdown-list { | ||||
|   height: 0; | ||||
|   overflow: hidden; | ||||
|   transition: height 0.3s ease, opacity 0.3s ease, transform 0.3s ease; | ||||
|   position: relative; | ||||
|   border: 1px solid #ffffff2e; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   list-style: none; | ||||
|   opacity: 0; | ||||
|   transform: translateY(-10px); | ||||
| } | ||||
|  | ||||
| .dropdown-list.show { | ||||
|   height: 500px; | ||||
|   opacity: 1; | ||||
|   transform: translateY(0); | ||||
| } | ||||
|  | ||||
| .dropdown-list.hidden { | ||||
|   pointer-events: none; | ||||
| } | ||||
|  | ||||
| .forecast-temperature { | ||||
|   font-weight: 700; | ||||
|   font-size: 18px; | ||||
| } | ||||
|  | ||||
| .carrier-toggle { | ||||
|   position: absolute; | ||||
|   bottom: 10px; | ||||
|   left: 310px; | ||||
|   background: rgba(0, 0, 0, 0.4); | ||||
|   border: none; | ||||
|   color: #fff; | ||||
|   font-size: 24px; | ||||
|   cursor: pointer; | ||||
|   padding: 5px 10px; | ||||
|   border-radius: 5px; | ||||
|   z-index: 1001; | ||||
|   transition: left 0.3s ease; | ||||
| } | ||||
|  | ||||
| .carrierinfo .bg.hidden + .carrier-toggle { | ||||
|   left: 10px; | ||||
| } | ||||
|  | ||||
| .bg.hidden { | ||||
|   transform: translateX(-100%); | ||||
|   transition: transform 0.3s ease; | ||||
| } | ||||
|  | ||||
| .bg { | ||||
|   transition: transform 0.3s ease; | ||||
| } | ||||
|  | ||||
| .routeinfo.shifted-left, | ||||
| .weatherinfo.shifted-left { | ||||
|   left: 20px !important; | ||||
|   transition: left 0.3s ease; | ||||
| } | ||||
| .stations-list { | ||||
|   margin-top: 20px; | ||||
|   list-style: none; | ||||
|   padding: 0; | ||||
|   font-size: 18px; | ||||
| } | ||||
|  | ||||
| .stop-buttons-container { | ||||
|   transition: transform 0.3s ease; | ||||
| } | ||||
|  | ||||
| .dropdown-list { | ||||
|   position: absolute; | ||||
|   left: 0; | ||||
|   bottom: 0; | ||||
|   width: 300px; | ||||
|   height: 0; | ||||
|   background: rgb(125, 109, 91); | ||||
|   background: linear-gradient( | ||||
|     90deg, | ||||
|     rgba(125, 109, 91, 1) 0%, | ||||
|     rgba(145, 132, 117, 1) 100% | ||||
|   ); | ||||
|   z-index: 2000; | ||||
|   box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.2); | ||||
|   overflow-y: hidden; | ||||
|   opacity: 0; | ||||
|   transform: translateY(100%); | ||||
|   transition: height 0.3s ease, opacity 0.3s ease, transform 0.3s ease; | ||||
|   list-style: none; | ||||
|   margin: 0; | ||||
|   font-size: 18px; | ||||
|   padding-bottom: 15px; | ||||
|   border-radius: 10px 10px 0 0; | ||||
| } | ||||
|  | ||||
| .dropdown-list.show { | ||||
|   height: calc(100vh - 275px); | ||||
|   opacity: 1; | ||||
|   transform: translateY(0); | ||||
|   overflow-y: auto; | ||||
| } | ||||
|  | ||||
| .carrier-toggle { | ||||
|   position: absolute; | ||||
|   bottom: 10px; | ||||
|   left: 310px; | ||||
|   background: rgba(0, 0, 0, 0.4); | ||||
|   border: none; | ||||
|   color: #fff; | ||||
|   font-size: 24px; | ||||
|   cursor: pointer; | ||||
|   padding: 5px 10px; | ||||
|   border-radius: 5px; | ||||
|   z-index: 1001; | ||||
|   transition: left 0.3s ease; | ||||
| } | ||||
|  | ||||
| .carrierinfo .bg.hidden + .carrier-toggle { | ||||
|   left: 10px; | ||||
| } | ||||
|  | ||||
| .bg.hidden { | ||||
|   transform: translateX(-100%); | ||||
|   transition: transform 0.3s ease; | ||||
| } | ||||
|  | ||||
| .bg { | ||||
|   transition: transform 0.3s ease; | ||||
| } | ||||
|  | ||||
| .dropdown-name { | ||||
|   font-size: 18px; | ||||
|   font-weight: 600; | ||||
|   color: #fff; | ||||
|   padding-bottom: 10px; | ||||
|   gap: 15px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   text-align: center; | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| .dropdown-list li { | ||||
|   list-style: none; | ||||
|   font-size: 16px; | ||||
|   color: #fff; | ||||
|   background-color: transparent; | ||||
| } | ||||
|  | ||||
| .sight-name { | ||||
|   margin: 0 20px; | ||||
|   padding: 20px 0 4px 0; | ||||
|   border-bottom: 1px solid #ffffff2e; | ||||
| } | ||||
|  | ||||
| .dropdown-list li.selected { | ||||
|   background-color: #4a4037; | ||||
| } | ||||
| .dropdown-list .sight-preview { | ||||
|   margin-top: 15px; | ||||
|   padding: 15px; | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   border-radius: 10px; | ||||
| } | ||||
| .dropdown-list .sight-preview img { | ||||
|   width: 100%; | ||||
|   border-radius: 10px; | ||||
| } | ||||
| .dropdown-list .sight-preview h3 { | ||||
|   color: white; | ||||
|   margin-top: 10px; | ||||
| } | ||||
| .dropdown-list .sight-preview p { | ||||
|   color: white; | ||||
|   font-size: 16px; | ||||
| } | ||||
| .sight-preview-panel { | ||||
|   z-index: 451; | ||||
|   position: absolute; | ||||
|   top: 440px; | ||||
|   left: var(--panel-offset); | ||||
|   width: 350px; | ||||
|   background: rgb(125, 110, 92); | ||||
|   background: linear-gradient( | ||||
|     0deg, | ||||
|     rgba(125, 110, 92, 1) 0%, | ||||
|     rgba(150, 138, 124, 1) 100% | ||||
|   ); | ||||
|   border-radius: 10px; | ||||
|   color: white; | ||||
|   padding-bottom: 20px; | ||||
| } | ||||
|  | ||||
| .sight-preview-panel img { | ||||
|   width: 100%; | ||||
|   border-radius: 10px; | ||||
|   padding: 3px; | ||||
| } | ||||
|  | ||||
| .sight-preview-panel h3 { | ||||
|   padding: 10px; | ||||
|   margin: 0; | ||||
|   font-size: 18px; | ||||
| } | ||||
|  | ||||
| .sight-preview-panel p { | ||||
|   font-size: 16px; | ||||
|   margin: 0; | ||||
|   padding: 10px 10px 0 10px; | ||||
|   max-height: 150px; | ||||
|   overflow-y: auto; | ||||
| } | ||||
|  | ||||
| li.checked { | ||||
|   background: rgb(71, 51, 31); | ||||
|   background: linear-gradient( | ||||
|     90deg, | ||||
|     rgba(71, 51, 31, 1) 0%, | ||||
|     rgba(99, 82, 63, 1) 100% | ||||
|   ); | ||||
|   font-weight: 600; | ||||
| } | ||||
|  | ||||
| .fingers-option { | ||||
|   position: absolute; | ||||
|   top: 20px; | ||||
|   right: 490px; | ||||
| } | ||||
|  | ||||
| .stoparticle-option { | ||||
|   cursor: pointer; | ||||
|   margin-right: 10px; | ||||
| } | ||||
|  | ||||
| .stoparticle-option.selected { | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .stoparticle-option { | ||||
|   cursor: pointer; | ||||
|   margin-right: 10px; | ||||
| } | ||||
|  | ||||
| .stoparticle-option.selected { | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .sights-wrapper { | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   transition: all 0.4s ease; | ||||
| } | ||||
|  | ||||
| .scrollable-container { | ||||
|   max-height: 500px; | ||||
|   overflow-y: auto; | ||||
|   scroll-behavior: smooth; | ||||
|  | ||||
|   /* скрытие скроллбара */ | ||||
|   scrollbar-width: none; /* Firefox */ | ||||
|   -ms-overflow-style: none; /* Internet Explorer 10+ */ | ||||
| } | ||||
|  | ||||
| .scrollable-container::-webkit-scrollbar { | ||||
|   display: none; /* Safari and Chrome */ | ||||
| } | ||||
|  | ||||
| .sights-alphabet { | ||||
|   position: absolute; | ||||
|   top: 15px; | ||||
|   right: 35px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 10px; | ||||
|   max-height: 100%; | ||||
|   overflow-y: auto; | ||||
| } | ||||
|  | ||||
| .sights-alphabet span { | ||||
|   cursor: pointer; | ||||
|   color: white; | ||||
|   font-size: 16px; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .sights-alphabet span.active { | ||||
|   font-weight: bold; | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .sights-grid { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 12px; | ||||
|   padding: 20px 20px 20px 10px; | ||||
|   background: #806c58; | ||||
|   border-top: 1px solid #fff; | ||||
|   margin: 0 25px; | ||||
| } | ||||
|  | ||||
| .sight-card { | ||||
|   width: 30%; | ||||
|   min-width: 100px; | ||||
|   text-align: center; | ||||
|   color: white; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
| } | ||||
|  | ||||
| .sight-thumbnail { | ||||
|   width: 100%; | ||||
|   border-radius: 6px; | ||||
| } | ||||
|  | ||||
| .sight-title { | ||||
|   margin-top: 4px; | ||||
|   font-size: 15px; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .sight-letter { | ||||
|   color: white; | ||||
|   font-size: 18px; | ||||
|   margin-top: 10px; | ||||
| } | ||||
|  | ||||
| .sight-letter-group { | ||||
|   width: 30%; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   justify-content: center; | ||||
|   gap: 12px; | ||||
| } | ||||
|  | ||||
| .fade-enter-active, | ||||
| .fade-leave-active { | ||||
|   transition: opacity 0.3s ease; | ||||
| } | ||||
|  | ||||
| .fade-enter-from, | ||||
| .fade-leave-to { | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| .slide-fade-enter-active, | ||||
| .slide-fade-leave-active { | ||||
|   transition: all 0.4s ease; | ||||
| } | ||||
|  | ||||
| .slide-fade-enter-from { | ||||
|   opacity: 0; | ||||
|   transform: translateY(20px); | ||||
| } | ||||
|  | ||||
| .slide-fade-leave-to { | ||||
|   opacity: 0; | ||||
|   transform: translateY(20px); | ||||
| } | ||||
|  | ||||
| .landmarks-arrow { | ||||
|   transition: transform 0.3s ease; | ||||
| } | ||||
|  | ||||
| .arrow-rotated { | ||||
|   transform: rotate(180deg); | ||||
| } | ||||
|  | ||||
| .expand-collapse-transition-enter-active, | ||||
| .expand-collapse-transition-leave-active { | ||||
|   transition: all 0.4s ease; | ||||
| } | ||||
|  | ||||
| .expand-collapse-transition-enter-from, | ||||
| .expand-collapse-transition-leave-to { | ||||
|   opacity: 0; | ||||
|   transform: translateY(20px); | ||||
| } | ||||
|  | ||||
| .height-fade-enter-active, | ||||
| .height-fade-leave-active { | ||||
|   transition: all 0.4s ease; | ||||
| } | ||||
|  | ||||
| .height-fade-enter-from, | ||||
| .height-fade-leave-to { | ||||
|   opacity: 0; | ||||
|   transform: translateY(20px); | ||||
| } | ||||
|  | ||||
| .transfer-toggle { | ||||
|   position: absolute; | ||||
|   bottom: 10px; | ||||
|   padding: 5px 10px; | ||||
|   right: 490px; | ||||
|   cursor: pointer; | ||||
| } | ||||
|  | ||||
| .transfer-info { | ||||
|   position: absolute; | ||||
|   bottom: 10px; | ||||
|   right: 70px; | ||||
|   color: #fff; | ||||
|   background: rgb(73, 69, 65); | ||||
|   background: linear-gradient( | ||||
|     0deg, | ||||
|     rgba(73, 69, 65, 1) 50%, | ||||
|     rgba(103, 99, 94, 1) 100% | ||||
|   ); | ||||
|   border-radius: 10px; | ||||
|   padding: 15px; | ||||
|   border: 1px solid #ffffff2e; | ||||
|   min-width: 350px; | ||||
|   min-height: 150px; | ||||
|   font-size: 16px; | ||||
| } | ||||
| .transfer-icon { | ||||
|   width: 24px; | ||||
|   height: 24px; | ||||
|   vertical-align: middle; | ||||
| } | ||||
| .transfer-item { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 7px; | ||||
|   font-size: 18px; | ||||
|   margin-bottom: 6px; | ||||
| } | ||||
| .transfer-title { | ||||
|   font-weight: bold; | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
| .image-wrapper { | ||||
|   position: relative; | ||||
|   width: 100%; | ||||
|   height: 300px; | ||||
| } | ||||
|  | ||||
| .img { | ||||
|   width: 100% !important; | ||||
|   height: 100% !important; | ||||
|   background-size: cover; | ||||
|   background-position: center; | ||||
| } | ||||
|  | ||||
| .watermark { | ||||
|   position: absolute; | ||||
|   max-width: 50px; | ||||
|   max-height: 50px; | ||||
|   opacity: 1; | ||||
|   pointer-events: none; | ||||
| } | ||||
|  | ||||
| .watermark-lu { | ||||
|   top: 10px; | ||||
|   left: 10px; | ||||
| } | ||||
|  | ||||
| .watermark-rd { | ||||
|   bottom: 10px; | ||||
|   right: 10px; | ||||
| } | ||||
| .route-name { | ||||
|   position: relative; | ||||
| } | ||||
|  | ||||
| .route-names { | ||||
|   max-width: 300px; | ||||
|   width: 100%; | ||||
|   overflow: hidden; | ||||
| } | ||||
|  | ||||
| .scroll-wrapper { | ||||
|   max-width: 100%; | ||||
|   overflow: hidden; | ||||
|   position: relative; | ||||
|   white-space: nowrap; | ||||
| } | ||||
|  | ||||
| .scroll-inner { | ||||
|   display: inline-block; | ||||
|   white-space: nowrap; | ||||
|   animation: scrollLoop 20s linear infinite; | ||||
| } | ||||
|  | ||||
| .scroll-content { | ||||
|   display: flex; | ||||
|   gap: 30px; | ||||
| } | ||||
|  | ||||
| .name-block { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 10px; | ||||
| } | ||||
|  | ||||
| @keyframes scrollLoop { | ||||
|   0% { | ||||
|     transform: translateX(0%); | ||||
|   } | ||||
|   100% { | ||||
|     transform: translateX(-33.333%); | ||||
|   } | ||||
| } | ||||
|  | ||||
| .sight-preview-wrapper { | ||||
|   max-height: 300px; | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| <template> | ||||
|   <div class="hello"> | ||||
|     <h1>{{ msg }}</h1> | ||||
|     <p> | ||||
|       For a guide and recipes on how to configure / customize this project,<br> | ||||
|       check out the | ||||
|       <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. | ||||
|     </p> | ||||
|     <h3>Installed CLI Plugins</h3> | ||||
|     <ul> | ||||
|       <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li> | ||||
|       <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li> | ||||
|     </ul> | ||||
|     <h3>Essential Links</h3> | ||||
|     <ul> | ||||
|       <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li> | ||||
|       <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li> | ||||
|       <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li> | ||||
|       <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li> | ||||
|       <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li> | ||||
|     </ul> | ||||
|     <h3>Ecosystem</h3> | ||||
|     <ul> | ||||
|       <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li> | ||||
|       <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li> | ||||
|       <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li> | ||||
|       <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li> | ||||
|       <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li> | ||||
|     </ul> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: 'HelloWorld', | ||||
|   props: { | ||||
|     msg: String | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | ||||
| <style scoped> | ||||
| h3 { | ||||
|   margin: 40px 0 0; | ||||
| } | ||||
| ul { | ||||
|   list-style-type: none; | ||||
|   padding: 0; | ||||
| } | ||||
| li { | ||||
|   display: inline-block; | ||||
|   margin: 0 10px; | ||||
| } | ||||
| a { | ||||
|   color: #42b983; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										1009
									
								
								src/components/carrierinfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										475
									
								
								src/components/main.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										141
									
								
								src/components/routeinfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,141 @@ | ||||
| <template> | ||||
|   <div class="routeinfo"> | ||||
|     <div class="route-number">{{ routeNumber }}</div> | ||||
|     <div class="route-names"> | ||||
|       <div class="route-name" v-if="startStopName && endStopName"> | ||||
|         <div class="scroll-wrapper"> | ||||
|           <div class="scroll-inner"> | ||||
|             <div class="scroll-content"> | ||||
|               <div class="name-block"> | ||||
|                 <span class="name"> | ||||
|                   {{ startStopName }} — {{ endStopName }}     | ||||
|                   {{ startStopName }} — {{ endStopName }}     | ||||
|                   {{ startStopName }} — {{ endStopName }}     | ||||
|                 </span> | ||||
|                 <span class="translate"> | ||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     | ||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     | ||||
|                   {{ startStopNameEn }} — {{ endStopNameEn }}     | ||||
|                 </span> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Cookies from "js-cookie"; | ||||
|  | ||||
| async function requestAuth() { | ||||
|   const response = await fetch("https://wn-ts.krbl.ru/auth/login", { | ||||
|     method: "POST", | ||||
|     headers: { | ||||
|       "Content-Type": "application/json", | ||||
|     }, | ||||
|     body: JSON.stringify({ | ||||
|       email: "admin", | ||||
|       password: "changeme", | ||||
|     }), | ||||
|   }); | ||||
|  | ||||
|   const data = await response.json(); | ||||
|   Cookies.set("auth_token", data.token); | ||||
|   return data.token; | ||||
| } | ||||
|  | ||||
| async function checkAuth() { | ||||
|   const token = Cookies.get("auth_token"); | ||||
|   try { | ||||
|     if (!token) { | ||||
|       return await requestAuth(); | ||||
|     } else { | ||||
|       const response = await fetch("https://wn-ts.krbl.ru/auth/me", { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }); | ||||
|  | ||||
|       if (response.status === 401) { | ||||
|         return await requestAuth(); | ||||
|       } | ||||
|     } | ||||
|     return token; | ||||
|   } catch (error) { | ||||
|     console.error("Auth check failed:", error); | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default { | ||||
|   name: "RouteInfo", | ||||
|   data() { | ||||
|     return { | ||||
|       routeNumber: "", | ||||
|       startStopName: "", | ||||
|       endStopName: "", | ||||
|       startStopNameEn: "", | ||||
|       endStopNameEn: "", | ||||
|     }; | ||||
|   }, | ||||
|   async mounted() { | ||||
|     const contextRes = await fetch( | ||||
|       "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|     ); | ||||
|     const data = await contextRes.json(); | ||||
|     this.routeNumber = data.routeNumber; | ||||
|  | ||||
|     const startStopId = data.startStopId; | ||||
|     const endStopId = data.endStopId; | ||||
|  | ||||
|     const token = await checkAuth(); | ||||
|     if (!token) { | ||||
|       console.error( | ||||
|         "No valid token received, skipping wn-ts.krbl.ru requests." | ||||
|       ); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const [startStopRes, endStopRes] = await Promise.all([ | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${startStopId}`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${endStopId}`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|     ]); | ||||
|  | ||||
|     const startStopData = await startStopRes.json(); | ||||
|     const endStopData = await endStopRes.json(); | ||||
|  | ||||
|     this.startStopName = startStopData.name; | ||||
|     this.endStopName = endStopData.name; | ||||
|  | ||||
|     const [startStopEnRes, endStopEnRes] = await Promise.all([ | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${startStopId}?lang=en`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|       fetch(`https://wn-ts.krbl.ru/station/${endStopId}?lang=en`, { | ||||
|         headers: { | ||||
|           Authorization: `Bearer ${token}`, | ||||
|         }, | ||||
|       }), | ||||
|     ]); | ||||
|  | ||||
|     const startStopEnData = await startStopEnRes.json(); | ||||
|     const endStopEnData = await endStopEnRes.json(); | ||||
|  | ||||
|     this.startStopNameEn = startStopEnData.name; | ||||
|     this.endStopNameEn = endStopEnData.name; | ||||
|   }, | ||||
|   methods: {}, | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										505
									
								
								src/components/stopinfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,505 @@ | ||||
| <template> | ||||
|   <div class="stopinfo"> | ||||
|     <div class="bg"> | ||||
|       <div class="container"> | ||||
|         <div class="image-wrapper"> | ||||
|           <!-- @click="openModal" --> | ||||
|           <div | ||||
|             :style="`background-image: url('${imageUrl || defaultImageUrl}')`" | ||||
|             class="img" | ||||
|           ></div> | ||||
|           <img | ||||
|             v-if="watermarkLU" | ||||
|             :src="watermarkLU" | ||||
|             class="watermark watermark-lu" | ||||
|           /> | ||||
|           <img | ||||
|             v-if="watermarkRD" | ||||
|             :src="watermarkRD" | ||||
|             class="watermark watermark-rd" | ||||
|           /> | ||||
|         </div> | ||||
|         <span class="stopname">{{ stopName }}</span> | ||||
|         <span class="stopdescription">{{ selectedArticleBody }}</span> | ||||
|         <div class="stoparticles"> | ||||
|           <span | ||||
|             v-for="article in articles" | ||||
|             :key="article.id" | ||||
|             :class="{ selected: article.id === selectedArticleId }" | ||||
|             @click="selectArticle(article.id)" | ||||
|             class="stoparticle-option" | ||||
|           > | ||||
|             {{ article.heading }} | ||||
|           </span> | ||||
|         </div> | ||||
|       </div> | ||||
|       <svg | ||||
|         class="fingers-option" | ||||
|         xmlns="http://www.w3.org/2000/svg" | ||||
|         width="39" | ||||
|         height="48" | ||||
|         fill="none" | ||||
|         viewBox="0 0 39 48" | ||||
|       > | ||||
|         <path | ||||
|           fill="#fff" | ||||
|           fill-rule="evenodd" | ||||
|           d="M20.072 14.57c0 1.17.5 2.22 1.3 2.95v1.93a5.511 5.511 0 0 1-2.87-4.87c0-3.06 2.49-5.55 5.55-5.55 3.05 0 5.52 2.48 5.51 5.54 0 2.1-1.18 3.93-2.9 4.87v-1.92a3.96 3.96 0 0 0 1.32-2.95c0-2.19-1.76-3.96-3.94-3.96-2.18 0-3.96 1.78-3.96 3.97l-.01-.01Zm14.12 33.34h-9.5c-.08-.28-.15-.56-.22-.83-.17-.65-.34-1.28-.54-1.9-.13-.4-.37-.8-.64-1.12-1.55-1.79-2.57-3.89-3.5-6.04l-.45-1.05c-.65-1.54-1.31-3.07-2.02-4.58-.81-1.7-1.97-3.15-3.48-4.31-.31-.24-.59-.57-.78-.91-.42-.75 0-1.44.85-1.49.99-.05 1.87.3 2.68.82 1.54.97 2.68 2.35 3.76 3.79.3.4.59.82.88 1.23.22.32.48.5.88.37.39-.13.49-.44.49-.82 0-4.57.02-9.13.04-13.7v-2.83c.02-.46.05-.95.53-1.18.57-.28 1.19-.28 1.75.03.19.1.34.36.4.58.08.28.09.6.09.9v8.52c0 .22.02.52.16.64.18.15.51.27.72.21.2-.06.44-.35.48-.57.17-.9.53-1.18 1.56-1.11.8.05 1.23.51 1.23 1.31v2.44c0 .49.26.77.68.77.41 0 .66-.24.71-.71.09-.79.57-1.17 1.45-1.13.8.04 1.31.51 1.31 1.25v2.56c0 .5.23.78.65.8.42.02.68-.24.74-.73.09-.77.64-1.24 1.42-1.22.78.02 1.34.53 1.34 1.3 0 3.01.01 6.02-.03 9.04-.01 1.18-.54 2.23-1.08 3.26-.13.25-.27.51-.4.76-.45.86-.91 1.72-1.27 2.62-.26.65-.43 1.33-.6 2.04-.08.32-.16.65-.25.99h-.04Zm-19.91-16.86a3.94 3.94 0 0 1-3.94-3.96c0-2.19 1.78-3.96 3.96-3.97 1.82 0 3.36 1.24 3.81 2.93a14.1 14.1 0 0 1 1.68 1.58c.02-.18.03-.36.03-.54 0-3.06-2.46-5.54-5.51-5.54s-5.54 2.48-5.54 5.55c0 3.06 2.46 5.54 5.51 5.54.56 0 1.1-.08 1.6-.24-.27-.5-.58-.96-.92-1.4-.22.04-.45.06-.68.06v-.01Z" | ||||
|           clip-rule="evenodd" | ||||
|           opacity=".75" | ||||
|         /> | ||||
|         <path | ||||
|           fill="#fff" | ||||
|           d="M9.562 32.63c-.26-.28-.65-.3-.97-.05-.1.08-.19.18-.28.27l-6.51 6.51c-.11.11-.22.2-.4.36v-.56c0-1.52.02-3.04.02-4.56 0-.52-.24-.81-.67-.82-.45 0-.71.29-.71.84-.02 2.25-.04 4.49-.04 6.73 0 .53.24.79.75.79h6.8c.16 0 .35-.04.49-.13.3-.18.37-.48.26-.8-.11-.34-.39-.48-.74-.48h-4.63c-.14 0-.27-.01-.48-.02.14-.15.23-.26.33-.36l6.51-6.51c.09-.09.19-.18.27-.28.25-.3.24-.68 0-.95v.02Zm19.35-23.1c.26.28.65.3.97.05.1-.08.19-.18.28-.27l6.51-6.51c.11-.11.22-.2.4-.36V3c0 1.52-.02 3.04-.02 4.56 0 .52.24.81.67.82.45 0 .71-.29.71-.84l.03-6.73c0-.53-.24-.79-.75-.79-2.26-.02-4.53-.02-6.79-.02-.16 0-.35.04-.49.13-.3.18-.37.48-.26.8.11.34.39.48.74.48h4.63c.14 0 .27.01.48.02-.14.15-.23.26-.33.36l-6.51 6.51c-.09.09-.19.18-.27.28-.25.3-.24.68 0 .95Z" | ||||
|           opacity=".75" | ||||
|         /> | ||||
|       </svg> | ||||
|  | ||||
|       <div class="transfer-toggle" @click="toggleTransfers"> | ||||
|         <svg | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|           width="48" | ||||
|           height="48" | ||||
|           fill="none" | ||||
|           viewBox="0 0 48 48" | ||||
|         > | ||||
|           <path | ||||
|             fill="#fff" | ||||
|             d="M24 0c-.65 0-1.3.03-1.96.08C10.42 1 1.01 10.41.08 22.03c-.51 6.36 1.47 12.25 5.06 16.8.46.59.62 1.38.32 2.06-.7 1.54-1.83 2.75-2.41 3.34-.55.55-1.08 1.04-1.67 1.47-.59.43-.54 1.33.07 1.72.63.4 1.56.57 2.67.57 2.72 0 6.46-1 9.18-1.93.2-.07.4-.1.61-.1.27 0 .55.06.8.17 2.86 1.2 6 1.87 9.29 1.87.65 0 1.32-.03 1.98-.08 11.65-.94 21.08-10.41 21.95-22.07C48.99 11.76 37.87 0 24 0Zm5.51 37.71c-.03.11-.17.23-.28.27-1.49.5-2.96 1.14-4.5 1.44-1.78.35-3.59.26-5.18-.84-1.33-.92-1.82-2.22-1.74-3.78.09-1.89.7-3.67 1.19-5.47.52-1.91 1.11-3.8 1.46-5.74.48-2.65-.68-3.11-3.07-2.85-.59.06-1.16.3-1.81.48.14-.6.25-1.14.41-1.67.03-.1 3.64-1.61 5.45-1.77 1.91-.17 3.76.02 5.16 1.54.79.85 1.01 1.89.99 3.01-.05 2.47-1.01 4.74-1.61 7.09-.37 1.44-.73 2.88-1.05 4.33-.09.4-.09.84-.05 1.25.08.97.56 1.39 1.55 1.57 1.19.21 2.29-.12 3.5-.53-.15.62-.27 1.15-.41 1.66l-.01.01Zm-2.66-22.5c-.2.02-.39.02-.65.04-1.8.02-3.47-1.1-4-2.73-.61-1.87.36-3.81 2.33-4.66 2.26-.98 5.05.21 5.74 2.47.78 2.54-1.21 4.62-3.41 4.88h-.01Z" | ||||
|           /> | ||||
|         </svg> | ||||
|  | ||||
|         <div v-if="showTransfers" class="transfer-info"> | ||||
|           <div | ||||
|             v-if="Object.keys(filteredTransfers).length > 0" | ||||
|             class="transfer-title" | ||||
|           > | ||||
|             Доступные пересадки: | ||||
|           </div> | ||||
|           <div | ||||
|             v-for="(value, key) in filteredTransfers" | ||||
|             :key="key" | ||||
|             class="transfer-item" | ||||
|           > | ||||
|             <img | ||||
|               v-if="transferIcons[key]" | ||||
|               :src="transferIcons[key]" | ||||
|               class="transfer-icon" | ||||
|               :alt="key" | ||||
|             /> | ||||
|             <strong v-else>{{ key }}:</strong> {{ value }} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <transition name="modal"> | ||||
|         <div v-if="isModalOpen" class="modal-overlay" @click="closeModal"> | ||||
|           <img :src="imageUrl" class="modal-image" @click.stop /> | ||||
|           <button class="modal-close" @click.stop="closeModal">×</button> | ||||
|         </div> | ||||
|       </transition> | ||||
|       <transition name="expand-collapse-transition"> | ||||
|         <div> | ||||
|           <div class="landmarks" @click="toggleSightsList"> | ||||
|             <div class="landmarks-header"> | ||||
|               <svg | ||||
|                 class="landmarks-arrow" | ||||
|                 :class="{ 'arrow-rotated': showSightsList }" | ||||
|                 xmlns="http://www.w3.org/2000/svg" | ||||
|                 width="25" | ||||
|                 height="15" | ||||
|                 fill="none" | ||||
|                 viewBox="0 0 25 15" | ||||
|               > | ||||
|                 <path | ||||
|                   fill="#fff" | ||||
|                   fill-opacity=".85" | ||||
|                   d="M.382 11.643c-.24.227-.382.553-.382.92 0 .737.552 1.29 1.288 1.29.368 0 .708-.128.92-.368L13.052 2.408h-1.515l10.844 11.077c.227.24.566.368.92.368a1.26 1.26 0 0 0 1.289-1.29 1.25 1.25 0 0 0-.383-.92L13.25.425A1.288 1.288 0 0 0 12.302 0a1.32 1.32 0 0 0-.963.425L.382 11.643Z" | ||||
|                 /> | ||||
|               </svg> | ||||
|               <span class="header-name">Достопримечательности</span> | ||||
|             </div> | ||||
|           </div> | ||||
|           <transition name="slide-fade"> | ||||
|             <div | ||||
|               v-show="true" | ||||
|               class="sights-wrapper" | ||||
|               :style="{ maxHeight: showSightsList ? '500px' : '0px' }" | ||||
|             > | ||||
|               <div class="sights-alphabet"> | ||||
|                 <span | ||||
|                   v-for="letter in availableLetters" | ||||
|                   :key="letter" | ||||
|                   @click="selectLetter(letter)" | ||||
|                 > | ||||
|                   {{ letter }} | ||||
|                 </span> | ||||
|               </div> | ||||
|               <div | ||||
|                 class="sights-grid scrollable-container" | ||||
|                 ref="scrollContainer" | ||||
|               > | ||||
|                 <div | ||||
|                   v-for="(group, letter) in groupedSights" | ||||
|                   :key="letter" | ||||
|                   :ref="'letter-' + letter" | ||||
|                   class="sight-letter-group" | ||||
|                 > | ||||
|                   <div | ||||
|                     v-for="sight in group" | ||||
|                     :key="sight.id" | ||||
|                     class="sight-card" | ||||
|                   > | ||||
|                     <img | ||||
|                       class="sight-thumbnail" | ||||
|                       :src="`http://31.129.106.67:8080/media/${sight.thumbnail}/download`" | ||||
|                     /> | ||||
|                     <div class="sight-title">{{ sight.name }}</div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </transition> | ||||
|         </div> | ||||
|       </transition> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import "../assets/style/main.css"; | ||||
| import axios from "axios"; | ||||
| import Cookies from "js-cookie"; | ||||
|  | ||||
| export default { | ||||
|   name: "StopInfo", | ||||
|   data() { | ||||
|     return { | ||||
|       isModalOpen: false, | ||||
|       autoCloseTimer: null, | ||||
|       imageUrl: "", | ||||
|       defaultImageUrl: | ||||
|         "https://lh3.googleusercontent.com/gps-cs-s/AB5caB8lUwofb2NIg6n0-cEl8nIWsySAUc52KNj4XezuOdo-aeqTgQlD1kTVa5MaynL2Yg4ByoTYTKNTR7K59f7kjzU9yzpudstjRiT2F6M_ilxFYFpcvMZz6OwlRFF2MrsCPSwUa7vqew=s680-w680-h510", | ||||
|       sightId: 17, | ||||
|       stopName: "", | ||||
|       watermarkLU: "", | ||||
|       watermarkRD: "", | ||||
|       articles: [], | ||||
|       selectedArticleId: null, | ||||
|       selectedArticleBody: "", | ||||
|       sights: [], | ||||
|       showSightsList: false, | ||||
|       selectedLetter: null, | ||||
|       showTransfers: false, | ||||
|       transferIcons: { | ||||
|         tram: require("@/icons/tram.svg"), | ||||
|         trolleybus: require("@/icons/trolleybus.svg"), | ||||
|         bus: require("@/icons/bus.svg"), | ||||
|         train: require("@/icons/train.svg"), | ||||
|         metro_red: require("@/icons/metro_red.svg"), | ||||
|         metro_green: require("@/icons/metro_green.svg"), | ||||
|         metro_blue: require("@/icons/metro_blue.svg"), | ||||
|         metro_purple: require("@/icons/metro_purple.svg"), | ||||
|         metro_orange: require("@/icons/metro_orange.svg"), | ||||
|       }, | ||||
|       nextStopTransfers: null, | ||||
|       stops: [], | ||||
|       routeProgress: null, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     availableLetters() { | ||||
|       const letters = this.sights | ||||
|         .map((s) => { | ||||
|           if (!s.name || typeof s.name !== "string") { | ||||
|             console.warn("Invalid sight object or missing name:", s); | ||||
|             return null; | ||||
|           } | ||||
|           return s.name[0].toUpperCase(); | ||||
|         }) | ||||
|         .filter((l) => l !== null); | ||||
|       return [...new Set(letters)].sort((a, b) => a.localeCompare(b, "ru")); | ||||
|     }, | ||||
|     groupedSights() { | ||||
|       const map = {}; | ||||
|       const validSights = this.sights.filter((sight) => { | ||||
|         const isValid = sight.name && typeof sight.name === "string"; | ||||
|         if (!isValid) { | ||||
|           console.warn("Skipping invalid sight in groupedSights:", sight); | ||||
|         } | ||||
|         return isValid; | ||||
|       }); | ||||
|       const sorted = validSights.sort((a, b) => | ||||
|         a.name.localeCompare(b.name, "ru") | ||||
|       ); | ||||
|       for (const sight of sorted) { | ||||
|         const letter = sight.name[0].toUpperCase(); | ||||
|         if (!map[letter]) map[letter] = []; | ||||
|         map[letter].push(sight); | ||||
|       } | ||||
|       return map; | ||||
|     }, | ||||
|     filteredTransfers() { | ||||
|       if (!this.nextStopTransfers) return {}; | ||||
|       return Object.fromEntries( | ||||
|         Object.entries(this.nextStopTransfers).filter(([, value]) => value) | ||||
|       ); | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     async checkAuth() { | ||||
|       let token = Cookies.get("auth_token"); | ||||
|       if (!token) { | ||||
|         await this.requestAuth(); | ||||
|         return; | ||||
|       } | ||||
|       try { | ||||
|         await axios.get("https://wn-ts.krbl.ru/auth/me", { | ||||
|           headers: { | ||||
|             Authorization: `Bearer ${token}`, | ||||
|           }, | ||||
|         }); | ||||
|       } catch (e) { | ||||
|         await this.requestAuth(); | ||||
|       } | ||||
|     }, | ||||
|     async requestAuth() { | ||||
|       const response = await axios.post("https://wn-ts.krbl.ru/auth/login", { | ||||
|         email: "admin", | ||||
|         password: "changeme", | ||||
|       }); | ||||
|       const token = response.data.token; | ||||
|       Cookies.set("auth_token", token); | ||||
|       document.cookie = `auth_token=${token}; path=/;`; | ||||
|     }, | ||||
|     getCookie(name) { | ||||
|       const matches = document.cookie.match( | ||||
|         new RegExp( | ||||
|           "(?:^|; )" + | ||||
|             name.replace(/([.$?*|{}()[\]\\/+^])/g, "\\$1") + | ||||
|             "=([^;]*)" | ||||
|         ) | ||||
|       ); | ||||
|       return matches ? decodeURIComponent(matches[1]) : undefined; | ||||
|     }, | ||||
|     openModal() { | ||||
|       const imageDiv = this.$el.querySelector(".img"); | ||||
|       if (imageDiv) { | ||||
|         const bgImage = window.getComputedStyle(imageDiv).backgroundImage; | ||||
|         this.imageUrl = bgImage.slice(5, bgImage.length - 2); | ||||
|       } | ||||
|       this.isModalOpen = true; | ||||
|       this.autoCloseTimer = setTimeout(() => { | ||||
|         this.closeModal(); | ||||
|       }, 30000); | ||||
|     }, | ||||
|     closeModal() { | ||||
|       this.isModalOpen = false; | ||||
|       if (this.autoCloseTimer) { | ||||
|         clearTimeout(this.autoCloseTimer); | ||||
|         this.autoCloseTimer = null; | ||||
|       } | ||||
|     }, | ||||
|     async fetchSightInfo() { | ||||
|       if (!this.sightId) { | ||||
|         console.warn("No sightId provided for fetchSightInfo"); | ||||
|         return; | ||||
|       } | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}`, | ||||
|         { | ||||
|           headers: { | ||||
|             Authorization: `Bearer ${token}`, | ||||
|           }, | ||||
|         } | ||||
|       ); | ||||
|       this.stopName = response.data.name; | ||||
|       this.watermarkLU = response.data.watermark_lu | ||||
|         ? `http://31.129.106.67:8080/media/${response.data.watermark_lu}/download` | ||||
|         : ""; | ||||
|       this.watermarkRD = response.data.watermark_rd | ||||
|         ? `http://31.129.106.67:8080/media/${response.data.watermark_rd}/download` | ||||
|         : ""; | ||||
|     }, | ||||
|     async fetchArticles() { | ||||
|       if (!this.sightId) { | ||||
|         console.warn("No sightId provided for fetchArticles"); | ||||
|         return; | ||||
|       } | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}/article`, | ||||
|         { | ||||
|           headers: { | ||||
|             Authorization: `Bearer ${token}`, | ||||
|           }, | ||||
|         } | ||||
|       ); | ||||
|       this.articles = response.data; | ||||
|       if (this.articles.length > 0) { | ||||
|         this.selectArticle(this.articles[0].id); | ||||
|       } | ||||
|     }, | ||||
|     async fetchSights() { | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       const geoRes = await axios.get( | ||||
|         "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|       ); | ||||
|       const routeId = geoRes.data.routeId; | ||||
|       if (!routeId) { | ||||
|         console.warn("Missing routeId in geo context:", geoRes.data); | ||||
|         return; | ||||
|       } | ||||
|       const sightsRes = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/route/${routeId}/sight`, | ||||
|         { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         } | ||||
|       ); | ||||
|       const rawSights = sightsRes.data; | ||||
|       const detailedSights = await Promise.all( | ||||
|         rawSights.map(async (sight) => { | ||||
|           const detailRes = await axios.get( | ||||
|             `https://wn-ts.krbl.ru/sight/${sight.id}`, | ||||
|             { | ||||
|               headers: { Authorization: `Bearer ${token}` }, | ||||
|             } | ||||
|           ); | ||||
|           return { | ||||
|             id: sight.id, | ||||
|             name: detailRes.data.name, | ||||
|             thumbnail: detailRes.data.thumbnail, | ||||
|           }; | ||||
|         }) | ||||
|       ); | ||||
|       this.sights = detailedSights; | ||||
|     }, | ||||
|     toggleSightsList() { | ||||
|       this.showSightsList = !this.showSightsList; | ||||
|     }, | ||||
|     selectArticle(id) { | ||||
|       this.selectedArticleId = id; | ||||
|       const selected = this.articles.find((article) => article.id === id); | ||||
|       this.selectedArticleBody = selected ? selected.body : ""; | ||||
|       this.fetchArticleMedia(id); | ||||
|     }, | ||||
|     async fetchArticleMedia(articleId) { | ||||
|       const token = this.getCookie("auth_token"); | ||||
|       try { | ||||
|         const response = await axios.get( | ||||
|           `https://wn-ts.krbl.ru/article/${articleId}/media`, | ||||
|           { | ||||
|             headers: { | ||||
|               Authorization: `Bearer ${token}`, | ||||
|             }, | ||||
|           } | ||||
|         ); | ||||
|         if (response.data && response.data.length > 0) { | ||||
|           const mediaId = response.data[0].id; | ||||
|           this.imageUrl = `http://31.129.106.67:8080/media/${mediaId}/download`; | ||||
|         } else { | ||||
|           this.imageUrl = ""; | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.error("Error fetching article media:", error); | ||||
|         this.imageUrl = ""; | ||||
|       } | ||||
|     }, | ||||
|     async fetchGeolocationContext() { | ||||
|       try { | ||||
|         const response = await axios.get( | ||||
|           "http://31.129.106.67:6001/v1/geolocation/context" | ||||
|         ); | ||||
|         this.routeProgress = response.data.routeProgress; | ||||
|         console.log("Updated routeProgress:", this.routeProgress); | ||||
|         const token = this.getCookie("auth_token"); | ||||
|         const stopsResponse = await axios.get( | ||||
|           `https://wn-ts.krbl.ru/route/${response.data.routeId}/station`, | ||||
|           { | ||||
|             headers: { Authorization: `Bearer ${token}` }, | ||||
|           } | ||||
|         ); | ||||
|         this.stops = stopsResponse.data; | ||||
|         const newSightId = response.data.nearestSightId; | ||||
|         if (newSightId && newSightId !== this.sightId) { | ||||
|           this.sightId = newSightId; | ||||
|           await this.fetchSightInfo(); | ||||
|           await this.fetchArticles(); | ||||
|         } | ||||
|         const nextStopId = response.data.routeProgress?.endStopId; | ||||
|         console.log("Fetched next stop ID:", nextStopId); | ||||
|         console.log("Stops:", this.stops); | ||||
|         if (nextStopId && this.stops) { | ||||
|           const nextStop = this.stops.find((stop) => stop.id == nextStopId); | ||||
|           console.log("Fetched next stop ID:", nextStopId); | ||||
|           console.log("Matching stop:", nextStop); | ||||
|           if (nextStop && nextStop.transfers) { | ||||
|             console.log("Transfers at next stop:", nextStop.transfers); | ||||
|             this.nextStopTransfers = nextStop.transfers; | ||||
|           } else { | ||||
|             console.log("No transfers found at next stop"); | ||||
|             this.nextStopTransfers = null; | ||||
|           } | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.error("Error fetching geolocation context:", error); | ||||
|       } | ||||
|     }, | ||||
|     selectLetter(letter) { | ||||
|       const section = this.$refs[`letter-${letter}`]?.[0]; | ||||
|       const container = this.$refs.scrollContainer; | ||||
|       if (section && container) { | ||||
|         const top = section.offsetTop; | ||||
|         container.scrollTo({ top, behavior: "smooth" }); | ||||
|       } | ||||
|     }, | ||||
|     toggleTransfers() { | ||||
|       console.log("Transfer toggle clicked"); | ||||
|       this.showTransfers = !this.showTransfers; | ||||
|       console.log("showTransfers:", this.showTransfers); | ||||
|  | ||||
|       if (!this.showTransfers) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (!this.stops || !this.routeProgress) { | ||||
|         console.warn("Missing stops or routeProgress"); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       const nextStopId = this.routeProgress.endStopId; | ||||
|       console.log("Next stop ID from routeProgress:", nextStopId); | ||||
|       console.log("All stops:", this.stops); | ||||
|  | ||||
|       const nextStop = this.stops.find((stop) => stop.id === nextStopId); | ||||
|       console.log("Next stop object:", nextStop); | ||||
|  | ||||
|       if (nextStop && nextStop.transfers) { | ||||
|         console.log("Transfers at next stop:", nextStop.transfers); | ||||
|         this.nextStopTransfers = nextStop.transfers; | ||||
|       } else { | ||||
|         console.log("No transfers found at next stop"); | ||||
|         this.nextStopTransfers = null; | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   async mounted() { | ||||
|     await this.checkAuth(); | ||||
|     await this.fetchGeolocationContext(); | ||||
|     await this.fetchSights(); | ||||
|     this.geolocationInterval = setInterval(() => { | ||||
|       this.fetchGeolocationContext(); | ||||
|     }, 1000); | ||||
|     // this.fetchSightInfo(); | ||||
|     // this.fetchArticles(); | ||||
|   }, | ||||
|   unmounted() { | ||||
|     if (this.autoCloseTimer) { | ||||
|       clearTimeout(this.autoCloseTimer); | ||||
|     } | ||||
|     if (this.geolocationInterval) { | ||||
|       clearInterval(this.geolocationInterval); | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										286
									
								
								src/components/weatherinfo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,286 @@ | ||||
| <template> | ||||
|   <div class="weatherinfo"> | ||||
|     <div class="time">{{ formattedTime }}</div> | ||||
|     <div class="date">{{ formattedDate }}, {{ dayOfWeek }}</div> | ||||
|  | ||||
|     <div class="weather-forecast"> | ||||
|       <div class="current-weather"> | ||||
|         <img | ||||
|           :src="weatherIcon(currentWeather.description, true)" | ||||
|           alt="Weather Icon" | ||||
|         /> | ||||
|         <div class="temperature-celsius"> | ||||
|           {{ currentWeather.temperatureCelsius }}°C | ||||
|         </div> | ||||
|         <div> | ||||
|           {{ currentWeather.description }} | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <div class="forecast"> | ||||
|         <div v-for="day in forecastDays" :key="day.date" class="forecast-day"> | ||||
|           <img :src="weatherIcon(day.description)" alt="Forecast Icon" /> | ||||
|           <div>{{ day.dayShort }}</div> | ||||
|           <div class="forecast-temperature">{{ day.temperatureCelsius }}°</div> | ||||
|         </div> | ||||
|         <div class="additional-forecast"> | ||||
|           <div class="humidity"> | ||||
|             <svg | ||||
|               xmlns="http://www.w3.org/2000/svg" | ||||
|               width="12" | ||||
|               height="16" | ||||
|               fill="none" | ||||
|               viewBox="0 0 12 16" | ||||
|             > | ||||
|               <path | ||||
|                 fill="#fff" | ||||
|                 d="M6 15.92a5.708 5.708 0 0 1-5.702-5.702c0-3.155 3.36-7.75 4.804-9.578C5.32.363 5.647.205 6 .205s.68.158.897.435c1.445 1.83 4.806 6.425 4.806 9.578A5.71 5.71 0 0 1 6 15.92ZM6 1.203a.143.143 0 0 0-.112.055c-1.078 1.362-4.59 6.037-4.59 8.957A4.708 4.708 0 0 0 6 14.918a4.708 4.708 0 0 0 4.703-4.703c0-2.92-3.513-7.595-4.588-8.957A.15.15 0 0 0 6 1.203Z" | ||||
|               /> | ||||
|             </svg> | ||||
|  | ||||
|             <span>{{ currentWeather.humidity }}%</span> | ||||
|           </div> | ||||
|           <div class="wind"> | ||||
|             <svg | ||||
|               xmlns="http://www.w3.org/2000/svg" | ||||
|               width="16" | ||||
|               height="16" | ||||
|               fill="none" | ||||
|               viewBox="0 0 16 16" | ||||
|             > | ||||
|               <g class="det_wind"> | ||||
|                 <path | ||||
|                   fill="#fff" | ||||
|                   d="M5.507 5.83H1.15a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5h4.355c.995 0 1.805-.81 1.805-1.805 0-.995-.81-1.805-1.805-1.805-.995 0-1.805.81-1.805 1.805 0 .275-.225.5-.5.5a.501.501 0 0 1-.5-.5A2.809 2.809 0 0 1 5.505.22 2.81 2.81 0 0 1 8.31 3.025 2.808 2.808 0 0 1 5.507 5.83Z" | ||||
|                   class="Vector" | ||||
|                 /> | ||||
|                 <path | ||||
|                   fill="#fff" | ||||
|                   d="M12.543 8.575H3.537a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5h9.006c.995 0 1.805-.81 1.805-1.805 0-.995-.81-1.805-1.806-1.805-.995 0-1.804.81-1.804 1.805 0 .275-.226.5-.5.5a.501.501 0 0 1-.5-.5 2.809 2.809 0 0 1 2.805-2.805 2.809 2.809 0 0 1 2.805 2.805 2.809 2.809 0 0 1-2.806 2.805Zm-2.385 7.207a2.809 2.809 0 0 1-2.806-2.805c0-.275.225-.5.5-.5s.5.225.5.5c0 .995.81 1.805 1.806 1.805.995 0 1.805-.81 1.805-1.805 0-.995-.81-1.805-1.805-1.805H1.15a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5h9.005a2.809 2.809 0 0 1 2.805 2.805 2.807 2.807 0 0 1-2.803 2.805Z" | ||||
|                   class="Vector" | ||||
|                 /> | ||||
|               </g> | ||||
|             </svg> | ||||
|  | ||||
|             <span>{{ currentWeather.windSpeed }} м/с</span> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import "../assets/style/main.css"; | ||||
| import axios from "axios"; | ||||
| import Cookies from "js-cookie"; | ||||
| import clearIcon from "@/icons/clear-day.svg"; | ||||
| import cloudsIcon from "@/icons/cloudy.svg"; | ||||
| import rainIcon from "@/icons/rainy.svg"; | ||||
| import snowIcon from "@/icons/snowy.svg"; | ||||
| import defaultIcon from "@/icons/default.svg"; | ||||
| import thunderIcon from "@/icons/thunderstorms.svg"; | ||||
| import fogIcon from "@/icons/fog.svg"; | ||||
|  | ||||
| export default { | ||||
|   name: "WeatherInfo", | ||||
|   data() { | ||||
|     return { | ||||
|       isModalOpen: false, | ||||
|       autoCloseTimer: null, | ||||
|       imageUrl: "", | ||||
|       sightId: 14, | ||||
|       stopName: "", | ||||
|       articles: [], | ||||
|       selectedArticleId: null, | ||||
|       selectedArticleBody: "", | ||||
|       currentWeather: { description: "", temperatureCelsius: "" }, | ||||
|       forecastDays: [], | ||||
|       formattedTime: "", | ||||
|       formattedDate: "", | ||||
|       dayOfWeek: "", | ||||
|       timerInterval: null, | ||||
|       weatherDescriptionMap: { | ||||
|         thunderstorm: "Гроза", | ||||
|         drizzle: "Слабый дождь", | ||||
|         rain: "Дождь", | ||||
|         snow: "Снег", | ||||
|         atmosphere: "Туман", | ||||
|         clear: "Ясно", | ||||
|         clouds: "Облачно", | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
|   methods: { | ||||
|     async getToken() { | ||||
|       let token = Cookies.get("auth_token"); | ||||
|       try { | ||||
|         await axios.get("https://wn-ts.krbl.ru/auth/me", { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         }); | ||||
|       } catch (error) { | ||||
|         if (error.response && error.response.status === 401) { | ||||
|           const response = await axios.post( | ||||
|             "https://wn-ts.krbl.ru/auth/login", | ||||
|             { | ||||
|               email: "admin", | ||||
|               password: "changeme", | ||||
|             } | ||||
|           ); | ||||
|           token = response.data.token; | ||||
|           Cookies.set("auth_token", token); | ||||
|         } | ||||
|       } | ||||
|       return token; | ||||
|     }, | ||||
|     async fetchSightInfo() { | ||||
|       const token = await this.getToken(); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}`, | ||||
|         { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         } | ||||
|       ); | ||||
|       this.stopName = response.data.name; | ||||
|     }, | ||||
|     async fetchArticles() { | ||||
|       const token = await this.getToken(); | ||||
|       const response = await axios.get( | ||||
|         `https://wn-ts.krbl.ru/sight/${this.sightId}/article`, | ||||
|         { | ||||
|           headers: { Authorization: `Bearer ${token}` }, | ||||
|         } | ||||
|       ); | ||||
|       this.articles = response.data; | ||||
|       if (this.articles.length > 0) { | ||||
|         this.selectArticle(this.articles[0].id); | ||||
|       } | ||||
|     }, | ||||
|     selectArticle(id) { | ||||
|       this.selectedArticleId = id; | ||||
|       const selected = this.articles.find((article) => article.id === id); | ||||
|       this.selectedArticleBody = selected ? selected.body : ""; | ||||
|     }, | ||||
|     getCurrentTime() { | ||||
|       const now = new Date(); | ||||
|       const separator = now.getSeconds() % 2 === 0 ? ":" : " "; | ||||
|       this.formattedTime = now | ||||
|         .toLocaleTimeString("ru-RU", { | ||||
|           hour: "2-digit", | ||||
|           minute: "2-digit", | ||||
|           hour12: false, | ||||
|         }) | ||||
|         .replace(":", separator); | ||||
|       this.formattedDate = now.toLocaleDateString("ru-RU", { | ||||
|         day: "2-digit", | ||||
|         month: "2-digit", | ||||
|       }); | ||||
|       this.dayOfWeek = now.toLocaleDateString("ru-RU", { weekday: "long" }); | ||||
|     }, | ||||
|     weatherIcon(description) { | ||||
|       if (!description) return clearIcon; | ||||
|  | ||||
|       switch (this.reverseTranslateDescription(description).toLowerCase()) { | ||||
|         case "clear": | ||||
|           return clearIcon; | ||||
|         case "clouds": | ||||
|           return cloudsIcon; | ||||
|         case "rain": | ||||
|         case "drizzle": | ||||
|           return rainIcon; | ||||
|         case "snow": | ||||
|           return snowIcon; | ||||
|         case "thunderstorm": | ||||
|           return thunderIcon; | ||||
|         case "atmosphere": | ||||
|           return fogIcon; | ||||
|         default: | ||||
|           return defaultIcon; | ||||
|       } | ||||
|     }, | ||||
|     reverseTranslateDescription(translated) { | ||||
|       const entry = Object.entries(this.weatherDescriptionMap).find( | ||||
|         ([, value]) => value === translated | ||||
|       ); | ||||
|       return entry ? entry[0] : translated; | ||||
|     }, | ||||
|     async fetchWeatherData() { | ||||
|       console.log("Fetching weather data..."); | ||||
|       const now = new Date(); | ||||
|       const response = await axios.post( | ||||
|         "https://weather.wn.krbl.ru/v1/weather", | ||||
|         { | ||||
|           coordinates: { latitude: 59.938784, longitude: 30.314997 }, | ||||
|         } | ||||
|       ); | ||||
|       this.currentWeather = { | ||||
|         ...response.data.currentWeather, | ||||
|         temperatureCelsius: Math.round( | ||||
|           response.data.currentWeather.temperatureCelsius | ||||
|         ), | ||||
|       }; | ||||
|       const descriptionKey = | ||||
|         response.data.currentWeather.description.toLowerCase(); | ||||
|       this.currentWeather.description = | ||||
|         this.weatherDescriptionMap[descriptionKey] || | ||||
|         response.data.currentWeather.description; | ||||
|  | ||||
|       const nextThreeDays = response.data.forecast | ||||
|         .filter((item) => { | ||||
|           const itemDate = new Date(item.date); | ||||
|           return itemDate > now; | ||||
|         }) | ||||
|         .sort((a, b) => new Date(a.date) - new Date(b.date)) | ||||
|         .reduce((acc, item) => { | ||||
|           const day = item.date.split(" ")[0]; | ||||
|           if (!acc[day]) { | ||||
|             acc[day] = { | ||||
|               date: item.date, | ||||
|               description: item.description, | ||||
|               temps: [item.minTemperatureCelsius, item.maxTemperatureCelsius], | ||||
|             }; | ||||
|           } else { | ||||
|             acc[day].temps.push( | ||||
|               item.minTemperatureCelsius, | ||||
|               item.maxTemperatureCelsius | ||||
|             ); | ||||
|           } | ||||
|           return acc; | ||||
|         }, {}); | ||||
|  | ||||
|       this.forecastDays = Object.entries(nextThreeDays) | ||||
|         .slice(1, 4) | ||||
|         .map(([, info]) => { | ||||
|           const avgTemp = | ||||
|             info.temps.reduce((a, b) => a + b, 0) / info.temps.length; | ||||
|           const descriptionKey = info.description.toLowerCase(); | ||||
|           const translatedDescription = | ||||
|             this.weatherDescriptionMap[descriptionKey] || info.description; | ||||
|           return { | ||||
|             date: info.date, | ||||
|             description: translatedDescription, | ||||
|             temperatureCelsius: Math.round(avgTemp), | ||||
|             dayShort: new Date(info.date) | ||||
|               .toLocaleDateString("ru-RU", { weekday: "short" }) | ||||
|               .replace(/^\p{L}/u, (char) => char.toUpperCase()), | ||||
|           }; | ||||
|         }); | ||||
|     }, | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.fetchSightInfo(); | ||||
|     this.fetchArticles(); | ||||
|     this.fetchWeatherData(); | ||||
|     this.getCurrentTime(); | ||||
|     this.timerInterval = setInterval(this.getCurrentTime, 1000); | ||||
|   }, | ||||
|   unmounted() { | ||||
|     if (this.timerInterval) { | ||||
|       clearInterval(this.timerInterval); | ||||
|     } | ||||
|     if (this.autoCloseTimer) { | ||||
|       clearTimeout(this.autoCloseTimer); | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										11
									
								
								src/icons/bus.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <g clip-path="url(#a)"> | ||||
|     <path fill="#fff" d="M63.03 32.175c-.097 17.144-14.06 30.952-31.204 30.855C14.68 62.933.873 48.97.97 31.806 1.086 14.681 15.05.873 32.175.97c17.144.097 30.972 14.08 30.855 31.205Z"/> | ||||
|     <path fill="#816C5A" d="m41.658 12.276-19.064-.116a5.459 5.459 0 0 0-5.489 5.43l-.155 26.628a5.452 5.452 0 0 0 4.17 5.314v.194c0 1.105.892 1.998 1.998 2.017a2.02 2.02 0 0 0 2.017-1.978v-.02l13.42.078v.02c0 1.085.892 1.997 1.978 2.016 1.106 0 2.017-.892 2.017-1.978v-.136a5.453 5.453 0 0 0 4.403-5.333l.155-26.608c0-3.045-2.444-5.508-5.45-5.528ZM20.907 22.458c0-1.435 1.454-2.58 3.18-2.58l15.787.098c1.745 0 3.18 1.183 3.161 2.618l-.039 9.212c-.019 1.435-1.454 2.58-3.2 2.58l-15.786-.098c-1.746 0-3.162-1.182-3.162-2.618l.059-9.212Zm3.937 19.452c-.466.505-1.048.737-1.785.737-.717 0-1.318-.252-1.803-.756-.485-.504-.718-1.105-.718-1.823s.252-1.3.776-1.745a2.692 2.692 0 0 1 1.804-.66c.717 0 1.28.252 1.745.718.446.484.679 1.066.679 1.706a2.47 2.47 0 0 1-.698 1.823Zm17.61.097c-.486.505-1.067.737-1.785.737-.717 0-1.319-.252-1.784-.756-.485-.504-.737-1.105-.718-1.823 0-.718.252-1.3.776-1.746a2.692 2.692 0 0 1 1.804-.659c.717 0 1.28.233 1.745.718.446.504.679 1.047.66 1.706 0 .757-.233 1.358-.699 1.823Z"/> | ||||
|   </g> | ||||
|   <defs> | ||||
|     <clipPath id="a"> | ||||
|       <path fill="#fff" d="M0 0h64v64H0z"/> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										10
									
								
								src/icons/clear-day.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <g clip-path="url(#a)"> | ||||
|     <path fill="#FCD500" d="M19.4 21.64c-.57 0-1.14-.22-1.58-.65l-8.45-8.46a2.227 2.227 0 1 1 3.15-3.15l8.45 8.45c.87.87.87 2.28 0 3.15-.43.44-1 .66-1.57.66Zm42.37 12.6H49.81c-1.23 0-2.23-1-2.23-2.23s1-2.23 2.23-2.23h11.96c1.23 0 2.23 1 2.23 2.23s-1 2.23-2.23 2.23ZM44.6 21.64c-.57 0-1.14-.22-1.58-.65-.87-.87-.87-2.28 0-3.15l8.45-8.45a2.227 2.227 0 1 1 3.15 3.15l-8.45 8.45c-.43.43-1 .65-1.57.65ZM32 16.42c-1.23 0-2.23-1-2.23-2.23V2.23C29.77 1 30.77 0 32 0s2.23 1 2.23 2.23v11.96c0 1.23-1 2.23-2.23 2.23ZM14.19 34.24H2.24C1 34.24 0 33.24 0 32.01s1-2.23 2.23-2.23h11.95c1.23 0 2.23 1 2.23 2.23s-.99 2.23-2.22 2.23Zm-3.24 21.05c-.57 0-1.14-.22-1.58-.65-.87-.87-.87-2.28 0-3.15l8.45-8.45a2.227 2.227 0 1 1 3.15 3.15l-8.45 8.45c-.43.43-1 .65-1.57.65ZM32 64.01c-1.23 0-2.23-1-2.23-2.23V49.82c0-1.23 1-2.23 2.23-2.23s2.23 1 2.23 2.23v11.96c0 1.23-1 2.23-2.23 2.23Zm21.05-8.72c-.57 0-1.14-.22-1.58-.65l-8.45-8.45a2.227 2.227 0 1 1 3.15-3.15l8.45 8.45a2.227 2.227 0 0 1-1.57 3.8ZM32 43.93c6.625 0 12-5.375 12-12s-5.375-12-12-12-12 5.375-12 12 5.375 12 12 12Z"/> | ||||
|   </g> | ||||
|   <defs> | ||||
|     <clipPath id="a"> | ||||
|       <path fill="#fff" d="M0 0h64v64H0z"/> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										3
									
								
								src/icons/cloudy.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#fff" d="M47.15 14.23c-.31 0-.62.01-.92.03-1.42.08-2.81-.53-3.6-1.72C39.6 7.98 34.51 5 28.75 5c-7.17 0-13.3 4.63-15.73 11.15-.4 1.09-1.3 1.93-2.41 2.29C4.47 20.42 0 26.35 0 33.36 0 42 6.78 49 15.15 49h32C56.46 49 64 41.22 64 31.61c0-9.6-7.54-17.38-16.85-17.38Z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 381 B | 
							
								
								
									
										10
									
								
								src/icons/default.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <g clip-path="url(#a)"> | ||||
|     <path fill="#FCD500" d="M19.4 21.64c-.57 0-1.14-.22-1.58-.65l-8.45-8.46a2.227 2.227 0 1 1 3.15-3.15l8.45 8.45c.87.87.87 2.28 0 3.15-.43.44-1 .66-1.57.66Zm42.37 12.6H49.81c-1.23 0-2.23-1-2.23-2.23s1-2.23 2.23-2.23h11.96c1.23 0 2.23 1 2.23 2.23s-1 2.23-2.23 2.23ZM44.6 21.64c-.57 0-1.14-.22-1.58-.65-.87-.87-.87-2.28 0-3.15l8.45-8.45a2.227 2.227 0 1 1 3.15 3.15l-8.45 8.45c-.43.43-1 .65-1.57.65ZM32 16.42c-1.23 0-2.23-1-2.23-2.23V2.23C29.77 1 30.77 0 32 0s2.23 1 2.23 2.23v11.96c0 1.23-1 2.23-2.23 2.23ZM14.19 34.24H2.24C1 34.24 0 33.24 0 32.01s1-2.23 2.23-2.23h11.95c1.23 0 2.23 1 2.23 2.23s-.99 2.23-2.22 2.23Zm-3.24 21.05c-.57 0-1.14-.22-1.58-.65-.87-.87-.87-2.28 0-3.15l8.45-8.45a2.227 2.227 0 1 1 3.15 3.15l-8.45 8.45c-.43.43-1 .65-1.57.65ZM32 64.01c-1.23 0-2.23-1-2.23-2.23V49.82c0-1.23 1-2.23 2.23-2.23s2.23 1 2.23 2.23v11.96c0 1.23-1 2.23-2.23 2.23Zm21.05-8.72c-.57 0-1.14-.22-1.58-.65l-8.45-8.45a2.227 2.227 0 1 1 3.15-3.15l8.45 8.45a2.227 2.227 0 0 1-1.57 3.8ZM32 43.93c6.625 0 12-5.375 12-12s-5.375-12-12-12-12 5.375-12 12 5.375 12 12 12Z"/> | ||||
|   </g> | ||||
|   <defs> | ||||
|     <clipPath id="a"> | ||||
|       <path fill="#fff" d="M0 0h64v64H0z"/> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										4
									
								
								src/icons/fog.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#FCD500" d="M49.02 29.96c8.27 0 14.98-6.71 14.98-14.98S57.29 0 49.02 0 34.04 6.71 34.04 14.98c-.01 8.28 6.7 14.98 14.98 14.98Z"/> | ||||
|   <path fill="#fff" d="M43.467 17.383c-.272 0-.554.01-.826.02a3.974 3.974 0 0 1-3.445-1.708C36.386 11.642 31.75 9 26.503 9c-6.538 0-12.128 4.112-14.425 9.933-.443 1.122-1.36 1.967-2.508 2.354C4.01 23.154 0 28.498 0 34.786 0 42.633 6.256 49 13.972 49h29.495C52.039 49 59 41.928 59 33.197c0-8.731-6.95-15.814-15.533-15.814Z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 572 B | 
							
								
								
									
										4
									
								
								src/icons/metro_blue.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#2D3B8E" fill-rule="evenodd" d="M62.933 31.961C62.933 14.836 49.008.97 31.942.97 14.875.97.872 14.817.872 31.96c0 17.125 13.829 31.07 31.07 31.07s30.991-13.906 30.991-31.07Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M55.796 31.05c-.698-9.891-7.68-17.125-15.573-19.84-2.773 9.057-5.508 18.269-8.32 27.21-2.831-8.941-5.547-18.153-8.32-27.21C15.69 13.925 8.708 21.159 8.01 31.05a16.371 16.371 0 0 0 0 3.083c.349 6.478 3.18 12.277 6.807 15.845h9.561c.136-.446-.795-.892-1.28-1.183-3.685-2.676-7.389-6.09-9.173-10.802-2.463-6.09-1.125-13.712 2.23-18.017.892-1.028 3.86-3.801 5.74-2.366.64.504 1.087 2.734 1.494 3.86 2.87 9.25 8.436 27.46 8.436 27.46l.097.33.097-.33s5.566-18.21 8.437-27.46c.388-1.126.834-3.375 1.493-3.86 1.881-1.435 4.849 1.338 5.74 2.366 3.375 4.305 4.713 11.927 2.231 18.017-1.784 4.712-5.508 8.126-9.173 10.802-.485.291-1.416.737-1.28 1.183h9.56c3.627-3.568 6.44-9.348 6.808-15.845.078-.989.078-1.997-.039-3.083Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										4
									
								
								src/icons/metro_green.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#056939" fill-rule="evenodd" d="M62.933 31.961C62.933 14.836 49.008.97 31.942.97 14.875.97.872 14.817.872 31.96c0 17.125 13.829 31.07 31.07 31.07s30.991-13.906 30.991-31.07Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M55.796 31.05c-.698-9.891-7.68-17.125-15.573-19.84-2.773 9.057-5.508 18.269-8.32 27.21-2.831-8.941-5.547-18.153-8.32-27.21C15.69 13.925 8.708 21.159 8.01 31.05a16.371 16.371 0 0 0 0 3.083c.349 6.478 3.18 12.277 6.807 15.845h9.561c.136-.446-.795-.892-1.28-1.183-3.685-2.676-7.389-6.09-9.173-10.802-2.463-6.09-1.125-13.712 2.23-18.017.892-1.028 3.86-3.801 5.74-2.366.64.504 1.087 2.734 1.494 3.86 2.87 9.25 8.436 27.46 8.436 27.46l.097.33.097-.33s5.566-18.21 8.437-27.46c.388-1.126.834-3.375 1.493-3.86 1.881-1.435 4.849 1.338 5.74 2.366 3.375 4.305 4.713 11.927 2.231 18.017-1.784 4.712-5.508 8.126-9.173 10.802-.485.291-1.416.737-1.28 1.183h9.56c3.627-3.568 6.44-9.348 6.808-15.845.078-.989.078-1.997-.039-3.083Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										4
									
								
								src/icons/metro_orange.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#EB5C2C" fill-rule="evenodd" d="M62.933 31.961C62.933 14.836 49.008.97 31.942.97 14.875.97.872 14.817.872 31.96c0 17.125 13.829 31.07 31.07 31.07s30.991-13.906 30.991-31.07Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M55.796 31.05c-.698-9.891-7.68-17.125-15.573-19.84-2.773 9.057-5.508 18.269-8.32 27.21-2.831-8.941-5.547-18.153-8.32-27.21C15.69 13.925 8.708 21.159 8.01 31.05a16.371 16.371 0 0 0 0 3.083c.349 6.478 3.18 12.277 6.807 15.845h9.561c.136-.446-.795-.892-1.28-1.183-3.685-2.676-7.389-6.09-9.173-10.802-2.463-6.09-1.125-13.712 2.23-18.017.892-1.028 3.86-3.801 5.74-2.366.64.504 1.087 2.734 1.494 3.86 2.87 9.25 8.436 27.46 8.436 27.46l.097.33.097-.33s5.566-18.21 8.437-27.46c.388-1.126.834-3.375 1.493-3.86 1.881-1.435 4.849 1.338 5.74 2.366 3.375 4.305 4.713 11.927 2.231 18.017-1.784 4.712-5.508 8.126-9.173 10.802-.485.291-1.416.737-1.28 1.183h9.56c3.627-3.568 6.44-9.348 6.808-15.845.078-.989.078-1.997-.039-3.083Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										4
									
								
								src/icons/metro_purple.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#64328A" fill-rule="evenodd" d="M62.933 31.961C62.933 14.836 49.008.97 31.942.97 14.875.97.872 14.817.872 31.96c0 17.125 13.829 31.07 31.07 31.07s30.991-13.906 30.991-31.07Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M55.796 31.05c-.698-9.891-7.68-17.125-15.573-19.84-2.773 9.057-5.508 18.269-8.32 27.21-2.831-8.941-5.547-18.153-8.32-27.21C15.69 13.925 8.708 21.159 8.01 31.05a16.371 16.371 0 0 0 0 3.083c.349 6.478 3.18 12.277 6.807 15.845h9.561c.136-.446-.795-.892-1.28-1.183-3.685-2.676-7.389-6.09-9.173-10.802-2.463-6.09-1.125-13.712 2.23-18.017.892-1.028 3.86-3.801 5.74-2.366.64.504 1.087 2.734 1.494 3.86 2.87 9.25 8.436 27.46 8.436 27.46l.097.33.097-.33s5.566-18.21 8.437-27.46c.388-1.126.834-3.375 1.493-3.86 1.881-1.435 4.849 1.338 5.74 2.366 3.375 4.305 4.713 11.927 2.231 18.017-1.784 4.712-5.508 8.126-9.173 10.802-.485.291-1.416.737-1.28 1.183h9.56c3.627-3.568 6.44-9.348 6.808-15.845.078-.989.078-1.997-.039-3.083Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										4
									
								
								src/icons/metro_red.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#E52629" fill-rule="evenodd" d="M62.933 31.961C62.933 14.836 49.008.97 31.942.97 14.875.97.872 14.817.872 31.96c0 17.125 13.829 31.07 31.07 31.07s30.991-13.906 30.991-31.07Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M55.796 31.05c-.698-9.891-7.68-17.125-15.573-19.84-2.773 9.057-5.508 18.269-8.32 27.21-2.831-8.941-5.547-18.153-8.32-27.21C15.69 13.925 8.708 21.159 8.01 31.05a16.371 16.371 0 0 0 0 3.083c.349 6.478 3.18 12.277 6.807 15.845h9.561c.136-.446-.795-.892-1.28-1.183-3.685-2.676-7.389-6.09-9.173-10.802-2.463-6.09-1.125-13.712 2.23-18.017.892-1.028 3.86-3.801 5.74-2.366.64.504 1.087 2.734 1.494 3.86 2.87 9.25 8.436 27.46 8.436 27.46l.097.33.097-.33s5.566-18.21 8.437-27.46c.388-1.126.834-3.375 1.493-3.86 1.881-1.435 4.849 1.338 5.74 2.366 3.375 4.305 4.713 11.927 2.231 18.017-1.784 4.712-5.508 8.126-9.173 10.802-.485.291-1.416.737-1.28 1.183h9.56c3.627-3.568 6.44-9.348 6.808-15.845.078-.989.078-1.997-.039-3.083Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										4
									
								
								src/icons/rainy.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#fff" d="M47.15 11.22c-.31 0-.62.01-.92.03-1.42.08-2.81-.53-3.6-1.72C39.59 4.98 34.51 2 28.75 2c-7.17 0-13.3 4.63-15.73 11.15-.4 1.09-1.3 1.93-2.41 2.29C4.47 17.41 0 23.35 0 30.35c0 8.64 6.78 15.64 15.15 15.64h32c9.3 0 16.85-7.78 16.85-17.39 0-9.6-7.54-17.38-16.85-17.38Z"/> | ||||
|   <path fill="#00B1FF" fill-rule="evenodd" d="M17.69 48.502a1.5 1.5 0 0 1 .548 2.049l-3.77 6.52a1.5 1.5 0 1 1-2.597-1.502l3.77-6.52a1.5 1.5 0 0 1 2.05-.547Zm8.17 4.719a1.5 1.5 0 0 1 .548 2.05l-3.77 6.52a1.5 1.5 0 0 1-2.597-1.502l3.77-6.52a1.5 1.5 0 0 1 2.05-.547Zm13.339-4.251a1.5 1.5 0 0 1 .55 2.05l-3.76 6.52a1.5 1.5 0 0 1-2.599-1.5l3.76-6.52a1.5 1.5 0 0 1 2.049-.55Zm8.17 4.72a1.5 1.5 0 0 1 .55 2.05l-3.76 6.52a1.5 1.5 0 1 1-2.599-1.5l3.76-6.52a1.5 1.5 0 0 1 2.049-.55Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 888 B | 
							
								
								
									
										6
									
								
								src/icons/snowy.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,6 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M31.95.19a2 2 0 0 1 2 2v59.68a2 2 0 1 1-4 0V2.19a2 2 0 0 1 2-2Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M20.446 4.926a2 2 0 0 1 2.828 0l8.737 8.736 8.615-8.607a2 2 0 1 1 2.828 2.83L32.008 19.318 20.446 7.754a2 2 0 0 1 0-2.828ZM31.89 45.042l11.564 11.564a2 2 0 0 1-2.828 2.828L31.89 50.7l-8.616 8.615a2 2 0 0 1-2.828-2.828L31.89 45.042ZM4.368 16.11a2 2 0 0 1 2.732-.732l51.69 29.84a2 2 0 0 1-2 3.464L5.1 18.842a2 2 0 0 1-.732-2.732Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M14.331 8.578a2 2 0 0 1 2.45 1.413l4.2 15.634-15.804 4.227a2 2 0 1 1-1.034-3.864l11.936-3.193-3.16-11.766a2 2 0 0 1 1.412-2.45Zm35.326-.06a2 2 0 0 1 1.415 2.45l-3.193 11.929 11.77 3.161a2 2 0 1 1-1.038 3.864l-15.63-4.199 4.227-15.79a2 2 0 0 1 2.45-1.415ZM2.568 35.703a2 2 0 0 1 2.45-1.415l15.632 4.188-4.238 15.802a2 2 0 0 1-3.864-1.036l3.202-11.938-11.768-3.152a2 2 0 0 1-1.414-2.45Zm58.864.059a2 2 0 0 1-1.414 2.45L48.08 41.414l3.153 11.768a2 2 0 1 1-3.864 1.036l-4.187-15.632 15.8-4.238a2 2 0 0 1 2.45 1.414Z" clip-rule="evenodd"/> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M59.522 16.11a2 2 0 0 1-.732 2.732L7.1 48.682a2 2 0 0 1-2-3.464l51.69-29.84a2 2 0 0 1 2.732.732Z" clip-rule="evenodd"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.3 KiB | 
							
								
								
									
										4
									
								
								src/icons/thunderstorms.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#fff" d="M47.15 12.22c-.31 0-.62.01-.92.03-1.42.08-2.81-.53-3.6-1.72C39.59 5.98 34.51 3 28.75 3c-7.17 0-13.3 4.63-15.73 11.15-.4 1.09-1.3 1.93-2.41 2.29C4.47 18.42 0 24.35 0 31.35c0 8.64 6.78 15.64 15.15 15.64h32c9.3 0 16.85-7.78 16.85-17.39 0-9.6-7.54-17.38-16.85-17.38Z"/> | ||||
|   <path fill="#FCD500" d="M26.1 47.01 47.64 22.1l-3.11 14.79h12.45L33.37 61.8l3.69-14.79H26.1Z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 490 B | 
							
								
								
									
										4
									
								
								src/icons/train.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <path fill="#fff" fill-rule="evenodd" d="M63.03 31.593C63.03 14.448 49.028.582 31.922.582 14.817.582.97 14.429.97 31.592c0 17.145 13.789 31.05 30.952 31.05 17.164 0 31.108-13.886 31.108-31.05Z" clip-rule="evenodd"/> | ||||
|   <path fill="#816C5A" d="M45.266 39.505V18.25c0-2.968-2.522-5.78-6.013-5.78H33.3l1.377-4.305v-.117c3.724.097 4.403.408 4.461.64 0 .156.252.194.252.194s.446-.038.446-.252c0-.077 0-.349-.136-.485-.678-.543-2.967-.892-7.873-.892-2.289 0-4.17.155-5.508.291-1.843.194-2.735.504-2.735 1.086 0 .194.194.252.194.252s.601-.038.601-.194c0-.077.64-.543 4.46-.64v.097l1.785 4.306h-6.05c-3.434 0-5.994 2.812-5.994 5.78v21.255c0 2.967 2.289 5.353 4.616 5.702h2.075l-5.663 9.736h2.619l.155-.194c0-.078.194-.136.194-.136h18.56s.252.039.388.136v.194h2.773l-5.663-9.736h2.036c2.308-.33 4.597-2.715 4.597-5.683ZM29.596 8.01h4.46l-1.552 4.46h-1.183l-1.726-4.46Zm-1.552 6.632c0-.504.446-.95 1.008-.95h5.74c.602 0 .99.446.99.95v1.726c0 .544-.407.95-.99.95h-5.74c-.64 0-1.008-.407-1.008-.95v-1.726ZM21.74 21.47c0-1.629 1.144-2.967 2.986-2.967h14.371c1.94 0 2.929 1.338 2.929 2.967v3.801c.155 1.9-1.28 2.987-2.929 2.987h-14.37c-1.688 0-2.987-1.086-2.987-2.987v-3.8Zm14.351 23.874c0 .097.99 1.784 1.144 2.056.117.136 0 .194 0 .194H26.92s-.31-.04-.252-.194c.116-.252 1.105-1.94 1.144-2.056.039-.078.155-.136.155-.136h7.835c-.02 0 .175.058.291.136Zm-11.617-3.22C23 42.124 21.74 41 21.74 39.468c0-1.455 1.26-2.58 2.734-2.58 1.338 0 2.58 1.145 2.58 2.58 0 1.532-1.242 2.657-2.58 2.657Zm14.294 7.894c.096.039.95 1.823 1.086 2.036.155.097 0 .194 0 .194h-15.71s-.252-.077-.155-.194c.097-.194 1.145-1.978 1.145-2.036.038-.097.155-.155.155-.155h13.246c-.02 0 .116.039.233.155Zm-2.037-10.55c0-1.455 1.144-2.58 2.676-2.58 1.339 0 2.522 1.145 2.522 2.58 0 1.532-1.183 2.657-2.522 2.657-1.532 0-2.676-1.125-2.676-2.657Z"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										11
									
								
								src/icons/tram.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <g clip-path="url(#a)"> | ||||
|     <path fill="#fff" d="M64 31.787c-.097 17.144-14.08 30.972-31.205 30.875-17.144-.097-30.953-14.08-30.856-31.244C2.036 14.274 16 .446 33.144.543 50.29.64 64.097 14.623 64 31.787Z"/> | ||||
|     <path fill="#816C5A" d="m42.59 17.959-19.065-.117a5.475 5.475 0 0 0-5.508 5.43l-.155 26.648c-.02 3.025 2.405 5.47 5.43 5.508l19.064.116c3.026.02 5.47-2.424 5.489-5.43L48 23.467c.02-3.026-2.405-5.489-5.41-5.508ZM21.837 28.14c0-1.436 1.435-2.6 3.18-2.58l15.787.097c1.765.02 3.18 1.183 3.161 2.618l-.058 9.251c0 1.416-1.435 2.58-3.2 2.58L24.92 40.01c-1.745-.02-3.18-1.183-3.16-2.618l.077-9.251Zm3.937 19.471c-.466.485-1.048.737-1.785.737-.717 0-1.318-.252-1.803-.756-.485-.505-.718-1.106-.718-1.823 0-.718.252-1.3.776-1.746a2.64 2.64 0 0 1 1.803-.679c.718 0 1.3.253 1.746.737.465.485.679 1.048.66 1.707a2.484 2.484 0 0 1-.68 1.823Zm17.61.097c-.486.504-1.067.756-1.785.737-.718-.02-1.319-.271-1.784-.756a2.504 2.504 0 0 1-.718-1.804c0-.737.272-1.3.776-1.765.524-.446 1.105-.679 1.804-.679.717 0 1.299.252 1.745.737.446.485.679 1.048.66 1.707.019.718-.233 1.319-.699 1.823ZM25.93 9.464l2.346 6.982c.233.698.99 1.067 1.688.853.698-.232 1.066-1.008.834-1.706l-1.746-5.198 7.777.039-1.823 5.178a1.359 1.359 0 0 0 .834 1.707 1.348 1.348 0 0 0 1.707-.815l2.424-6.962c.02-.039.02-.097.039-.136v-.039c0-.058.02-.116.02-.194v-.097c0-.097 0-.155-.02-.232a1.328 1.328 0 0 0-.854-1.009c-.194-.058-.368-.077-.562-.058-.058 0-.097-.02-.136-.02L27.442 7.7h-.116a1.43 1.43 0 0 0-.562.059 1.345 1.345 0 0 0-.873.95c-.039.097-.039.194-.039.33 0 .135.02.252.058.368 0 .02.02.039.02.058Z"/> | ||||
|   </g> | ||||
|   <defs> | ||||
|     <clipPath id="a"> | ||||
|       <path fill="#fff" d="M0 0h64v64H0z"/> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										11
									
								
								src/icons/trolleybus.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,11 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"> | ||||
|   <g clip-path="url(#a)"> | ||||
|     <path fill="#fff" d="M63.03 32.175c-.097 17.163-14.08 30.972-31.205 30.894C14.681 62.953.873 48.97.97 31.806 1.067 14.662 15.05.834 32.175.931c17.144.116 30.952 14.08 30.855 31.244Z"/> | ||||
|     <path fill="#816C5A" d="m41.62 16.718-19.065-.097c-3.006-.02-5.488 2.424-5.488 5.43l-.175 26.647c-.02 2.58 1.765 4.732 4.17 5.334v.174a2.02 2.02 0 0 0 1.978 2.017c1.105 0 2.017-.892 2.017-1.978v-.02l13.42.078c0 1.105.893 2.017 1.979 2.017a2 2 0 0 0 2.017-1.998v-.135a5.448 5.448 0 0 0 4.402-5.314l.155-26.648c.04-3.025-2.404-5.488-5.41-5.507ZM20.848 26.899c0-1.415 1.435-2.579 3.2-2.56l15.767.078c1.745 0 3.161 1.164 3.161 2.599l-.058 9.25c0 1.436-1.435 2.6-3.2 2.58l-15.787-.097c-1.765 0-3.18-1.183-3.161-2.618l.078-9.232Zm3.936 19.491c-.465.485-1.047.737-1.784.737-.717 0-1.319-.252-1.803-.756-.485-.504-.699-1.106-.699-1.823 0-.718.253-1.3.776-1.746a2.657 2.657 0 0 1 1.804-.659c.698 0 1.3.252 1.745.737.446.485.68 1.067.68 1.707a2.55 2.55 0 0 1-.719 1.803Zm17.61.117c-.465.485-1.067.737-1.784.717a2.36 2.36 0 0 1-1.784-.756c-.485-.504-.718-1.125-.718-1.823 0-.718.272-1.3.776-1.746.523-.446 1.125-.678 1.803-.659.718 0 1.3.252 1.746.737a2.42 2.42 0 0 1 .66 1.707c.019.698-.233 1.299-.699 1.823ZM28.51 16.058a1.464 1.464 0 0 0 1.9-.93l2.734-7.817a1.475 1.475 0 0 0-.911-1.92 1.515 1.515 0 0 0-1.92.931l-2.735 7.816c-.271.776.136 1.63.931 1.92Zm5.1.02c.775.29 1.648-.136 1.92-.912l2.734-7.835a1.5 1.5 0 0 0-.93-1.92 1.487 1.487 0 0 0-1.901.93l-2.715 7.817a1.495 1.495 0 0 0 .892 1.92Z"/> | ||||
|   </g> | ||||
|   <defs> | ||||
|     <clipPath id="a"> | ||||
|       <path fill="#fff" d="M0 0h64v64H0z"/> | ||||
|     </clipPath> | ||||
|   </defs> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.7 KiB |