1- /* (L)TravelCal - Version 0.29 */
1+ /* (L)TravelCal - Version 0.31 */
22
3- let map , ds , drGo ;
3+ let map , ds , drGo , drBack ;
44let returnMode = false ;
55
66const TUNNEL_DATA = [
@@ -17,7 +17,15 @@ const TUNNEL_DATA = [
1717
1818function initApp ( ) {
1919 ds = new google . maps . DirectionsService ( ) ;
20- drGo = new google . maps . DirectionsRenderer ( { polylineOptions : { strokeColor : "#E3193F" , strokeWeight : 5 } } ) ;
20+ // ✅ 去程:紅色
21+ drGo = new google . maps . DirectionsRenderer ( {
22+ polylineOptions : { strokeColor : "#E3193F" , strokeWeight : 6 , strokeOpacity : 0.8 }
23+ } ) ;
24+ // ✅ 回程:藍色
25+ drBack = new google . maps . DirectionsRenderer ( {
26+ polylineOptions : { strokeColor : "#1976D2" , strokeWeight : 5 , strokeOpacity : 0.7 } ,
27+ suppressMarkers : true
28+ } ) ;
2129
2230 const now = new Date ( ) ;
2331 const offset = now . getTimezoneOffset ( ) * 60000 ;
@@ -27,7 +35,6 @@ function initApp() {
2735 document . querySelectorAll ( '.node-input' ) . forEach ( bindAutocomplete ) ;
2836 renderButtons ( 'goTunnels' , 'go' ) ;
2937 renderButtons ( 'backTunnels' , 'back' ) ;
30- smartFilterTunnels ( ) ;
3138}
3239
3340function bindAutocomplete ( inp ) {
@@ -73,65 +80,52 @@ function smartFilterTunnels() {
7380 const combined = Array . from ( document . querySelectorAll ( '.node-input' ) ) . map ( i => i . value . toLowerCase ( ) ) . join ( " " ) ;
7481 document . querySelectorAll ( '.t-btn' ) . forEach ( btn => {
7582 const data = TUNNEL_DATA . find ( d => d . loc === btn . getAttribute ( 'data-loc' ) ) ;
76- const matched = data . match . toLowerCase ( ) . split ( '|' ) . some ( term => combined . includes ( term ) ) ;
77- if ( showAll || matched ) btn . classList . add ( 'visible' ) ;
83+ if ( showAll || data . match . toLowerCase ( ) . split ( '|' ) . some ( term => combined . includes ( term ) ) ) btn . classList . add ( 'visible' ) ;
7884 else btn . classList . remove ( 'visible' , 'active' ) ;
7985 } ) ;
8086}
8187
8288async function calculate ( ) {
83- const btn = document . querySelector ( '.primary-btn' ) ;
84- btn . style . opacity = "0.7" ;
85-
8689 const inputs = document . querySelectorAll ( '.node-input' ) ;
8790 const locs = Array . from ( inputs ) . map ( i => i . value ) . filter ( v => v . length > 2 ) ;
88- if ( locs . length < 2 ) { btn . style . opacity = "1" ; return ; }
91+ if ( locs . length < 2 ) return ;
92+
93+ if ( ! map ) map = new google . maps . Map ( document . getElementById ( 'map' ) , {
94+ zoom : 12 , center : { lat : 22.3 , lng : 114.1 } ,
95+ disableDefaultUI : true ,
96+ styles : [ { stylers :[ { invert_lightness :true } ] } ]
97+ } ) ;
98+
99+ drGo . setMap ( null ) ; drBack . setMap ( null ) ;
89100
90101 const goTime = new Date ( document . getElementById ( 'start-time' ) . value ) ;
91- const goSelected = Array . from ( document . querySelectorAll ( '.go-t.active' ) ) . map ( b =>
92- TUNNEL_DATA . find ( d => d . loc === b . getAttribute ( 'data-loc' ) )
93- ) ;
102+ const goSelected = Array . from ( document . querySelectorAll ( '.go-t.active' ) ) . map ( b => TUNNEL_DATA . find ( d => d . loc === b . getAttribute ( 'data-loc' ) ) ) ;
94103 const go = await getRouteData ( locs [ 0 ] , locs [ locs . length - 1 ] , goSelected , goTime ) ;
95104
96105 let totalKm = go . km , totalToll = go . toll , totalSec = go . sec ;
97106
107+ if ( go . raw ) { drGo . setMap ( map ) ; drGo . setDirections ( go . raw ) ; }
108+
98109 if ( returnMode ) {
99110 const backTime = new Date ( document . getElementById ( 'return-time' ) . value ) ;
100- const backSelected = Array . from ( document . querySelectorAll ( '.back-t.active' ) ) . map ( b =>
101- TUNNEL_DATA . find ( d => d . loc === b . getAttribute ( 'data-loc' ) )
102- ) ;
111+ const backSelected = Array . from ( document . querySelectorAll ( '.back-t.active' ) ) . map ( b => TUNNEL_DATA . find ( d => d . loc === b . getAttribute ( 'data-loc' ) ) ) ;
103112 const back = await getRouteData ( locs [ locs . length - 1 ] , locs [ 0 ] , backSelected , backTime ) ;
104- totalKm += back . km ;
105- totalToll += back . toll ;
106- totalSec += back . sec ;
113+ totalKm += back . km ; totalToll += back . toll ; totalSec += back . sec ;
114+ if ( back . raw ) { drBack . setMap ( map ) ; drBack . setDirections ( back . raw ) ; }
107115 }
108116
109- if ( go . raw ) {
110- document . getElementById ( 'map' ) . style . display = 'block' ;
111- if ( ! map ) map = new google . maps . Map ( document . getElementById ( 'map' ) , { zoom : 12 , center : { lat : 22.3 , lng : 114.1 } , disableDefaultUI : true , styles : [ { stylers :[ { invert_lightness :true } ] } ] } ) ;
112- drGo . setMap ( map ) ;
113- drGo . setDirections ( go . raw ) ;
114- }
115117 updateUI ( totalKm , totalToll , totalSec ) ;
116- btn . style . opacity = "1" ;
117118}
118119
119120async function getRouteData ( start , end , tunnels , time ) {
120121 return new Promise ( resolve => {
121122 let pts = tunnels . map ( t => ( { location : t . loc , stopover : true , lat : t . lat , toll : getToll ( t . loc , time ) } ) ) ;
122- const ntKeywords = [ 'sha tin' , 'tai po' , 'fanling' , 'yuen long' , 'tuen mun' , 'fo tan' , '沙田' , '火炭' , '大埔' ] ;
123- const isNorthbound = ntKeywords . some ( k => end . toLowerCase ( ) . includes ( k ) ) ;
124- if ( isNorthbound ) pts . sort ( ( a , b ) => a . lat - b . lat ) ;
125- else pts . sort ( ( a , b ) => b . lat - a . lat ) ;
126-
127- const tollSum = pts . reduce ( ( a , b ) => a + b . toll , 0 ) ;
128- const cleanWays = pts . map ( p => ( { location : p . location , stopover : true } ) ) ;
129-
130- ds . route ( { origin : start , destination : end , waypoints : cleanWays , travelMode : 'DRIVING' , optimizeWaypoints : false } , ( res , stat ) => {
123+ pts . sort ( ( a , b ) => end . includes ( 'Sha Tin' ) ? a . lat - b . lat : b . lat - a . lat ) ;
124+ ds . route ( { origin : start , destination : end , waypoints : pts . map ( p => ( { location :p . location , stopover :true } ) ) , travelMode : 'DRIVING' } , ( res , stat ) => {
131125 if ( stat === 'OK' ) {
132126 const km = res . routes [ 0 ] . legs . reduce ( ( a , b ) => a + b . distance . value , 0 ) / 1000 ;
133127 const sec = res . routes [ 0 ] . legs . reduce ( ( a , b ) => a + b . duration . value , 0 ) ;
134- resolve ( { km, toll : tollSum , sec, raw : res } ) ;
128+ resolve ( { km, toll : pts . reduce ( ( a , b ) => a + b . toll , 0 ) , sec, raw : res } ) ;
135129 } else resolve ( { km : 0 , toll : 0 , sec : 0 , raw : null } ) ;
136130 } ) ;
137131 } ) ;
@@ -140,29 +134,19 @@ async function getRouteData(start, end, tunnels, time) {
140134function updateUI ( km , toll , sec ) {
141135 const car = document . getElementById ( 'car-model' ) . value . split ( '|' ) ;
142136 const energy = km * parseFloat ( car [ 0 ] ) * parseFloat ( car [ 1 ] ) ;
143- const evTotal = energy + toll ;
144-
145137 document . getElementById ( 'km' ) . innerText = km . toFixed ( 1 ) + " km" ;
146138 document . getElementById ( 'duration' ) . innerText = Math . round ( sec / 60 ) + " min" ;
147139 document . getElementById ( 't-fee' ) . innerText = "$" + toll ;
148140 document . getElementById ( 'e-cost' ) . innerText = "$" + energy . toFixed ( 1 ) ;
149- document . getElementById ( 'total' ) . innerText = evTotal . toFixed ( 1 ) ;
150-
151- const fuelCostBase = km * 1.8 ;
152- const savedAmount = fuelCostBase - energy ;
153- const savingsEl = document . getElementById ( 'savings' ) ;
154- savingsEl . innerText = "$" + Math . max ( 0 , savedAmount ) . toFixed ( 1 ) ;
155-
156- savingsEl . style . color = "#FFD700" ;
157- setTimeout ( ( ) => { savingsEl . style . color = "#4CD964" ; } , 500 ) ;
141+ document . getElementById ( 'total' ) . innerText = ( energy + toll ) . toFixed ( 1 ) ;
142+ document . getElementById ( 'savings' ) . innerText = "$" + ( km * 1.8 - energy ) . toFixed ( 1 ) ;
158143}
159144
160145function addNode ( ) {
161- const container = document . getElementById ( 'nodes-container' ) ;
162146 const div = document . createElement ( 'div' ) ;
163147 div . className = 'input-group' ;
164148 div . innerHTML = `<input class="node-input" placeholder="中途站" autocomplete="off">` ;
165- container . appendChild ( div ) ;
149+ document . getElementById ( 'nodes- container' ) . appendChild ( div ) ;
166150 bindAutocomplete ( div . querySelector ( '.node-input' ) ) ;
167151}
168152
0 commit comments