PageSourceSearch

https://locategmfleetworktrucks.com/bundles/js/marketing?v=sjAkdu67hDZcqk3QYtuJuYHswbOBqN9MfwlIaoGKt4w1

js locategmfleetworktrucks.com collected 2026-10-08 00:00:35 UTC 420,354 bytes, 10,375 lines download raw bytes

1/* Minification failed. Returning unminified contents.
2(2499,27-28): run-time error JS1195: Expected expression: .
3(2499,75-76): run-time error JS1003: Expected ':': ,
4(2500,32-33): run-time error JS1009: Expected '}': :
5(2491,33): run-time error JS1004: Expected ';'
6(2500,32-33): run-time error JS1195: Expected expression: :
7(2501,27-28): run-time error JS1195: Expected expression: .
8(2501,79-80): run-time error JS1003: Expected ':': ,
9(2502,29): run-time error JS1004: Expected ';'
10(2502,29-30): run-time error JS1195: Expected expression: :
11(2502,37-38): run-time error JS1195: Expected expression: .
12(2502,78-79): run-time error JS1003: Expected ':': ,
13(2503,36): run-time error JS1004: Expected ';'
14(2503,36-37): run-time error JS1195: Expected expression: :
15(2504,27-28): run-time error JS1195: Expected expression: .
16(2504,80-81): run-time error JS1003: Expected ':': ,
17(2505,25): run-time error JS1004: Expected ';'
18(2505,25-26): run-time error JS1195: Expected expression: :
19(2505,33-34): run-time error JS1195: Expected expression: .
20(2505,48-49): run-time error JS1003: Expected ':': ,
21(2507,22): run-time error JS1004: Expected ';'
22(2507,22-23): run-time error JS1195: Expected expression: :
23(2508,13-14): run-time error JS1195: Expected expression: }
24(2508,14-15): run-time error JS1009: Expected '}': ;
25(3094,21-24): run-time error JS1009: Expected '}': ...
26(3096,17-18): run-time error JS1195: Expected expression: }
27(3100,13-26): run-time error JS1006: Expected ')': trackAscEvent
28(3101,9-10): run-time error JS1002: Syntax error: }
29(3101,10-11): run-time error JS1195: Expected expression: ,
30(3107,53-54): run-time error JS1010: Expected identifier: (
31(3152,17-20): run-time error JS1009: Expected '}': ...
32(3153,13-14): run-time error JS1195: Expected expression: }
33(3156,9-10): run-time error JS1002: Syntax error: }
34(3156,10-11): run-time error JS1195: Expected expression: ,
35(3162,48-49): run-time error JS1010: Expected identifier: (
36(3174,17-20): run-time error JS1009: Expected '}': ...
37(3175,13-14): run-time error JS1195: Expected expression: }
38(3200,9-10): run-time error JS1002: Syntax error: }
39(3200,10-11): run-time error JS1195: Expected expression: ,
40(3206,39-40): run-time error JS1010: Expected identifier: (
41(3276,10-11): run-time error JS1195: Expected expression: ,
42(3282,35-36): run-time error JS1010: Expected identifier: (
43(3349,26-27): run-time error JS1195: Expected expression: .
44(3357,30-31): run-time error JS1003: Expected ':': ,
45(3358,26-27): run-time error JS1009: Expected '}': :
46(3332,35): run-time error JS1004: Expected ';'
47(3358,26-27): run-time error JS1195: Expected expression: :
48(3363,13-14): run-time error JS1195: Expected expression: }
49(3419,9-10): run-time error JS1002: Syntax error: }
50(3419,10-11): run-time error JS1195: Expected expression: ,
51(3425,39-40): run-time error JS1010: Expected identifier: (
52(3462,17-20): run-time error JS1009: Expected '}': ...
53(3463,13-14): run-time error JS1195: Expected expression: }
54(3475,25-28): run-time error JS1009: Expected '}': ...
55(3475,25-28): run-time error JS1006: Expected ')': ...
56(3474,36): run-time error JS1004: Expected ';'
57(3476,30): run-time error JS1004: Expected ';'
58(3476,30-31): run-time error JS1195: Expected expression: :
59(3477,21-22): run-time error JS1195: Expected expression: }
60(3477,22-23): run-time error JS1195: Expected expression: )
61(3479,13-14): run-time error JS1002: Syntax error: }
62(3480,9-10): run-time error JS1002: Syntax error: }
63(3480,10-11): run-time error JS1195: Expected expression: ,
64(3482,34-35): run-time error JS1010: Expected identifier: (
65(3501,38-39): run-time error JS1195: Expected expression: .
66(3501,46-47): run-time error JS1195: Expected expression: .
67(3501,55-56): run-time error JS1003: Expected ':': ,
68(3507,17-20): run-time error JS1009: Expected '}': ...
69(3508,13-14): run-time error JS1195: Expected expression: }
70(3510,9-10): run-time error JS1002: Syntax error: }
71(3510,10-11): run-time error JS1195: Expected expression: ,
72(3516,46-47): run-time error JS1010: Expected identifier: (
73(3538,17-20): run-time error JS1009: Expected '}': ...
74(3539,13-14): run-time error JS1195: Expected expression: }
75(3550,37-38): run-time error JS1195: Expected expression: .
76(3550,42-43): run-time error JS1003: Expected ':': ;
77(3556,9-10): run-time error JS1002: Syntax error: }
78(3556,10-11): run-time error JS1195: Expected expression: ,
79(3562,41-42): run-time error JS1010: Expected identifier: (
80(3587,17-20): run-time error JS1009: Expected '}': ...
81(3588,13-14): run-time error JS1195: Expected expression: }
82(3631,9-10): run-time error JS1002: Syntax error: }
83(3631,10-11): run-time error JS1195: Expected expression: ,
84(3632,5-6): run-time error JS1002: Syntax error: }
85(3652,13-16): run-time error JS1009: Expected '}': ...
86(3654,9-10): run-time error JS1195: Expected expression: }
87(3658,5-6): run-time error JS1002: Syntax error: }
88(3668,28-29): run-time error JS1195: Expected expression: .
89(3668,35-36): run-time error JS1003: Expected ':': )
90(3669,13-18): run-time error JS1009: Expected '}': const
91(3669,13-18): run-time error JS1006: Expected ')': const
92(3699,5-6): run-time error JS1002: Syntax error: }
93(3847,1-2): run-time error JS1002: Syntax error: }
94(3847,2-3): run-time error JS1195: Expected expression: )
95(4556,22-23): run-time error JS1195: Expected expression: .
96(4558,35-36): run-time error JS1003: Expected ':': ;
97(9008,73-76): run-time error JS1009: Expected '}': ...
98(9008,73-76): run-time error JS1006: Expected ')': ...
99(9008,72): run-time error JS1004: Expected ';'
100(9008,97): run-time error JS1004: Expected ';'
101(9008,97-98): run-time error JS1195: Expected expression: :
102(9033,82-85): run-time error JS1009: Expected '}': ...
103(9033,82-85): run-time error JS1006: Expected ')': ...
104(9033,81): run-time error JS1004: Expected ';'
105(9033,100): run-time error JS1004: Expected ';'
106(9033,100-101): run-time error JS1195: Expected expression: :
107(9034,5-6): run-time error JS1002: Syntax error: }
108(9034,6-7): run-time error JS1195: Expected expression: )
109(9045,76-79): run-time error JS1009: Expected '}': ...
110(9045,76-79): run-time error JS1006: Expected ')': ...
111(9045,76): run-time error JS1004: Expected ';'
112(9045,94): run-time error JS1004: Expected ';'
113(9045,94-95): run-time error JS1195: Expected expression: :
114(9046,5-6): run-time error JS1002: Syntax error: }
115(9046,6-7): run-time error JS1195: Expected expression: )
116(9049,81-84): run-time error JS1009: Expected '}': ...
117(9049,81-84): run-time error JS1006: Expected ')': ...
118(9049,80): run-time error JS1004: Expected ';'
119(9049,99): run-time error JS1004: Expected ';'
120(9049,99-100): run-time error JS1195: Expected expression: :
121(9050,5-6): run-time error JS1002: Syntax error: }
122(9050,6-7): run-time error JS1195: Expected expression: )
123(9053,81-84): run-time error JS1009: Expected '}': ...
124(9053,81-84): run-time error JS1006: Expected ')': ...
125(9053,80): run-time error JS1004: Expected ';'
126(9053,99): run-time error JS1004: Expected ';'
127(9053,99-100): run-time error JS1195: Expected expression: :
128(9054,5-6): run-time error JS1002: Syntax error: }
129(9054,6-7): run-time error JS1195: Expected expression: )
130(9057,77-80): run-time error JS1009: Expected '}': ...
131(9057,77-80): run-time error JS1006: Expected ')': ...
132(9057,76): run-time error JS1004: Expected ';'
133(9057,95): run-time error JS1004: Expected ';'
134(9057,95-96): run-time error JS1195: Expected expression: :
135(9058,5-6): run-time error JS1002: Syntax error: }
136(9058,6-7): run-time error JS1195: Expected expression: )
137(9061,74-77): run-time error JS1009: Expected '}': ...
138(9061,74-77): run-time error JS1006: Expected ')': ...
139(9061,73): run-time error JS1004: Expected ';'
140(9061,92): run-time error JS1004: Expected ';'
141(9061,92-93): run-time error JS1195: Expected expression: :
142(9062,5-6): run-time error JS1002: Syntax error: }
143(9062,6-7): run-time error JS1195: Expected expression: )
144(9071,74-77): run-time error JS1009: Expected '}': ...
145(9071,74-77): run-time error JS1006: Expected ')': ...
146(9071,73): run-time error JS1004: Expected ';'
147(9071,92): run-time error JS1004: Expected ';'
148(9071,92-93): run-time error JS1195: Expected expression: :
149(9072,5-6): run-time error JS1002: Syntax error: }
150(9072,6-7): run-time error JS1195: Expected expression: )
151(9075,63-66): run-time error JS1009: Expected '}': ...
152(9075,63-66): run-time error JS1006: Expected ')': ...
153(9075,62): run-time error JS1004: Expected ';'
154(9075,81): run-time error JS1004: Expected ';'
155(9075,81-82): run-time error JS1195: Expected expression: :
156(9076,5-6): run-time error JS1002: Syntax error: }
157(9076,6-7): run-time error JS1195: Expected expression: )
158(9079,79-82): run-time error JS1009: Expected '}': ...
159(9079,79-82): run-time error JS1006: Expected ')': ...
160(9079,78): run-time error JS1004: Expected ';'
161(9079,97): run-time error JS1004: Expected ';'
162(9079,97-98): run-time error JS1195: Expected expression: :
163(9080,5-6): run-time error JS1002: Syntax error: }
164(9080,6-7): run-time error JS1195: Expected expression: )
165(9083,69-72): run-time error JS1009: Expected '}': ...
166(9083,69-72): run-time error JS1006: Expected ')': ...
167(9083,68): run-time error JS1004: Expected ';'
168(9083,87): run-time error JS1004: Expected ';'
169(9083,87-88): run-time error JS1195: Expected expression: :
170(9084,5-6): run-time error JS1002: Syntax error: }
171(9084,6-7): run-time error JS1195: Expected expression: )
172(9087,74-77): run-time error JS1009: Expected '}': ...
173(9087,74-77): run-time error JS1006: Expected ')': ...
174(9087,73): run-time error JS1004: Expected ';'
175(9087,92): run-time error JS1004: Expected ';'
176(9087,92-93): run-time error JS1195: Expected expression: :
177(9088,5-6): run-time error JS1002: Syntax error: }
178(9088,6-7): run-time error JS1195: Expected expression: )
179(9091,76-79): run-time error JS1009: Expected '}': ...
180(9091,76-79): run-time error JS1006: Expected ')': ...
181(9091,75): run-time error JS1004: Expected ';'
182(9091,94): run-time error JS1004: Expected ';'
183(9091,94-95): run-time error JS1195: Expected expression: :
184(9092,5-6): run-time error JS1002: Syntax error: }
185(9092,6-7): run-time error JS1195: Expected expression: )
186(9095,69-72): run-time error JS1009: Expected '}': ...
187(9095,69-72): run-time error JS1006: Expected ')': ...
188(9095,68): run-time error JS1004: Expected ';'
189(9095,87): run-time error JS1004: Expected ';'
190(9095,87-88): run-time error JS1195: Expected expression: :
191(9096,5-6): run-time error JS1002: Syntax error: }
192(9096,6-7): run-time error JS1195: Expected expression: )
193(9099,71-74): run-time error JS1009: Expected '}': ...
194(9099,71-74): run-time error JS1006: Expected ')': ...
195(9099,70): run-time error JS1004: Expected ';'
196(9099,89): run-time error JS1004: Expected ';'
197(9099,89-90): run-time error JS1195: Expected expression: :
198(9100,5-6): run-time error JS1002: Syntax error: }
199(9100,6-7): run-time error JS1195: Expected expression: )
200(9103,71-74): run-time error JS1009: Expected '}': ...
201(9103,71-74): run-time error JS1006: Expected ')': ...
202(9103,70): run-time error JS1004: Expected ';'
203(9103,89): run-time error JS1004: Expected ';'
204(9103,89-90): run-time error JS1195: Expected expression: :
205(9104,5-6): run-time error JS1002: Syntax error: }
206(9104,6-7): run-time error JS1195: Expected expression: )
207(9107,78-81): run-time error JS1009: Expected '}': ...
208(9107,78-81): run-time error JS1006: Expected ')': ...
209(9107,77): run-time error JS1004: Expected ';'
210(9107,96): run-time error JS1004: Expected ';'
211(9107,96-97): run-time error JS1195: Expected expression: :
212(9108,5-6): run-time error JS1002: Syntax error: }
213(9108,6-7): run-time error JS1195: Expected expression: )
214(9111,89-92): run-time error JS1009: Expected '}': ...
215(9111,89-92): run-time error JS1006: Expected ')': ...
216(9111,88): run-time error JS1004: Expected ';'
217(9111,107): run-time error JS1004: Expected ';'
218(9111,107-108): run-time error JS1195: Expected expression: :
219(9112,5-6): run-time error JS1002: Syntax error: }
220(9112,6-7): run-time error JS1195: Expected expression: )
221(9119,70-73): run-time error JS1009: Expected '}': ...
222(9119,70-73): run-time error JS1006: Expected ')': ...
223(9119,69): run-time error JS1004: Expected ';'
224(9119,88): run-time error JS1004: Expected ';'
225(9119,88-89): run-time error JS1195: Expected expression: :
226(9120,5-6): run-time error JS1002: Syntax error: }
227(9120,6-7): run-time error JS1195: Expected expression: )
228(9123,70-73): run-time error JS1009: Expected '}': ...
229(9123,70-73): run-time error JS1006: Expected ')': ...
230(9123,69): run-time error JS1004: Expected ';'
231(9123,88): run-time error JS1004: Expected ';'
232(9123,88-89): run-time error JS1195: Expected expression: :
233(9124,5-6): run-time error JS1002: Syntax error: }
234(9124,6-7): run-time error JS1195: Expected expression: )
235(9128,77-80): run-time error JS1009: Expected '}': ...
236(9128,77-80): run-time error JS1006: Expected ')': ...
237(9128,76): run-time error JS1004: Expected ';'
238(9128,95): run-time error JS1004: Expected ';'
239(9128,95-96): run-time error JS1195: Expected expression: :
240(9129,5-6): run-time error JS1002: Syntax error: }
241(9129,6-7): run-time error JS1195: Expected expression: )
242(9132,77-80): run-time error JS1009: Expected '}': ...
243(9132,77-80): run-time error JS1006: Expected ')': ...
244(9132,76): run-time error JS1004: Expected ';'
245(9132,95): run-time error JS1004: Expected ';'
246(9132,95-96): run-time error JS1195: Expected expression: :
247(9133,5-6): run-time error JS1002: Syntax error: }
248(9133,6-7): run-time error JS1195: Expected expression: )
249(9136,77-80): run-time error JS1009: Expected '}': ...
250(9136,77-80): run-time error JS1006: Expected ')': ...
251(9136,76): run-time error JS1004: Expected ';'
252(9136,95): run-time error JS1004: Expected ';'
253(9136,95-96): run-time error JS1195: Expected expression: :
254(9137,5-6): run-time error JS1002: Syntax error: }
255(9137,6-7): run-time error JS1195: Expected expression: )
256(9140,80-83): run-time error JS1009: Expected '}': ...
257(9140,80-83): run-time error JS1006: Expected ')': ...
258(9140,79): run-time error JS1004: Expected ';'
259(9140,98): run-time error JS1004: Expected ';'
260(9140,98-99): run-time error JS1195: Expected expression: :
261(9141,5-6): run-time error JS1002: Syntax error: }
262(9141,6-7): run-time error JS1195: Expected expression: )
263(9145,80-83): run-time error JS1009: Expected '}': ...
264(9145,80-83): run-time error JS1006: Expected ')': ...
265(9145,79): run-time error JS1004: Expected ';'
266(9145,98): run-time error JS1004: Expected ';'
267(9145,98-99): run-time error JS1195: Expected expression: :
268(9146,5-6): run-time error JS1002: Syntax error: }
269(9146,6-7): run-time error JS1195: Expected expression: )
270(9149,80-83): run-time error JS1009: Expected '}': ...
271(9149,80-83): run-time error JS1006: Expected ')': ...
272(9149,79): run-time error JS1004: Expected ';'
273(9149,98): run-time error JS1004: Expected ';'
274(9149,98-99): run-time error JS1195: Expected expression: :
275(9150,5-6): run-time error JS1002: Syntax error: }
276(9150,6-7): run-time error JS1195: Expected expression: )
277(9153,80-83): run-time error JS1009: Expected '}': ...
278(9153,80-83): run-time error JS1006: Expected ')': ...
279(9153,79): run-time error JS1004: Expected ';'
280(9153,98): run-time error JS1004: Expected ';'
281(9153,98-99): run-time error JS1195: Expected expression: :
282(9154,5-6): run-time error JS1002: Syntax error: }
283(9154,6-7): run-time error JS1195: Expected expression: )
284(9157,80-83): run-time error JS1009: Expected '}': ...
285(9157,80-83): run-time error JS1006: Expected ')': ...
286(9157,79): run-time error JS1004: Expected ';'
287(9157,98): run-time error JS1004: Expected ';'
288(9157,98-99): run-time error JS1195: Expected expression: :
289(9158,5-6): run-time error JS1002: Syntax error: }
290(9158,6-7): run-time error JS1195: Expected expression: )
291(9161,80-83): run-time error JS1009: Expected '}': ...
292(9161,80-83): run-time error JS1006: Expected ')': ...
293(9161,79): run-time error JS1004: Expected ';'
294(9161,98): run-time error JS1004: Expected ';'
295(9161,98-99): run-time error JS1195: Expected expression: :
296(9162,5-6): run-time error JS1002: Syntax error: }
297(9162,6-7): run-time error JS1195: Expected expression: )
298(9165,80-83): run-time error JS1009: Expected '}': ...
299(9165,80-83): run-time error JS1006: Expected ')': ...
300(9165,79): run-time error JS1004: Expected ';'
301(9165,98): run-time error JS1004: Expected ';'
302(9165,98-99): run-time error JS1195: Expected expression: :
303(9166,5-6): run-time error JS1002: Syntax error: }
304(9166,6-7): run-time error JS1195: Expected expression: )
305(9169,80-83): run-time error JS1009: Expected '}': ...
306(9169,80-83): run-time error JS1006: Expected ')': ...
307(9169,79): run-time error JS1004: Expected ';'
308(9169,98): run-time error JS1004: Expected ';'
309(9169,98-99): run-time error JS1195: Expected expression: :
310(9170,5-6): run-time error JS1002: Syntax error: }
311(9170,6-7): run-time error JS1195: Expected expression: )
312(9173,80-83): run-time error JS1009: Expected '}': ...
313(9173,80-83): run-time error JS1006: Expected ')': ...
314(9173,79): run-time error JS1004: Expected ';'
315(9173,98): run-time error JS1004: Expected ';'
316(9173,98-99): run-time error JS1195: Expected expression: :
317(9174,5-6): run-time error JS1002: Syntax error: }
318(9174,6-7): run-time error JS1195: Expected expression: )
319(9177,80-83): run-time error JS1009: Expected '}': ...
320(9177,80-83): run-time error JS1006: Expected ')': ...
321(9177,79): run-time error JS1004: Expected ';'
322(9177,98): run-time error JS1004: Expected ';'
323(9177,98-99): run-time error JS1195: Expected expression: :
324(9178,5-6): run-time error JS1002: Syntax error: }
325(9178,6-7): run-time error JS1195: Expected expression: )
326(9181,80-83): run-time error JS1009: Expected '}': ...
327(9181,80-83): run-time error JS1006: Expected ')': ...
328(9181,79): run-time error JS1004: Expected ';'
329(9181,98): run-time error JS1004: Expected ';'
330(9181,98-99): run-time error JS1195: Expected expression: :
331(9182,5-6): run-time error JS1002: Syntax error: }
332(9182,6-7): run-time error JS1195: Expected expression: )
333(9186,79-82): run-time error JS1009: Expected '}': ...
334(9186,79-82): run-time error JS1006: Expected ')': ...
335(9186,78): run-time error JS1004: Expected ';'
336(9186,97): run-time error JS1004: Expected ';'
337(9186,97-98): run-time error JS1195: Expected expression: :
338(9187,5-6): run-time error JS1002: Syntax error: }
339(9187,6-7): run-time error JS1195: Expected expression: )
340(9189,79-82): run-time error JS1009: Expected '}': ...
341(9189,79-82): run-time error JS1006: Expected ')': ...
342(9189,78): run-time error JS1004: Expected ';'
343(9189,97): run-time error JS1004: Expected ';'
344(9189,97-98): run-time error JS1195: Expected expression: :
345(9190,5-6): run-time error JS1002: Syntax error: }
346(9190,6-7): run-time error JS1195: Expected expression: )
347(9193,79-82): run-time error JS1009: Expected '}': ...
348(9193,79-82): run-time error JS1006: Expected ')': ...
349(9193,78): run-time error JS1004: Expected ';'
350(9193,97): run-time error JS1004: Expected ';'
351(9193,97-98): run-time error JS1195: Expected expression: :
352(9194,5-6): run-time error JS1002: Syntax error: }
353(9194,6-7): run-time error JS1195: Expected expression: )
354(9197,79-82): run-time error JS1009: Expected '}': ...
355(9197,79-82): run-time error JS1006: Expected ')': ...
356(9197,78): run-time error JS1004: Expected ';'
357(9197,97): run-time error JS1004: Expected ';'
358(9197,97-98): run-time error JS1195: Expected expression: :
359(9198,5-6): run-time error JS1002: Syntax error: }
360(9198,6-7): run-time error JS1195: Expected expression: )
361(9201,79-82): run-time error JS1009: Expected '}': ...
362(9201,79-82): run-time error JS1006: Expected ')': ...
363(9201,78): run-time error JS1004: Expected ';'
364(9201,97): run-time error JS1004: Expected ';'
365(9201,97-98): run-time error JS1195: Expected expression: :
366(9202,5-6): run-time error JS1002: Syntax error: }
367(9202,6-7): run-time error JS1195: Expected expression: )
368(9205,79-82): run-time error JS1009: Expected '}': ...
369(9205,79-82): run-time error JS1006: Expected ')': ...
370(9205,78): run-time error JS1004: Expected ';'
371(9205,97): run-time error JS1004: Expected ';'
372(9205,97-98): run-time error JS1195: Expected expression: :
373(9206,5-6): run-time error JS1002: Syntax error: }
374(9206,6-7): run-time error JS1195: Expected expression: )
375(9210,74-77): run-time error JS1009: Expected '}': ...
376(9210,74-77): run-time error JS1006: Expected ')': ...
377(9210,73): run-time error JS1004: Expected ';'
378(9210,92): run-time error JS1004: Expected ';'
379(9210,92-93): run-time error JS1195: Expected expression: :
380(9211,5-6): run-time error JS1002: Syntax error: }
381(9211,6-7): run-time error JS1195: Expected expression: )
382(9214,74-77): run-time error JS1009: Expected '}': ...
383(9214,74-77): run-time error JS1006: Expected ')': ...
384(9214,73): run-time error JS1004: Expected ';'
385(9214,92): run-time error JS1004: Expected ';'
386(9214,92-93): run-time error JS1195: Expected expression: :
387(9215,5-6): run-time error JS1002: Syntax error: }
388(9215,6-7): run-time error JS1195: Expected expression: )
389(9218,74-77): run-time error JS1009: Expected '}': ...
390(9218,74-77): run-time error JS1006: Expected ')': ...
391(9218,73): run-time error JS1004: Expected ';'
392(9218,92): run-time error JS1004: Expected ';'
393(9218,92-93): run-time error JS1195: Expected expression: :
394(9219,5-6): run-time error JS1002: Syntax error: }
395(9219,6-7): run-time error JS1195: Expected expression: )
396(9222,74-77): run-time error JS1009: Expected '}': ...
397(9222,74-77): run-time error JS1006: Expected ')': ...
398(9222,73): run-time error JS1004: Expected ';'
399(9222,92): run-time error JS1004: Expected ';'
400(9222,92-93): run-time error JS1195: Expected expression: :
401(9223,5-6): run-time error JS1002: Syntax error: }
402(9223,6-7): run-time error JS1195: Expected expression: )
403(9226,74-77): run-time error JS1009: Expected '}': ...
404(9226,74-77): run-time error JS1006: Expected ')': ...
405(9226,73): run-time error JS1004: Expected ';'
406(9226,92): run-time error JS1004: Expected ';'
407(9226,92-93): run-time error JS1195: Expected expression: :
408(9227,5-6): run-time error JS1002: Syntax error: }
409(9227,6-7): run-time error JS1195: Expected expression: )
410(9230,74-77): run-time error JS1009: Expected '}': ...
411(9230,74-77): run-time error JS1006: Expected ')': ...
412(9230,73): run-time error JS1004: Expected ';'
413(9230,92): run-time error JS1004: Expected ';'
414(9230,92-93): run-time error JS1195: Expected expression: :
415(9231,5-6): run-time error JS1002: Syntax error: }
416(9231,6-7): run-time error JS1195: Expected expression: )
417(9234,80-83): run-time error JS1009: Expected '}': ...
418(9234,80-83): run-time error JS1006: Expected ')': ...
419(9234,79): run-time error JS1004: Expected ';'
420(9235,5-6): run-time error JS1002: Syntax error: }
421(9235,6-7): run-time error JS1195: Expected expression: )
422(9238,81-84): run-time error JS1009: Expected '}': ...
423(9238,81-84): run-time error JS1006: Expected ')': ...
424(9238,80): run-time error JS1004: Expected ';'
425(9239,5-6): run-time error JS1002: Syntax error: }
426(9239,6-7): run-time error JS1195: Expected expression: )
427(9242,80-83): run-time error JS1009: Expected '}': ...
428(9242,80-83): run-time error JS1006: Expected ')': ...
429(9242,79): run-time error JS1004: Expected ';'
430(9243,5-6): run-time error JS1002: Syntax error: }
431(9243,6-7): run-time error JS1195: Expected expression: )
432(9246,84-87): run-time error JS1009: Expected '}': ...
433(9246,84-87): run-time error JS1006: Expected ')': ...
434(9246,83): run-time error JS1004: Expected ';'
435(9247,5-6): run-time error JS1002: Syntax error: }
436(9247,6-7): run-time error JS1195: Expected expression: )
437(9335,1-2): run-time error JS1002: Syntax error: }
438(9335,2-3): run-time error JS1195: Expected expression: )
439(3412,21-27): run-time error JS1018: 'return' statement outside of function: return
440(3408,21-27): run-time error JS1018: 'return' statement outside of function: return
441(3394,21-27): run-time error JS1018: 'return' statement outside of function: return
442(3387,21-27): run-time error JS1018: 'return' statement outside of function: return
443 */
444/* eslint-disable */
445// Array.find
446// https://tc39.github.io/ecma262/#sec-array.prototype.find
447if (!Array.prototype.find) {
448    Object.defineProperty(Array.prototype, "find", {
449        value: function (predicate) {
450            // 1. Let O be ? ToObject(this value).
451            if (this == null) {
452                throw new TypeError('"this" is null or not defined');
453            }
454
455            var o = Object(this);
456
457            // 2. Let len be ? ToLength(? Get(O, "length")).
458            var len = o.length >>> 0;
459
460            // 3. If IsCallable(predicate) is false, throw a TypeError exception.
461            if (typeof predicate !== "function") {
462                throw new TypeError("predicate must be a function");
463            }
464
465            // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
466            var thisArg = arguments[1];
467
468            // 5. Let k be 0.
469            var k = 0;
470
471            // 6. Repeat, while k < len
472            while (k < len) {
473                // a. Let Pk be ! ToString(k).
474                // b. Let kValue be ? Get(O, Pk).
475                // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
476                // d. If testResult is true, return kValue.
477                var kValue = o[k];
478                if (predicate.call(thisArg, kValue, k, o)) {
479                    return kValue;
480                }
481                // e. Increase k by 1.
482                k++;
483            }
484
485            // 7. Return undefined.
486            return undefined;
487        },
488        configurable: true,
489        writable: true
490    });
491}
492
493// Object.assign
494// added to decouple us from $.extend
495if (typeof Object.assign !== "function") {
496    // Must be writable: true, enumerable: false, configurable: true
497    Object.defineProperty(Object, "assign", {
498        value: function assign(target, varArgs) { // .length of function is 2
499            "use strict";
500            if (target == null) { // TypeError if undefined or null
501                throw new TypeError("Cannot convert undefined or null to object");
502            }
503
504            var to = Object(target);
505
506            for (var index = 1; index < arguments.length; index++) {
507                var nextSource = arguments[index];
508
509                if (nextSource != null) { // Skip over if undefined or null
510                    for (var nextKey in nextSource) {
511                        // Avoid bugs when hasOwnProperty is shadowed
512                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
513                            to[nextKey] = nextSource[nextKey];
514                        }
515                    }
516                }
517            }
518            return to;
519        },
520        writable: true,
521        configurable: true
522    });
523}
524
525
526
527// Object.entries
528// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill
529// dependent on Object.keys (we don't support IE9
530// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
531if (!Object.entries) {
532    Object.entries = function (obj) {
533        var ownProps = Object.keys(obj),
534            i = ownProps.length,
535            resArray = new Array(i); // preallocate the Array
536        while (i--)
537            resArray[i] = [ownProps[i], obj[ownProps[i]]];
538
539        return resArray;
540    };
541}
542
543if (!String.prototype.endsWith) {
544    String.prototype.endsWith = function (search, this_len) {
545        if (this_len === undefined || this_len > this.length) {
546            this_len = this.length;
547        }
548        return this.substring(this_len - search.length, this_len) === search;
549    };
550}
551
552if (!Array.prototype.includes) {
553    Array.prototype.includes = function (term) {
554        return (this.indexOf(term) !== -1);
555    };
556}
557
558
559/* eslint-enable */;
560(function (wts, modules) {
561    "use strict";
562
563    //
564    // utilities that help keep our code cleaner
565    //
566
567    var _ = (modules.utilities = {
568        FORBIDDEN_SYMBOLS: /[[\]*{}^<>!?\\;=|~]+/g,
569
570        /**
571         * Returns the input value — useful as a functional predicate
572         * @param {any} value the data passed to this fn, if any
573         * @returns {any} the argument value
574         */
575        identity: function (value) {
576            return value;
577        },
578
579        // returns true|false
580        isNullOrUndefined: function (input) {
581            return input === null || typeof input === "undefined";
582        },
583
584        // returns true|false
585        isNumber: function (input) {
586            return $.isNumeric(input);
587        },
588
589        // returns true|false
590        isInteger: function (input) {
591            return _.isNumber(input) && input === Math.round(input);
592        },
593
594        /**
595         * @description Binds all functions of an object (and it's prototype) to the context of the object.
596         * @param {object}
596 obj - an object containing function members
597         * @param {object} [scope=undefined] - scope in which the target members will be bound
598         * @param {string[]} [exceptions=undefined] - a string list of function names that should not be bound
599         * @returns {undefined}
600         */
601        bindAll: function (obj, scope, exceptions) {
602            var scopeParam = typeof scope === "undefined" ? obj : scope;
603            var exceptionsParam = exceptions || [];
604
605            var keys = [];
606            for (var key in obj) keys.push(key); // eslint-disable-line
607
608            keys.filter(function (k) {
609                return exceptionsParam.indexOf(k) < 0;
610            }).forEach(function (k) {
611                if (typeof obj[k] === "function")
612                    obj[k] = obj[k].bind(scopeParam);
613            });
614        },
615
616        /**
617         * @description Returns a function, that, as long as it continues to be invoked, will not be triggered.
618         * The function will be called after it stops being called for N milliseconds. If `immediate` is passed, trigger the
619         * function on the leading edge, instead of the trailing.
620         * @param {Function} func - function to debounce
621         * @param {number} wait time that must pass without an additional call, in milliseconds
622         * @param {boolean} [immediate=undefined] - immediate Whether the callback should be called on the leading edge of debouncing
623         * @see https://davidwalsh.name/javascript-debounce-function
624         * @returns {CallableFunction} returns the debounced function
625         */
626        debounce: function (func, wait, immediate) {
627            var timeout = null;
628            function run() {
629                var context = this;
630                var args = arguments;
631
632                var later = function () {
633                    timeout = null;
634                    if (!immediate) {
635                        func.apply(context, args);
636                    }
637                };
638
639                var callNow = immediate && !timeout;
640                clearTimeout(timeout);
641                timeout = setTimeout(later, wait);
642                if (callNow) {
643                    func.apply(context, args);
644                }
645            }
646
647            run.cancel = function () {
648                if (timeout !== null) {
649                    clearTimeout(timeout);
650                    timeout = null;
651                }
652            };
653
654            return run;
655        },
656
657        /**
658         * Scroll the viewport to a specific element
659         * @param {jQuery|HTMLElement} element The element to scroll to
660         * @param {int} duration A duration to scroll, in milliseconds
661         * @returns {undefined}
662         */
663        scrollToElement: function (element, duration) {
664            var $element = null;
665
666            if (!($element instanceof window.jQuery)) {
667                $element = $(element);
668            } else {
669                $element = element;
670            }
671
672            $("html, body").animate(
673                {
674                    scrollTop: $element.offset().top,
675                },
676                duration
677            );
678        },
679
680        /**
681         * Defers a function to be called at the "next tick".
682         * @param {Function} fn A function that can be called
683         * @returns {int} An ID for calling window.cancelTimeout
684         * @todo Update this to return a deferred function, instead of immediately deferring
685         */
686        defer: function (fn) {
687            return window.setTimeout(fn, 0);
688        },
689
690        /**
691         * Decodes HTML via jQuery
692         * @param {string} text Text to be decoded
693         * @returns {string} the decoded text
694         * @see http://stackoverflow.com/questions/1219860/html-encoding-in-javascript-jquery
695         */
696        encodeHTML: function (text) {
697            if (text === null) {
698                return null;
699            }
700
701            return text
702                .replace(/"/g, "&quot;")
703                .replace(/'/g, "&apos;")
704                .replace(/</g, "&lt;")
705                .replace(/>/g, "&gt;");
706        },
707
708        /**
709         * Decodes HTML via jQuery
710         * @param {string} text Text to be decoded
711         * @returns {string} the decoded text
712         */
713        decodeHTML: function (text) {
714            return $("<div />").html(text).text();
715        },
716
717        /**
718         * Escapes HTML via jQuery
719         * @param {string} text Text to be escaped
720         * @returns {string} Text that is escaped
721         */
722        escapeHTML: function (text) {
723            return $("<div/>").text(text).html();
724        },
725
726        /**
727         * Retrieves a truck's ID from the DOM, likely for use on VSP
728         * @param {HTMLElement} element An element representing the .truck component or one if its children
729         * @returns {string|null} The truck ID, or null
730         */
731        getTruckID: function (element) {
732            var $truck = $(element).closest(".truck");
733            return $truck.data("truck-id") || null;
734        },
735
736        /**
737         * Retrieves a query string parameter by name
738         * @todo This methodology doesn't properly accomodate for multiple items per name
739         * @param {string} name The key to look for in the search
740         * @returns {string|null} The query parameter, as a string or null
741         */
742        getQueryParameter: function (name) {
743            var search = window.location.search.substring(1);
744            if (search.length === 0) {
745                return null;
746            }
747
748            var parts = search.split("&");
749            var queryStringParams = parts.reduce(function (map, part, i) {
750                var keyValue = part[i].split("=");
751                var key = keyValue[0];
752                var value = keyValue[1];
753
754                map[key] = value;
755
756                return map;
757            }, {});
758
759            var hasValue = typeof queryStringParams[name] === undefined;
760            return hasValue ? null : queryStringParams[name];
761        },
762
763        /**
764         * Convenience method for building a selector string for matching a name attribute
765         * General usage is to then pass the result to a jQuery selector
766         * @remarks This might be better as a jQuery plugin
767         * @param {string} [parent=undefined] A selector string representing a parent of a target element `.parent [name="name"]`
768         * @param {string} [selector=undefined] A selector string for prepending to the name selector `.parent .selector[name="name"]`
769         * @param {string} name The name of the element
770         * @returns {string} The selector string
771         */
772        nameSelector: function (/* [parent, selector,] name */) {
773            var parent = "";
774            var selector = "";
775            var name = "";
776
777            switch (arguments.length) {
778                case 1:
779                    name = arguments[0];
780                    break;
781
782                case 2:
783                    selector = arguments[0];
784                    name = arguments[1];
785                    break;
786
787                case 3:
788                    parent = arguments[0];
789                    selector = arguments[1];
790                    name = arguments[2];
791                    break;
792            }
793
794            return parent + " " + selector + "[name='" + name + "']";
795        },
796
797        /**
798         *  Retrieves the truck title text and photo from a vehicle listing
799         *  Previously named "GetTruckListingTitleAndPhoto" in _InventoryLayout.cshtml
800         *  @param {jQuery} $listing The truck listing in which a truck title and photo are being retrieved from
801         *  @returns {any}
801 An object containing "title" and "image" fields
802         *                 representing title text and img src of the vehicle
803         */
804        getTruckTitleAndPhoto: function ($listing) {
805            var $image = $listing.find(".truck-image__image");
806
807            // CASE: because of lazy-loading library which uses data-src, the attribute "src" may
808            // not be set on page load, so we fallback to the src property (as assigned by lazy-load)
809            // or the data-src attribute, which lazy-load would load.
810            var imageSrc =
811                $image.attr("src") ||
812                $image.prop("src") ||
813                $image.attr("data-src");
814
815            return {
816                title: $listing.find(".truck__link-text").text().trim(),
817                image: imageSrc,
818                truckID: $listing.data("truckId"),
819            };
820        },
821
822        /**
823         *  Parse a phone string for display: (530) 123-4567, Ext. 890
824         *  (this frontend method mirrors the logic of the WTSUtils.StringUtils.ParseForPhoneDisplay backend method)
825         *  @param {string} phoneNo The phone no.
826         *  @returns {string} A formatted phone number
827         */
828        parseForPhoneDisplay: function (phoneNo) {
829            if (!phoneNo) {
830                return "";
831            }
832
833            let parts = this.parsePhoneString(phoneNo);
834            let telPhoneCode = "";
835
836            if (parts.isValid) {
837                if (parts.extension) {
838                    telPhoneCode = `(${parts.area}) ${parts.prefix}-${parts.suffix}, Ext. ${parts.extension}`;
839                } else {
840                    telPhoneCode = `(${parts.area}) ${parts.prefix}-${parts.suffix}`;
841                }
842            }
843
844            return telPhoneCode;
845        },
846
847        /**
848         *  Parse the phone string into its parts.
849         *  @param {string} phoneNo The phone no.
850         *  @returns {PhoneParts} A JavaScript object containing the parsed area code,
851         *  prefix, suffix, and extension of the provided phone number
852         */
853        parsePhoneString: function (phoneNo) {
854            /**
855             *  @class PhoneParts
856             *  @type {Object}
857             *  @property {boolean} isValid
858             *  @property {string} area The phone number area code.
859             *  @property {string} prefix The phone number prefix.
860             *  @property {string} suffix The phone number suffix.
861             *  @property {string} extension The phone number extension.
862             */
863            class PhoneParts {
864                constructor(isValid, area, prefix, suffix, extension) {
865                    this.isValid = isValid;
866                    this.area = area;
867                    this.prefix = prefix;
868                    this.suffix = suffix;
869                    this.extension = extension;
870                }
871            }
872
873            const phoneDigitsRegex = new RegExp("[^0-9\\x]");
874
875            if (!phoneNo) {
876                return new PhoneParts(false, "", "", "", "");
877            }
878
879            // Remove everything but numerals and the x
880            let cleanString = phoneNo.replace(phoneDigitsRegex, "");
881
882            // If phoneNo started with 1 or 0 then 'x' will end up in the wrong position,
883            // so we'll just remove it and parse the remaining digits.
884            // 7-digit numbers are a thing of the past. Phone field validation rejects anything
885            // less than 10 digits, so the position of the 'x' is not important and could be problematic
886            cleanString = cleanString.replace("x", "");
887
888            // There are no U.S. area codes (or prefixes) that start with 1 (or 0), so let's get rid of those leading digits.
889            if (cleanString.startsWith("0")) {
890                // We really can't guess what the number should be, so just bail out
891                return new PhoneParts(false, "", "", "", "");
892            }
893
894            while (cleanString.startsWith("1")) {
895                // Here a leading 1 is probably just a bad habit taught by thousands of companies that think their users
896                // don't know how to use a phone ... in an era when most calls are mobile and don't need to start with 1.
897                cleanString = cleanString.substring(1);
898            }
899
900            // Now let's get the extension
901            let extension = null;
902
903            if (cleanString.length > 10) {
904                // get the extension
905                extension = cleanString.substring(10);
906
907                // remove the extension from original string
908                cleanString = cleanString.substring(0, 10);
909            }
910
911            if (cleanString.length == 10) {
912                let area = cleanString.substring(0, 3);
913                let prefix = cleanString.substring(3, 6);
914                let suffix = cleanString.substring(6, 10);
915
916                return new PhoneParts(true, area, prefix, suffix, extension);
917            }
918
919            // Anything else is not valid
920            return new PhoneParts(false, "", "", "", "");
921        },
922
923        /**
924         * Detect if a containing HTMLElement has content overflow on either axis
925         * @param {jQuery|HTMLElement} element The element whose content overflow status you want to check
926         * @returns {boolean}
927         */
928        hasContentOverflow: function (element) {
929            var $element = null;
930
931            if (!($element instanceof window.jQuery)) {
932                $element = $(element);
933            } else {
934                $element = element;
935            }
936
937            return (
938                $element.prop("scrollWidth") > $element.width() ||
939                $element.prop("scrollHeight") > $element.height()
940            );
941        },
942
943        hasForbiddenSymbols: function (str) {
944            return str.match(_.FORBIDDEN_SYMBOLS) !== null;
945        },
946
947        /**
948         * Helper function used to get the text of the innermost html element. Helpful when the text of "what" was clicked is needed.
949         * @param {any} $element the element to get the innermost text from.
950         * @returns {string} The inner text of the element.
951         */
952        getInnerText: function ($el) {
953            var $innermostChild = $el;
954            var $next = $innermostChild;
955
956            do {
957                $innermostChild = $next;
958                $next = $next.children();
959            } while ($next.length);
960
961            return $innermostChild.html().trim();
962        },
963
964        /**
965         * Full visible text of an element including nested children (e.g. anchor with styled spans).
966         * @param {any} $el the element to get aggregate text from.
967         * @returns {string} The trimmed text content of the element.
968         */
969        getAggregateText: function ($el) {
970            return ($el.text() || "").trim();
971        },
972
973        /**
974         * Helper function used to get the appropriate carousel description
975         * @param {any} $el The item within the carousel to resolve a desciption for.
976         * @returns Either "similar-vehicle-carousel" OR "recently-viewed-carousel", depending on which carousel was resolved from $el.
977         */
978        getCarouselDescription: function ($el) {
979            var $carousel = $el.parents(
980                ".wts-vehicle-carousel--similar-vehicles-carousel"
981            );
982
983            if ($carousel.length) {
984                return "similar-vehicle-carousel";
985            } else {
986                var $carousel = $el.parents(
987                    ".wts-vehicle-carousel--recently-viewed-carousel"
988                );
989                if (!$carousel.length) {
990                    // parent carousel not found so return nothing
991                    return;
992                }
993
994                return "recently-viewed-carousel";
995            }
996        },
997
998        /**
999         * Detects the device type by observing different window.matchMedia queries to test the screen's width.
1000         * Uses responsive breakpoints: 90rem (pc/desktop), 40rem (tablet), below that is mobile.
1001         * @returns {"pc"|"tablet"|"mobile"} The device type based on screen width.
1002         */
1003        detectDeviceType: function () {
1004            if (window.matchMedia("(min-width: 90rem)").matches) {
1005                return "pc";
1006            } else if (window.matchMedia("(min-width: 40rem)").matches) {
1007                return "tablet";
1008            } else {
1009                return "mobile";
1010            }
1011        },
1012
1013        /**
1014         * Normalizes dial strings to E.164 for ASC comm_phone_number (US-centric +1XXXXXXXXXX).
1015         * @param {string} input tel href fragment, display text, or digits
1016         * @returns {string} E.164 or empty if not enough digits
1017         */
1018        phoneToE164: function (input) {
1019            if (input == null || input === "") {
1020                return "";
1021            }
1022            var digits = String(input)
1023                .replace(/^tel:/i, "")
1024                .replace(/\D/g, "");
1025            if (digits.length === 10) {
1026                return "+1" + digits;
1027            }
1028            if (digits.length === 11 && digits.charAt(0) === "1") {
1029                return "+" + digits;
1030            }
1031            if (digits.length >= 10) {
1032                return "+" + digits;
1033            }
1034            return "";
1035        },
1036    });
1037})(window.wts, window.wts.modules);
1038;
1039; (function (wts, modules) {
1040
1041    // this is the correct mime type we expect for these
1042    var utilities = modules.utilities;
1043    var mimeType = "application/json";
1044    var cache = Object.create(null);
1045
1046    /**
1047     * Finds and parses the JSON script tag associated to the selector parameter, or returns null
1048     * @param {string} selector The DOMString corrosponding to the id of the <script> tag to find
1049     * @throws {DOMException} Upon an invalid selector
1050     * @throws {Error} Upon reading a script with an incorrect mime-type — should be "application/json"
1051     * @returns {any} The result of parsing the script tag
1052     */
1053    modules.getClientData = function (selector) {
1054
1055        // repeated calls don't reparse the JSON
1056        if (!utilities.isNullOrUndefined(cache[selector])) {
1057            return cache[selector];
1058        }
1059
1060        var scriptEl = document.querySelector(selector);
1061
1062        // enforce us to not incorrectly attaching running-code
1063        if (scriptEl !== null && scriptEl.type.toLowerCase() !== mimeType) {
1064            throw new Error("Script corresponding to '" + selector + "' is not of type '" + mimeType + "'");
1065        }
1066
1067        if (scriptEl === null) {
1068            return null;
1069        }
1070
1071        var data = null;
1072
1073        try {
1074            data = JSON.parse(scriptEl.innerText);
1075        }
1076        catch (ex) {
1077            console.error(ex);
1078        }
1079
1080        cache[selector] = data;
1081
1082        return data;
1083    };
1084
1085
1086})(window.wts, window.wts.modules);;
1087/**
1088 * @module EventEmitter
1089 */
1090; (function (wts, modules) {
1091    "use strict";
1092
1093    var WILDCARD_CHAR = "*";
1094
1095    /** 
1096     * @description A custom, cross-browser compliant event-emitter. Useful for implementing modules with observer-like
1097     * patterns (listeners). Not meant to be used for DOM-related events, meant for higher-level events.
1098     * @constructor
1099     * @type {IEventEmitter}
1100     * @example 
1101     * var events = new EventEmitter();
1102     * events.on("module:initialized", function(event){ console.log(event) });
1103     * // then, somewhere else in the code...
1104     * events.trigger("module:initialized", { data });
1105     * events.off("module:initialized") // clear all listeners
1106     */
1107    function EventEmitter() {
1108
1109        /**
1110         * @this {IEventEmitter}
1111         */
1112        this._listeners = Object.create(null);
1113        ;[
1114            "addEventListener",
1115            "removeEventListener",
1116            "dispatchEvent",
1117            "on",
1118            "off",
1119            "trigger",
1120            "clear"
1121        ].forEach(function (fn) {
1122            this[fn] = this[fn].bind(this);
1123        }, this);
1124
1125    }
1126
1127    EventEmitter.prototype = {
1128
1129        /**
1130         * @description Adds a new listener that will be be called when parameter event <type> is dispatched
1131         * @param {string} type The name of an event to listen for
1132         * @param {EventListenerCallback} callback The function that will be called when the event is fired
1133         * @param {object|undefined} [context=undefined] The context that the function will be called with—in effect: `callback.call(context);`
1134         * @throws {Error}
1135         * @returns {Function} The function for unlistening to this event listener
1136         */
1137        addEventListener: function (type, callback, context) {
1138            if (typeof type !== "string") {
1139                throw new Error("EventEmitter.addEventListener: argument 0 must be a string.");
1140            }
1141
1142            if (typeof callback === "undefined") {
1143                throw new Error("EventEmitter.addEventListener: argument 1 must be a function.");
1144            }
1145
1146            var that = this;
1147            this._listeners[type] = this._listeners[type] || [];
1148            /**
1149             * @var listener
1150             * @type {IEventListener}
1151             */
1152            var listener = {
1153                type: type,
1154                callback: callback,
1155                context: context || null,
1156                cancel: function () {
1157                    that.removeEventListener(type, callback);
1158                }
1159            };
1160
1161            this._listeners[type].push(listener);
1162
1163            return listener.cancel;
1164        },
1165
1166        /**
1167         * @description Removes a previously-defined listener
1168         * @param {string} type
1169         * @param {EventListenerCallback} callback
1170         * @returns {IEventEmitter}
1171         */
1172        removeEventListener: function (type, callback) {
1173
1174            if (typeof type !== "string") {
1175                throw new Error("EventEmitter.removeEventListener: argument 0 must be a string.");
1176            }
1177
1178            // if callback is not provided, clear all listeners for that event
1179            if (typeof callback === "undefined") {
1180                this._listeners[type] = [];
1181            }
1182            else {
1183
1184                var eventList = (this._listeners[type] || []);
1185                // remove the event
1186                this._listeners[type] = eventList.filter(function (listener, i) {
1187                    return (listener.callback !== callback);
1188                });
1189            }
1190            return this;
1191        },
1192
1193        /**
1194         * @description Clears ALL event listeners
1195         */
1196        clear: function () {
1197            this._listeners = Object.create(null);
1198        },
1199
1200        /**
1201         * @description Fires an event with the parameter type and data.
1202         * @param {string} type The event name to dispatch
1203         * @param {EventListenerData} [data=null] - Any additional data to be passed to the event callback
1204         * @returns {IEventListener[]} - An array of all the listener callbacks that were fired
1205         */
1206        dispatchEvent: function (type, data) {
1207
1208            var timestamp = Date.now();
1209            var listeners = (this._listeners[type] || []);
1210
1211            var wildcards = this._listeners[WILDCARD_CHAR] || [];
1212
1213            return listeners.concat(wildcards).map(function (event) {
1214                var eventDetails = {
1215                    type: type,
1216                    context: event.context,
1217                    data: typeof data === "undefined" ? null : data,
1218                    timestamp: timestamp
1219                };
1220
1221                return event.callback.call(event.context, eventDetails);
1222            });
1223        },
1224
1225        /**
1226         * @alias addEventListener
1227         * @param {[string, EventListenerCallback]|[string, EventListenerCallback, EventListenerContext]} arguments
1228         * @returns {IEventEmitter}
1229         */
1230        on: function () {
1231            return this.addEventListener.apply(this, arguments);
1232        },
1233
1234        /**
1235         * @alias removeEventListener
1236         * @param {[string, EventListenerCallback]} arguments
1237         * @returns {IEventEmitter}
1238         */
1239        off: function () {
1240            return this.removeEventListener.apply(this, arguments);
1241        },
1242
1243        /**
1244         * @alias dispatchEvent
1245         * @param {[string]|[string, EventListenerData]} arguments
1246         * @returns {IEventListener[]} list of callbacks triggered
1247         */
1248        trigger: function () {
1249            return this.dispatchEvent.apply(this, arguments);
1250        },
1251
1252        /**
1253         * Add a listener that will only fire once
1254         * @param {string} type The name of an event to listen for
1255         * @param {EventListenerCallback} callback The function that will be called when the event is fired
1256         * @param {EventListenerContext} [context=null] The context that the function will be called with—in effect: `callback.call(context);`
1257         * @returns {Function} A function to cancel listening to the event
1258         */
1259        once: function (type, callback, context) {
1260
1261            var removeCallback = this.on(type, function () {
1262                callback.apply(context, arguments);
1263                removeCallback();
1264            }, context);
1265
1266            return removeCallback;
1267        }
1268    };
1269
1270
1271    modules.EventEmitter = EventEmitter;
1272
1273})(window.wts, window.wts.modules);
1274;
1275
1276window.wts.UI_EVENTS = {
1277
1278    /**
1279     * When a user views a page — initial page views can be handled by analytics libraries
1280     * This event exists for pages loaded after the initial page view—such as truck search's search results.
1281     */
1282    PAGE_VIEW: "PAGE_VIEW",
1283
1284    /**
1285     * Fired from the document ready function of a page.  Intended to be fired when a page has loaded.
1286     */
1287    PAGE_LOAD: "PAGE_LOAD",
1288
1289    /**
1290     * Fired from various places within the UI when the user interacts with something on the page that triggers navigation or progression
1291     * to a new state.
1292     */
1293    INTERACTION_CLICK: "INTERACTION_CLICK",
1294
1295    /**
1296     * Fired when the "See All Photos" link is clicked.
1297     */
1298    SEE_MORE_INTERACTION_CLICK: "SEE_MORE_INTERACTION_CLICK",
1299
1300    /**
1301     * Handles interaction click events fired from controls targeted in the _Layout.cshtml file.
1302     */
1303    GENERAL_LAYOUT_INTERACTION_CLICK: "GENERAL_LAYOUT_INTERACTION_CLICK",
1304
1305    /**
1306     * Fired when the vocational carousel changes pages.
1307     */
1308    VOCATIONAL_CAROUSEL_PAGE_CHANGE: "VOCATIONAL_CAROUSEL_PAGE_CHANGE",
1309
1310    /**
1311     * Fired when either the items per page or sort by from the top/bottom navs changes.
1312     */
1313    PAGINATION_CONTROL_CHANGE: "PAGINATION_CONTROL_CHANGE",
1314
1315    /**
1316     * Fired from various places within the UI when the user interacts with a form for the first time (presently only I'm Interested).
1317     */
1318    FORM_START: "FORM_START",
1319
1320    /**
1321     * Fired when a form's contents change.
1322     */
1323    FORM_ENGAGE: "FORM_ENGAGE",
1324
1325    /**
1326     * Clicking I'm interested generally should fire this
1327     */
1328    OPEN_VEHICLE_INQUIRY_FORM: "OPEN_VEHICLE_INQUIRY_FORM",
1329
1330    /**
1331     * Submitting most "lead"-generating forms
1332     */
1333    SUBMIT_VEHICLE_INQUIRY_FORM: "SUBMIT_VEHICLE_INQUIRY_FORM",
1334
1335    /**
1336     * An error occurred submitting most "lead"-generating forms
1337     */
1338    SUBMIT_VEHICLE_INQUIRY_FORM_ERROR: "SUBMIT_VEHICLE_INQUIRY_FORM_ERROR",
1339
1340    /**
1341     * Submitting the load garage form
1342     **/
1343    SUBMIT_LOAD_GARAGE_FORM: "SUBMIT_LOAD_GARAGE_FORM",
1344
1345    /**
1346     * Submitting the custom form
1347     **/
1348    SUBMIT_CUSTOM_FORM: "SUBMIT_CUSTOM_FORM",
1349
1350    /** 
1351     * The user has waited for the end of a form submission
1352     */
1353    WAIT_FOR_FORM_CONFIRMATION: "WAIT_FOR_FORM_CONFIRMATION",
1354
1355    /**
1356     * Clicking the support link
1357     */
1358    CLICK_SUPPORT_LINK: "CLICK_SUPPORT_LINK",
1359
1360    /**
1361     * Clicking call us now from the I'm Interested modal
1362     */
1363    CALL_US_NOW_IM_INTERESTED: "CALL_US_NOW_IM_INTERESTED",
1364
1365    /**
1366     * Clicking the 'Request a Callback' button in the Call Us modal
1367     */
1368    CALLBACK_REQUEST_CLICK: "CALLBACK_REQUEST_CLICK",
1369
1370    /**
1371     * Submitting the Support request form
1372     */
1373    SUBMIT_SUPPORT_FORM: "SUBMIT_SUPPORT_FORM",
1374
1375    /**
1376     * A Ford Direct Milestone 4 overlay opened. Data: { overlayKey, inventoryID }.
1377     * overlayKey must exist in the OVERLAY_CATALOG in ford-direct-analytics.js.
1378     */
1379    FORD_OVERLAY_OPEN: "FORD_OVERLAY_OPEN",
1380
1381    /**
1382     * A Ford Direct Milestone 4 overlay closed. Data: { overlayKey }.
1383     * Keyed so that closing an unrelated modal cannot discard an overlay still open beneath it.
1384     */
1385    FORD_OVERLAY_CLOSE: "FORD_OVERLAY_CLOSE",
1386
1387    /**
1388     * Interactions that open the photo gallery
1389     */
1390    PHOTO_GALLERY_OPEN: "PHOTO_GALLERY_OPEN",
1391
1392    /**
1393     * Interactions that close the photo gallery
1394     */
1395    PHOTO_GALLERY_CLOSE: "PHOTO_GALLERY_CLOSE",
1396
1397    /**
1398     * Within the Photo Gallery, interactions that view the next photo
1399     */
1400    PHOTO_GALLERY_NEXT_PHOTO: "PHOTO_GALLERY_NEXT_PHOTO",
1401
1402    /**
1403     * Within the Photo Gallery, interactions that view the previous photo
1404     */
1405    PHOTO_GALLERY_PREVIOUS_PHOTO: "PHOTO_GALLERY_PREVIOUS_PHOTO",
1406
1407    /**
1408     * Within the Photo Gallery, interactions that view the next page of thumbnails
1409     */
1410    PHOTO_GALLERY_NEXT_PAGE: "PHOTO_GALLERY_NEXT_PAGE",
1411
1412    /**
1413    * Within the Photo Gallery, interactions that view the previous page of thumbnails
1414    */
1415    PHOTO_GALLERY_PREVIOUS_PAGE: "PHOTO_GALLERY_PREVIOUS_PAGE",
1416
1417    /**
1418     * Within the Photo Gallery, interactions that view the next photo
1419     */
1420    FEATURED_PHOTO_GALLERY_NEXT_PHOTO: "FEATURED_PHOTO_GALLERY_NEXT_PHOTO",
1421
1422    /**
1423     * Within the Photo Gallery, interactions that view the previous photo
1424     */
1425    FEATURED_PHOTO_GALLERY_PREVIOUS_PHOTO: "FEATURED_PHOTO_GALLERY_PREVIOUS_PHOTO",
1426
1427    /**
1428     * On the VDP featured gallery, clicking the large active photo to open the lightbox
1429     */
1430    FEATURED_PHOTO_GALLERY_VIEW_PHOTO: "FEATURED_PHOTO_GALLERY_VIEW_PHOTO",
1431
1432    /**
1433     * Within the Photo Gallery, interactions that view the next page of thumbnails
1434     */
1435    FEATURED_PHOTO_GALLERY_NEXT_PAGE: "FEATURED_PHOTO_GALLERY_NEXT_PAGE",
1436
1437    /**
1438    * Within the Photo Gallery, interactions that view the previous page of thumbnails
1439    */
1440    FEATURED_PHOTO_GALLERY_PREVIOUS_PAGE: "FEATURED_PHOTO_GALLERY_PREVIOUS_PAGE",
1441
1442    /**
1443     * Within the Photo Gallery, clicking a thumbnail
1444     */
1445    PHOTO_GALLERY_CLICK_THUMBNAIL: "PHOTO_GALLERY_CLICK_THUMBNAIL",
1446
1447    /**
1448     * Within the Photo Gallery, clicking the 'open in new tab' link
1449     */
1450    PHOTO_GALLERY_VIEW_PHOTO_NEW_TAB: "PHOTO_GALLERY_VIEW_PHOTO_NEW_TAB",
1451
1452    /**
1453     * Within the VDP, clicking a thumbnail related to the featured photo
1454     */
1455    CLICK_FEATURED_PHOTO_THUMB: "CLICK_FEATURED_PHOTO_THUMB",
1456
1457    /**
1458     * When clicking a link to view a vehicle's condition
1459     */
1460    CLICK_CONDITION_LINK: "CLICK_CONDITION_LINK",
1461
1462    /**
1463     * Clicking an outbound link related to a vehicle
1464     */
1465    CLICK_OUTBOUND_LINK: "CLICK_OUTBOUND_LINK",
1466
1467    /**
1468     * Clicking a vehicle's window sticker link
1469     */
1470    CLICK_WINDOWSTICKER_LINK: "CLICK_WINDOWSTICKER_LINK",
1471
1472    /**
1473     * Clicking a vehicle's body link to a datasheet
1474     */
1475    CLICK_BODY_DATASHEET_LINK: "CLICK_BODY_DATASHEET_LINK",
1476
1477    /**
1478     * Clicking a vehicle's chassis link to a datasheet
1479     */
1480    CLICK_CHASSIS_DATASHEET_LINK: "CLICK_CHASSIS_DATASHEET_LINK",
1481
1482    /**
1483     * Clicking a vehicle's auction link
1484     */
1485    CLICK_AUCTION_LINK: "CLICK_AUCTION_LINK",
1486
1487    /**
1488     * Clicking a VSP or VAST vehicle listing photo
1489     */
1490    CLICK_VEHICLE_LISTING_PHOTO: "CLICK_VEHICLE_LISTING_PHOTO",
1491
1492    /**
1493     * Clicking a VSP or VAST vehicle listing title
1494     */
1495    CLICK_VEHICLE_LISTING_TITLE: "CLICK_VEHICLE_LISTING_TITLE",
1496
1497    /**
1498     * Clicking a VSP or VAST vehicle listing "see more details" link
1499     */
1500    CLICK_VEHICLE_LISTING_SEE_DETAILS: "CLICK_VEHICLE_LISTING_SEE_DETAILS",
1501
1502    /**
1503     * Clicking a CTA to request an upfit
1504     */
1505    CLICK_REQUEST_UPFIT: "CLICK_REQUEST_UPFIT",
1506
1507    /**
1508     * Clicking a "more info" button related to an upfit package
1509     */
1510    CLICK_REQUEST_UPFIT_INFO: "CLICK_REQUEST_UPFIT_INFO",
1511
1512    /**
1513     * Clicking a spec sheet related to an upfit package
1514     */
1515    CLICK_UPFIT_SPECIFICATION_SHEET: "CLICK_UPFIT_SPECIFICATION_SHEET",
1516
1517     /**
1518     * Clicking on the view dealership website link on convoy
1519     */
1520    CLICK_VDP_COMVOY_WEBSITE_LINK: "CLICK_VDP_COMVOY_WEBSITE_LINK",
1521
1522    /**
1523     * Choosing a vocation related to van builder or upfit requests
1524     */
1525    SELECT_UPFIT_VOCATION: "SELECT_UPFIT_VOCATION",
1526
1527    /**
1528     * Selecting an upfit package
1529     */
1530    SELECT_UPFIT_PACKAGE: "SELECT_UPFIT_PACKAGE",
1531
1532    /**
1533     * Exiting a request upfit form or clicking to return to other packages
1534     */
1535    CLICK_REMOVE_SELECTED_PACKAGE: "CLICK_REMOVE_SELECTED_PACKAGE",
1536
1537    /**
1538     * Clicking to submit a Request Upfit form
1539     */
1540    SUBMIT_REQUEST_UPFIT_FORM: "SUBMIT_REQUEST_UPFIT_FORM",
1541
1542    /**
1543     * Adding a vocation to the van builder form
1544     */
1545    SELECT_VAN_BUILDER_VOCATION: "SELECT_VAN_BUILDER_VOCATION",
1546
1547    /**
1548     * Dealer Locator Search tracking
1549     */
1550    SELECT_DEALER_LOCATOR_SEARCH: "SELECT_DEALER_LOCATOR_SEARCH",
1551
1552    /**
1553     * Advancing the dealer locator search page when fetching more results
1554     */
1555    SELECT_DEALER_LOCATOR_SEARCH_PAGE: "SELECT_DEALER_LOCATOR_SEARCH_PAGE",
1556
1557    /**
1558     * Conditional search events that could occur on a dealer locator.
1559     */
1560    SELECT_DEALER_LOCATOR_SEARCH_CONDITIONAL: "SELECT_DEALER_LOCATOR_SEARCH_CONDITIONAL",
1561
1562    /**
1563     * Checking the checkbox to show portal dealer's inventory on VAST.
1564     */
1565    CLICK_SHOW_MY_INVENTORY_ON_VAST: "CLICK_SHOW_MY_INVENTORY_ON_VAST",
1566
1567    /**
1568     * Checking the checkbox to show sister inventory on VAST.
1569     */
1570    CLICK_SHOW_SISTER_INVENTORY_ON_VAST: "CLICK_SHOW_SISTER_INVENTORY_ON_VAST",
1571
1572    /**
1573     * Clicking the 'AVAILABLE FOR PURCHASE' tab on VAST.
1574     */
1575    CLICK_AVAILABLE_FOR_PURCHASE_TAB_ON_VAST: "CLICK_AVAILABLE_FOR_PURCHASE_TAB_ON_VAST",
1576
1577    /**
1578     * Clicking the 'WHOLE WTS NETWORK' tab on VAST.
1579     */
1580    CLICK_WHOLE_WTS_NETWORK_TAB_ON_VAST: "CLICK_WHOLE_WTS_NETWORK_TAB_ON_VAST",
1581
1582    /**
1583     * Clicking the 'BRIGHTDROP POOL' tab on VAST.
1584     */
1585    CLICK_BRIGHTDROP_POOL_TAB_ON_VAST: "CLICK_BRIGHTDROP_POOL_TAB_ON_VAST",
1586
1587    /**
1588     * Checking the checkbox to show group inventory on VAST.
1589     */
1590    CLICK_SHOW_GROUP_INVENTORY_ON_VAST: "CLICK_SHOW_GROUP_INVENTORY_ON_VAST",
1591
1592   /**
1593     * Clicking the email of a trade contact
1594     */
1595    CLICK_EMAIL_VAST_CONTACT: "CLICK_EMAIL_VAST_CONTACT",
1596
1597    /**
1598     * Clicking the phone number of a trade contact
1599     */
1600    CLICK_CALL_VAST_CONTACT: "CLICK_CALL_VAST_CONTACT",
1601
1602    /**
1603     * Clicking the phone number of a dealer related to a VAST vehicle
1604     */
1605    CLICK_CALL_VAST_DEALER: "CLICK_CALL_VAST_DEALER",
1606
1607    /**
1608     * Clicking the condition url of a VAST vehicle 
1609     */
1610    CLICK_VAST_CONDITION_LINK: "CLICK_VAST_CONDITION_LINK",
1611
1612    /**
1613     * Clicking the auction url of a VAST vehicle
1614     */
1615    CLICK_VAST_AUCTION_LINK: "CLICK_VAST_AUCTION_LINK",
1616
1617    /**
1618     * Clicking to submit a message to send to a VAST contact
1619     */
1620    SUBMIT_VAST_MESSAGE: "SUBMIT_VAST_MESSAGE",
1621
1622    /**
1623     * Clicking an external link
1624     */
1625    CLICK_VAST_OUTBOUND_LINK: "CLICK_VAST_OUTBOUND_LINK",
1626
1627    /**
1628     * Clicking to view the 'trade details' / VAST details of a VAST vehicle should trigger this
1629     */
1630    OPEN_VEHICLE_ACQUISITION_DETAILS: "OPEN_VEHICLE_ACQUISITION_DETAILS",
1631
1632    /**
1633     * Clicking a "back to search" link or button
1634     */
1635    CLICK_BACK_TO_SEARCH_RESULTS: "CLICK_BACK_TO_SEARCH_RESULTS",
1636
1637    /**
1638     * CLicking "Get a quote" for a Bare Chassis
1639     */
1640    CLICK_BARE_CHASSIS_INQUIRY: "CLICK_BARE_CHASSIS_INQUIRY",
1641
1642    /**
1643     * CLicking "Get a quote" for a Body Only
1644     */
1645     CLICK_BODY_ONLY_INQUIRY: "CLICK_BODY_ONLY_INQUIRY",
1646
1647    /**
1648     * CLicking the "Save to Garage" button
1649     */
1650    CLICK_SAVE_VEHICLE_TO_GARAGE: "CLICK_SAVE_VEHICLE_TO_GARAGE",
1651
1652    /**
1653     * Clicking a vehicle ad from the ad loader.
1654     */
1655    CLICK_VEHICLE_AD: "CLICK_VEHICLE_AD",
1656
1657    /**
1658     * Clicking a recently viewed vehicle from the recently viewed carousel.
1659     */
1660    CLICK_RECENTLY_VIEWED: "CLICK_RECENTLY_VIEWED",
1661
1662    /**
1663     * Perform a conditional search for a truck.
1664     */
1665    TRUCK_SEARCH_EVENT_CONDITIONAL: "TRUCK_SEARCH_EVENT_CONDITIONAL",
1666
1667    /**
1668     * Perform a vin search.
1669     */
1670    TRUCK_SEARCH_EVENT_VIN: "TRUCK_SEARCH_EVENT_VIN",
1671
1672    TRUCK_SEARCH_EVENT_RANGE_CONDITIONAL: "TRUCK_SEARCH_EVENT_RANGE_CONDITIONAL",
1673
1674    MILES_SEARCH_CONDITIONAL: "MILES_SEARCH_CONDITIONAL",
1675
1676    LOCATION_SEARCH_CONDITIONAL: "LOCATION_SEARCH_CONDITIONAL",
1677
1678    VOCATION_SEARCH_CONDITIONAL: "VOCATION_SEARCH_CONDITIONAL",
1679
1680    TRUCK_SEARCH_CLEAR_FILTERS: "TRUCK_SEARCH_CLEAR_FILTERS",
1681
1682    TRUCK_SEARCH_ZIPCODE_CHANGE: "TRUCK_SEARCH_ZIPCODE_CHANGE",
1683
1684    /**
1685     * Searching for a truck returns no results.
1686     */
1687    TRUCK_SEARCH_EVENT_NO_RESULTS: "TRUCK_SEARCH_EVENT_NO_RESULTS",
1688
1689    /**
1690     * Fired after a VSP search completes and the new results are rendered.
1691     */
1692    VSP_SEARCH_COMPLETE: "VSP_SEARCH_COMPLETE",
1693
1694    /**
1695     * Clicking on truck search facets within vast.
1696     */
1697    CLICK_TRUCK_SEARCH_FACETS_VAST: "CLICK_TRUCK_SEARCH_FACETS_VAST",
1698
1699    /**
1700     * Events that can come from the about page.
1701     */
1702    ABOUT_EVENT: "ABOUT_EVENT",
1703
1704    /**
1705     * Clicking a custom page's email dealer button.
1706     */
1707    CLICK_CUSTOM_PAGE_EMAIL_DEAILER: "CLICK_CUSTOM_PAGE_EMAIL_DEAILER",
1708
1709    TRACK_CLOSE_FORM_EVENT: "TRACK_CLOSE_FORM_EVENT",
1710    /**
1711     * Tracks various vehicle events occuring from vehicle details.
1712     */
1713    VEHICLE_DETAILS_TRACK_VEHICLE_EVENT: "VEHICLE_DETAILS_TRACK_VEHICLE_EVENT",
1714
1715    /**
1716     * Tracks various vehicle branding events occuring from vehicle details.
1717     */
1718    VEHICLE_DETAILS_TRACK_BRANDING_EVENT: "VEHICLE_DETAILS_TRACK_BRANDING_EVENT",
1719
1720    /**
1721     * Track when the dealer video youtube link has been clicked.
1722     */
1723    VEHICLE_DETAILS_TRACK_DEALER_VIDEO_EVENT: "VEHICLE_DETAILS_TRACK_DEALER_VIDEO_EVENT",
1724
1725    /**
1726     * Track when the dealer video youtube link has been clicked.
1727     */
1728    VEHICLE_DETAILS_TRACK_BODY_DETAILS_VIDEO_EVENT: "VEHICLE_DETAILS_TRACK_BODY_DETAILS_VIDEO_EVENT",
1729
1730    VEHICLE_DETAILS_TRACK_BODY_MANUFACTURER_VIDEO_EVENT: "VEHICLE_DETAILS_TRACK_BODY_MANUFACTURER_VIDEO_EVENT",
1731
1732    VEHICLE_DETAILS_CLOSE_VIDEO_EVENT: "VEHICLE_DETAILS_CLOSE_VIDEO_EVENT",
1733
1734    /**
1735     * Track when internal links has been clicked
1736     */
1737    VEHICLE_DETAILS_INTERNAL_LINK_CLICK: "VEHICLE_DETAILS_INTERNAL_LINK_CLICK",
1738
1739    /**
1740     * Track when the vehicles notes read more link has been clicked
1741     */
1742    READ_MORE_VEHICLE_NOTES_CLICK: "READ_MORE_VEHICLE_NOTES_CLICK",
1743
1744    /**
1745     * Track when the vehicles notes read less link has been clicked
1746     */
1747    READ_LESS_VEHICLE_NOTES_CLICK: "READ_LESS_VEHICLE_NOTES_CLICK",
1748
1749    /**
1750     * Track when the vehicle options view all link has been clicked
1751     */
1752    VIEW_ALL_VEHICLE_OPTIONS_CLICK: "VIEW_ALL_VEHICLE_OPTIONS_CLICK",
1753
1754    /**
1755     * Track when the see dealership's other listings link has been clicked
1756     */
1757    SEE_DEALERSHIPS_OTHER_LISTINGS_CLICK: "SEE_DEALERSHIPS_OTHER_LISTINGS_CLICK",
1758
1759
1760    /**
1761     * Tracks various garage events occurring from my garage.
1762     */
1763    MY_GARAGE_GARAGE_EVENT: "MY_GARAGE_GARAGE_EVENT",
1764
1765    /**
1766     * Tracks various vehicle events occurring from my garage.
1767     */
1768    MY_GARAGE_VEHICLE_EVENT: "MY_GARAGE_VEHICLE_EVENT",
1769
1770    /**
1771     * Tracks various search events occurring from inventory layout.
1772     */
1773    INVENTORY_LAYOUT_TRACK_SEARCH_EVENT: "INVENTORY_LAYOUT_TRACK_SEARCH_EVENT",
1774
1775    /**
1776     * Tracks various locator vehicle search events from inventory layout.
1777     */
1778    INVENTORY_LAYOUT_TRACK_LOCATOR_SEARCH_EVENT: "INVENTORY_LAYOUT_TRACK_LOCATOR_SEARCH_EVENT",
1779
1780    /**
1781     * Tracks Facetgroup events
1782     */
1783    VSP_FACETGROUP_STATE_OPEN_EVENT: "VSP_FACETGROUP_STATE_OPEN_EVENT",
1784
1785    VSP_FACETGROUP_STATE_CLOSE_EVENT: "VSP_FACETGROUP_STATE_CLOSE_EVENT",
1786
1787    VSP_FACETGROUP_CHASSIS_OPEN_EVENT: "VSP_FACETGROUP_CHASSIS_OPEN_EVENT",
1788
1789    VSP_FACETGROUP_CHASSIS_CLOSE_EVENT: "VSP_FACETGROUP_CHASSIS_CLOSE_EVENT",
1790
1791    VSP_FACETGROUP_BODY_OPEN_EVENT: "VSP_FACETGROUP_BODY_OPEN_EVENT",
1792
1793    VSP_FACETGROUP_BODY_CLOSE_EVENT: "VSP_FACETGROUP_BODY_CLOSE_EVENT",
1794
1795    VSP_FACETGROUP_ADDONS_OPEN_EVENT: "VSP_FACETGROUP_ADDONS_OPEN_EVENT",
1796
1797    VSP_FACETGROUP_ADDONS_CLOSE_EVENT: "VSP_FACETGROUP_ADDONS_CLOSE_EVENT",
1798
1799    VSP_FACETGROUP_TRAILER_OPEN_EVENT: "VSP_FACETGROUP_TRAILER_OPEN_EVENT",
1800
1801    VSP_FACETGROUP_TRAILER_CLOSE_EVENT: "VSP_FACETGROUP_TRAILER_CLOSE_EVENT",
1802
1803    VSP_FACETGROUP_PRICE_OPEN_EVENT: "VSP_FACETGROUP_PRICE_OPEN_EVENT",
1804
1805    VSP_FACETGROUP_PRICE_CLOSE_EVENT: "VSP_FACETGROUP_PRICE_CLOSE_EVENT",
1806
1807    VSP_FACETGROUP_PROGRESSIVE_OPEN_EVENT: "VSP_FACETGROUP_PROGRESSIVE_OPEN_EVENT",
1808
1809    VSP_FACETGROUP_PROGRESSIVE_CLOSE_EVENT: "VSP_FACETGROUP_PROGRESSIVE_CLOSE_EVENT",
1810
1811    VAST_VSP_FACETGROUP_PARTNER_OPEN_EVENT: "VAST_VSP_FACETGROUP_PARTNER_OPEN_EVENT",
1812
1813    VAST_VSP_FACETGROUP_PARTNER_CLOSE_EVENT: "VAST_VSP_FACETGROUP_PARTNER_CLOSE_EVENT",
1814
1815    /**
1816     * Tracks Facetgroup events
1817     */
1818    VSP_FACET_OPEN_EVENT: "VSP_FACET_OPEN_EVENT",
1819
1820    VSP_FACET_CLOSE_EVENT: "VSP_FACET_CLOSE_EVENT",
1821
1822    /*
1823     * Tracks changing the number of items per page on the VSP
1824     */
1825    VSP_CHANGE_ITEMS_PER_PAGE: "VSP_CHANGE_ITEMS_PER_PAGE",
1826
1827    VSP_CHANGE_SORT: "VSP_CHANGE_SORT",
1828
1829    /**
1830     * Tracks EZOrder events
1831     */
1832    EZORDER_EVENT: "EZORDER_EVENT",
1833
1834    /**
1835     * Tracks Custom Order events
1836     */
1837    CUSTOM_ORDER_EVENT: "CUSTOM_ORDER_EVENT",
1838
1839    /**
1840     * Tracks CV Showroom events
1841     */
1842    CVSHOWROOM_EVENT: "CVSHOWROOM_EVENT",
1843
1844    /**
1845     * Tracks Taycor events
1846     */
1847    TAYCOR_EVENT: "TAYCOR_EVENT",
1848
1849    /**
1850     * Tracks Crestline events
1851     */
1852    CRESTLINE_EVENT: "CRESTLINE_EVENT",
1853
1854    /**
1855     * Tracks WebBuy events
1856     */
1857    WEBBUY_EVENT: "WEBBUY_EVENT",
1858
1859    /**
1860     * Navigation Page Events
1861     */
1862    NAVIGATION_PAGE_PARTNER_LEARN_MORE: "NAVIGATION_PAGE_PARTNER_LEARN_MORE",
1863    NAVIGATION_PAGE_VIEW_ALL_PARTNERS: "NAVIGATION_PAGE_VIEW_ALL_PARTNERS",
1864
1865    /**
1866     * Tracks Marketplace events
1867     */
1868    MARKETPLACE_EVENT: "MARKETPLACE_EVENT",
1869
1870    /**
1871     * Custom Orders Catalog Events
1872     */
1873    COMMERCIAL_CATALOG_TOGGLE: "COMMERCIAL_CATALOG_TOGGLE",
1874    COMMERCIAL_CATALOG_VIEW_BODY_LINE_LITERATURE_PDF: "COMMERCIAL_CATALOG_VIEW_BODY_LINE_LITERATURE_PDF",
1875    COMMERCIAL_CATALOG_VIEW_BODY_LINE_VIDEO: "COMMERCIAL_CATALOG_VIEW_BODY_LINE_VIDEO",
1876    COMMERCIAL_CATALOG_START_REQUEST: "COMMERCIAL_CATALOG_START_REQUEST",
1877    COMMERCIAL_CATALOG_VIEW_PHOTO: "COMMERCIAL_CATALOG_VIEW_PHOTO",
1878    COMMERCIAL_CATALOG_NEXT_PHOTO: "COMMERCIAL_CATALOG_NEXT_PHOTO",
1879    COMMERCIAL_CATALOG_PREV_PHOTO: "COMMERCIAL_CATALOG_PREV_PHOTO",
1880    COMMERCIAL_CATALOG_CHANGE_BODY_TYPE: "COMMERCIAL_CATALOG_CHANGE_BODY_TYPE",
1881    COMMERCIAL_CATALOG_CLICK_BODY_LINE: "COMMERCIAL_CATALOG_CLICK_BODY_LINE",
1882    COMMERCIAL_CATALOG_CLICK_SHOW_MORE: "COMMERCIAL_CATALOG_CLICK_SHOW_MORE",
1883    COMMERCIAL_CATALOG_CLICK_SHOW_MORE: "COMMERCIAL_CATALOG_CLICK_SHOW_MORE",
1884    COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_NEXT: "COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_NEXT",
1885    COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_PREVIOUS: "COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_PREVIOUS",
1886
1887    /**
1888     * Conditional Call To Action Events
1889     */
1890    CONDITIONAL_CALL_TO_ACTION: "CONDITIONAL_CALL_TO_ACTION",
1891
1892    /**
1893    * Resource Rack Catalog Events
1894    */
1895    RESOURCE_RACK_LAUNCH_PAGE: "RESOURCE_RACK_LAUNCH_PAGE",
1896    RESOURCE_RACK_CHANGE_SEARCH_CONTROLS: "RESOURCE_RACK_CHANGE_SEARCH_CONTROLS",
1897    RESOURCE_RACK_PREVIEW_LINK_CLICK: "RESOURCE_RACK_PREVIEW_LINK_CLICK",
1898    RESOURCE_RACK_ACTION_MENU_GET_LINK: "RESOURCE_RACK_ACTION_MENU_GET_LINK",
1899    RESOURCE_RACK_MULTISELECT_FOOTER_ACTION_COPY_LINKS: "RESOURCE_RACK_MULTISELECT_FOOTER_ACTION_COPY_LINKS",
1900    RESOURCE_RACK_MULTISELECT_FOOTER_ACTION_START_PROSPECTING: "RESOURCE_RACK_MULTISELECT_FOOTER_ACTION_START_PROSPECTING",
1901
1902    /**
1903    * Tracking Links History Events
1904    */
1905    TRACKING_LINKS_HISTORY_LAUNCH_PAGE: "TRACKING_LINKS_HISTORY_LAUNCH_PAGE",
1906    TRACKING_LINKS_HISTORY_CREATE_TRACKING_LINK: "TRACKING_LINKS_HISTORY_CREATE_TRACKING_LINK",
1907    TRACKING_LINKS_HISTORY_SUBMIT_TRACKING_LINK: "TRACKING_LINKS_HISTORY_SUBMIT_TRACKING_LINK",
1908    TRACKING_LINKS_HISTORY_CLICK_DETAILS: "TRACKING_LINKS_HISTORY_CLICK_DETAILS",
1909    TRACKING_LINKS_HISTORY_CLICK_EDIT_TRACKING_LINK: "TRACKING_LINKS_HISTORY_CLICK_EDIT_TRACKING_LINK",
1910    TRACKING_LINKS_HISTORY_CLICK_SAVE_EDIT: "TRACKING_LINKS_HISTORY_CLICK_SAVE_EDIT",
1911    TRACKING_LINKS_HISTORY_CLICK_DISABLE_LINK: "TRACKING_LINKS_HISTORY_CLICK_DISABLE_LINK",
1912    TRACKING_LINKS_HISTORY_CLICK_COPY_LINK: "TRACKING_LINKS_HISTORY_CLICK_COPY_LINK",
1913
1914    /**
1915    * Tracking Links Email
1916    */
1917    TRACKING_LINKS_EMAIL_CLICK_SEE_LINKS: "TRACKING_LINKS_EMAIL_CLICK_SEE_LINKS",
1918
1919    /**
1920    * Invoice Binder
1921    */
1922    INVOICE_BINDER_LAUNCH_PAGE: "INVOICE_BINDER_LAUNCH_PAGE",
1923    INVOICE_BINDER_SEARCH: "INVOICE_BINDER_SEARCH",
1924    INVOICE_BINDER_CLICK_ON_COUNT: "INVOICE_BINDER_CLICK_ON_COUNT",
1925    INVOICE_BINDER_CLICK_VIEW_LISTING_DETAILS: "INVOICE_BINDER_CLICK_VIEW_LISTING_DETAILS",
1926    INVOICE_BINDER_CLICK_MARK_IF_NOT_UPFIT: "INVOICE_BINDER_CLICK_MARK_IF_NOT_UPFIT",
1927    INVOICE_BINDER_CLICK_WINDOW_STICKER: "INVOICE_BINDER_CLICK_WINDOW_STICKER",
1928    INVOICE_BINDER_CLICK_INVOICE_LINK: "INVOICE_BINDER_CLICK_INVOICE_LINK",
1929    INVOICE_BINDER_CLICK_UPLOAD_INVOICES: "INVOICE_BINDER_CLICK_UPLOAD_INVOICES",
1930    INVOICE_BINDER_CLICK_DELETE_INVOICE: "INVOICE_BINDER_CLICK_DELETE_INVOICE",
1931
1932    /**
1933    * PDF viewer
1934    */
1935    PDF_VIEWER_INTERNAL_LOADING: "PDF_VIEWER_INTERNAL_LOADING",
1936    PDF_VIEWER_CLICK_GET_SHAREABLE_LINK: "PDF_VIEWER_CLICK_GET_SHAREABLE_LINK",
1937    PDF_VIEWER_CLICK_GET_TRACKING_LINK: "PDF_VIEWER_CLICK_GET_TRACKING_LINK",
1938
1939
1940    /**
1941    * PRINTING
1942    */
1943    PRINTING_ACTION_PRINT_COMPLETE_LISTING: "PRINTING_ACTION_PRINT_COMPLETE_LISTING",
1944    PRINTING_ACTION_PRINT_UPFIT_WINDOW_STICKER: "PRINTING_ACTION_PRINT_UPFIT_WINDOW_STICKER",
1945
1946    /**
1947     * Inventory Management
1948     */
1949    // General
1950    INVENTORY_MANAGEMENT_GROUP_SELECT_MEMBER_DEALER: 'INVENTORY_MANAGEMENT_GROUP_SELECT_MEMBER_DEALER',
1951    INVENTORY_MANAGEMENT_EXPORT_LISTINGS: 'INVENTORY_MANAGEMENT_EXPORT_LISTINGS',
1952    // Create Listing
1953    INVENTORY_MANAGEMENT_CREATE_BODY_ONLY_LISTING: "INVENTORY_MANAGEMENT_CREATE_BODY_ONLY_LISTING",
1954    INVENTORY_MANAGEMENT_CREATE_TRAILER_LISTING: "INVENTORY_MANAGEMENT_CREATE_TRAILER_LISTING",
1955    INVENTORY_MANAGEMENT_CREATE_VEHICLE_LISTING: "INVENTORY_MANAGEMENT_CREATE_VEHICLE_LISTING",
1956    // Inventory Summary
1957    INVENTORY_MANAGEMENT_CLICK_TOTAL_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_TOTAL_LISTINGS",
1958    INVENTORY_MANAGEMENT_CLICK_ON_SALE_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_ON_SALE_LISTINGS",
1959    INVENTORY_MANAGEMENT_CLICK_FEATURED_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_FEATURED_LISTINGS",
1960    INVENTORY_MANAGEMENT_CLICK_HIDDEN_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_HIDDEN_LISTINGS",
1961    INVENTORY_MANAGEMENT_CLICK_PRIVATE_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_PRIVATE_LISTINGS",
1962    INVENTORY_MANAGEMENT_CLICK_VAST_ONLY_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_VAST_ONLY_LISTINGS",
1963    INVENTORY_MANAGEMENT_CLICK_UNKNOWN_IF_UPFIT: "INVENTORY_MANAGEMENT_CLICK_UNKNOWN_IF_UPFIT",
1964    INVENTORY_MANAGEMENT_CLICK_MISSING_PHOTOS: "INVENTORY_MANAGEMENT_CLICK_MISSING_PHOTOS",
1965    INVENTORY_MANAGEMENT_CLICK_INVALID_PHOTOS: "INVENTORY_MANAGEMENT_CLICK_INVALID_PHOTOS",
1966    INVENTORY_MANAGEMENT_CLICK_NO_INVOICES: "INVENTORY_MANAGEMENT_CLICK_NO_INVOICES",
1967    INVENTORY_MANAGEMENT_CLICK_MANUAL_LISTINGS: "INVENTORY_MANAGEMENT_CLICK_MANUAL_LISTINGS",
1968    // Inventory Search
1969    INVENTORY_MANAGEMENT_TOGGLE_ADVANCED_SEARCH: "INVENTORY_MANAGEMENT_TOGGLE_ADVANCED_SEARCH",
1970    INVENTORY_MANAGEMENT_RUN_LAST_QUERY: 'INVENTORY_MANAGEMENT_RUN_LAST_QUERY',
1971    INVENTORY_MANAGEMENT_TEXT_SEARCH: "INVENTORY_MANAGEMENT_TEXT_SEARCH",
1972    INVENTORY_MANAGEMENT_FILTER_INVENTORY_SELECT: "INVENTORY_MANAGEMENT_FILTER_INVENTORY_SELECT",
1973    INVENTORY_MANAGEMENT_FILTER_INVENTORY_CHECKBOX: "INVENTORY_MANAGEMENT_FILTER_INVENTORY_CHECKBOX",
1974    INVENTORY_MANAGEMENT_RESET_FILTERS: "INVENTORY_MANAGEMENT_RESET_FILTERS",
1975    // Action Menu
1976    INVENTORY_MANAGEMENT_CLICK_VIEW_INVOICES: "INVENTORY_MANAGEMENT_CLICK_VIEW_INVOICES",
1977    INVENTORY_MANAGEMENT_CLICK_EDIT_PRICING: "INVENTORY_MANAGEMENT_CLICK_EDIT_PRICING",
1978    INVENTORY_MANAGEMENT_CLICK_EDIT_TRADE_DETAILS: "INVENTORY_MANAGEMENT_CLICK_EDIT_TRADE_DETAILS",
1979    INVENTORY_MANAGEMENT_CLICK_EDIT_LISTING: "INVENTORY_MANAGEMENT_CLICK_EDIT_LISTING",
1980    INVENTORY_MANAGEMENT_CLICK_MARK_AS_MOVED: "INVENTORY_MANAGEMENT_CLICK_MARK_AS_MOVED",
1981    INVENTORY_MANAGEMENT_CLICK_DELETE_LISTING: "INVENTORY_MANAGEMENT_CLICK_DELETE_LISTING",
1982    // Youtube Video Player
1983    VIDEO_PLAYER_START: "VIDEO_PLAYER_START",
1984    VIDEO_PLAYER_RESUME: "VIDEO_PLAYER_RESUME",
1985    VIDEO_PLAYER_PAUSE: "VIDEO_PLAYER_PAUSE",
1986    VIDEO_PLAYER_THRESHOLD: "VIDEO_PLAYER_THRESHOLD"
1987};;
1988(function (wts, modules) {
1989
1990    var EventEmitter = modules.EventEmitter;
1991
1992    function Visor() {
1993        EventEmitter.call(this);
1994    }
1995
1996    Visor.prototype = Object.assign(EventEmitter.prototype, {
1997
1998        /**
1999         * @static
2000         */
2001        Events: wts.UI_EVENTS,
2002
2003        /**
2004         * @alias trigger
2005         * @returns {any[]} The results of the listeners
2006         */
2007        fire: function () {
2008            return this.trigger.apply(this, arguments);
2009        },
2010
2011        /**
2012         * Fires an event and allows you to wait until all listeners to that event are done
2013         * @async
2014         * @param {string} type The event name
2015         * @param {any}
2015 data Event Data to be passed to listeners of this event
2016         * @returns {Promise} A promise that resolves with all listener promises
2017         */
2018        fireAndWait: function (type, data) {
2019
2020            // filter out any listeners that are not a Thenable
2021            var promises = this.trigger(type, data)
2022                .filter(function (result) {
2023                    var hasResult = result !== null && typeof result !== "undefined";
2024                    var isThenable = false;
2025                    if (hasResult) {
2026                        isThenable = typeof result.promise !== "undefined";
2027                    }
2028                    return hasResult && isThenable;
2029                });
2030
2031            return $.when.apply(null, promises);
2032        }
2033
2034    });
2035
2036    // export as singleton
2037    wts.visor = new Visor();
2038
2039})(window.wts, window.wts.modules);;
2040(function (wts, modules) {
2041    /**
2042    * The location of any global visor event bindings
2043    * The UI should hook into visor here.
2044    *
2045    */
2046
2047    var UI_EVENTS = wts.visor.Events;
2048
2049    UI_EVENTS;
2050
2051    wts.ready.then(function () {
2052
2053        // phone links within ajax-updated truck search list items              
2054        var selector = ".truck__phone-number .truck__phone-link";
2055        rebindClickToCallEvent(selector);
2056
2057        wts.ready.then(function (wts, modules) {
2058            if (wts.truckSearch) {
2059
2060                wts.truckSearch.on("rebind", function () {
2061                    rebindClickToCallEvent(selector);
2062                });
2063            }
2064        });
2065
2066        // phone links within the vdp pricing column (delegated — pricing tel link is injected after ready)
2067        $(document).on(
2068            "click",
2069            ".mobile-call-action-link .action-link, .advanced-pricing-actions__phone-link, .vehicle-details__comvoy-mobile-action-link-phone",
2070            function (e) {
2071            var fordDirectEnabled =
2072                modules.FordDirectAnalytics &&
2073                modules.FordDirectAnalytics.isEnabled();
2074
2075            if (
2076                !wts.user.isTruckProOrAbove &&
2077                (window.analyticsEnabled || fordDirectEnabled)
2078            ) {
2079                var $t = $(this);
2080                var isPhotoGalleryPhone =
2081                    $t.closest("#photo-gallery-modal").length > 0;
2082                var invID = isPhotoGalleryPhone
2083                    ? $t
2084                          .closest("#photo-gallery-modal")
2085                          .find(".im-interested-container")
2086                          .attr("data-truck-id") ||
2087                      wts.sender.getInventory().current
2088                    : wts.sender.getInventory().current;
2089                var hrefTel = ($t.attr("href") || "").replace(/^tel:/i, "").trim();
2090                var e164 =
2091                    modules.utilities.phoneToE164(hrefTel) ||
2092                    modules.utilities.phoneToE164($t.attr("data-comm-phone-number")) ||
2093                    modules.utilities.phoneToE164($t.text().trim());
2094                var display =
2095                    $t.attr("data-phonedisplay") ||
2096                    $t.closest(".phone-holder-pricing__phone-link").attr("data-phonedisplay") ||
2097                    $t.text().trim();
2098
2099                var gaLabel =
2100                    (display && modules.utilities.phoneToE164(display)) ||
2101                    display ||
2102                    e164 ||
2103                    "";
2104
2105                // fire and wait call us now event
2106                wts.visor.fireAndWait(UI_EVENTS.CALL_US_NOW_IM_INTERESTED, {
2107                    inventoryID: invID,
2108                    commPhoneNumber: e164 || undefined,
2109                    gaPhoneLabel: gaLabel || undefined,
2110                    modalContext: isPhotoGalleryPhone
2111                        ? "see-all-photos-i-am-interested"
2112                        : undefined,
2113                    clickToCallSource: isPhotoGalleryPhone
2114                        ? undefined
2115                        : "vehicle-details",
2116                });
2117            }
2118        }
2119        );
2120    });
2121
2122    /**
2123     * Rebinds our truck search listing `Phone Call` link event listeners.
2124     * @param {string} selector the class name or id of the click-to-call HTMLElement.
2125     * @returns {void}
2126     */
2127    function rebindClickToCallEvent(selector) {
2128        $(selector).off("click").click(function (e) {
2129            var invID = $(this).closest(".truck-listings__item").attr("data-truck-id");
2130            // fire and wait call us now event
2131            // trackClickToCallEvent("Search", "Phone Call", invID);
2132            wts.visor.fireAndWait(UI_EVENTS.CALL_US_NOW_IM_INTERESTED,
2133                {
2134                    category: "Search",
2135                    action: "Phone Call",
2136                    inventoryID: invID
2137                }
2138            );
2139        });
2140    }
2141
2142})(window.wts, window.wts.modules);
2143;
2144(function (wts, modules) {
2145    // imports
2146    getClientData = modules.getClientData;
2147
2148    // initialization of module
2149    var ascTrackingSetup = getClientData("#asc-tracking-setup");
2150
2151    // "private static" members
2152    var _vocationalCarouselCurrentPage = 0;
2153    var _formInteractions = new Map();
2154    var _customFormInteractions = new Map();
2155    var _enableAscTracking = ascTrackingSetup.enableAscTracking;
2156    var _ascMeasurementIDs = ascTrackingSetup.ascMeasurementIDs;
2157    var _enableDebug = ascTrackingSetup.enableDebug;
2158    let _isResizing = false;
2159    let _resizeTimer;
2160
2161    /**
2162     * @param {JQuery} $element clicked phone link
2163     * @returns {string} E.164 when parsable; otherwise best-effort visible/href text
2164     */
2165    function normalizeCommPhoneFromClick($element) {
2166        var hrefRaw = ($element.attr("href") || "").replace(/^tel:/i, "").trim();
2167        var fromHref = modules.utilities.phoneToE164(hrefRaw);
2168        if (fromHref) {
2169            return fromHref;
2170        }
2171        var dataComm = $element.attr("data-comm-phone-number");
2172        if (dataComm) {
2173            var fromData = modules.utilities.phoneToE164(dataComm);
2174            if (fromData) {
2175                return fromData;
2176            }
2177        }
2178        var text = modules.utilities.getInnerText($element);
2179        return modules.utilities.phoneToE164(text) || text || "";
2180    }
2181
2182    /**
2183     * ASC `element_text` for click-to-call: E.164 when the visible label parses as a phone;
2184     * otherwise keep human-readable text; if empty, fall back to normalized {@link normalizeCommPhoneFromClick}.
2185     * @param {JQuery} $element clicked phone link
2186     * @param {string} commPhoneFallback E.164 or best-effort value from {@link normalizeCommPhoneFromClick}
2187     */
2188    function commPhoneElementText($element, commPhoneFallback) {
2189        var fromAttr = $element.attr("data-phonedisplay");
2190        var inner = modules.utilities.getInnerText($element);
2191        var candidate =
2192            (fromAttr && String(fromAttr).trim()) ||
2193            (inner && String(inner).trim()) ||
2194            "";
2195
2196        var asE164 = modules.utilities.phoneToE164(candidate);
2197        if (asE164) {
2198            return asE164;
2199        }
2200        if (candidate) {
2201            return candidate;
2202        }
2203        var fb = (commPhoneFallback && String(commPhoneFallback).trim()) || "";
2204        return modules.utilities.phoneToE164(fb) || fb;
2205    }
2206
2207    // Set up resize event listeners to manage the _isResizing flag
2208    window.addEventListener("resize", function () {
2209        _isResizing = true;
2210        clearTimeout(_resizeTimer);
2211        _resizeTimer = setTimeout(function () {
2212            _isResizing = false;
2213        }, 250); // 250ms delay to ensure resize event has settled
2214    });
2215
2216    const EVENT = Object.fromEntries(
2217        [
2218            "asc_pageview",
2219            "asc_item_pageview",
2220            "asc_itemlist_pageview",
2221            "asc_retail_process",
2222            "asc_cta_interaction",
2223            "asc_element_configuration",
2224            "asc_menu_interaction",
2225            "asc_media_interaction",
2226            "asc_special_offer",
2227            "asc_click_to_call",
2228            "asc_form_engagement",
2229            "asc_form_submission",
2230            "asc_form_submission_sales",
2231            "asc_form_submission_parts",
2232            "asc_form_submission_service",
2233            "asc_form_submission_appt",
2234            "asc_form_submission_other",
2235        ].map((value) => [value.toUpperCase(), value])
2236    );
2237
2238    const COMM_OUTCOME = Object.fromEntries(
2239        [
2240            "appt_booked",
2241            "vehicle_ordered",
2242            "parts_ordered",
2243            "get_back_commitment",
2244            "not_opportunity",
2245            "robocall",
2246            "opportunity",
2247            "unknown",
2248        ].map((value) => [value.toUpperCase(), value])
2249    );
2250
2251    const COMM_STATUS = Object.fromEntries(
2252        [
2253            "start",
2254            "close",
2255            "engage",
2256            "inject",
2257            "send_deal",
2258            "send_payment",
2259            "send_appt",
2260            "idle",
2261            "timeout",
2262            "unanswered",
2263            "transfer_abandon",
2264            "hold_time_abandon",
2265            "voicemail",
2266            "answered",
2267            "crm_update",
2268            "appt_booked",
2269            "phone_tree_keypress",
2270            "send",
2271            "agent_escalation",
2272            "digital_retailing_handoff",
2273            "load",
2274            "video_start",
2275            "video_end",
2276            "connect",
2277            "ring",
2278        ].map((value) => [value.toUpperCase(), value])
2279    );
2280
2281    const COMM_TYPE = Object.fromEntries(
2282        ["chat", "sms", "voice", "video", "form", "email"].map((value) => [
2283            value.toUpperCase(),
2284            value,
2285        ])
2286    );
2287
2288    const DEPARTMENT = Object.fromEntries(
2289        [
2290            "sales",
2291            "service",
2292            "parts",
2293            "trade",
2294            "credit",
2295            "body_shop",
2296            "admin",
2297            "accessories",
2298            "hr",
2299            "custom_shop",
2300            "rentals",
2301            "unknown",
2302            "quick_lube",
2303        ].map((value) => [value.toUpperCase(), value])
2304    );
2305
2306    const ELEMENT_POSITION = Object.fromEntries(
2307        [
2308            "top_left",
2309            "top_right",
2310            "top_center",
2311            "center_left",
2312            "center_right",
2313            "center_center",
2314            "bottom_left",
2315            "bottom_right",
2316            "bottom_center",
2317            "floating",
2318            "inline",
2319            "unknown",
2320        ].map((value) => [value.toUpperCase(), value])
2321    );
2322
2323    const ELEMENT_STATE = Object.fromEntries(
2324        ["active", "inactive"].map((value) => [value.toUpperCase(), value])
2325    );
2326
2327    const ELEMENT_TYPE = Object.fromEntries(
2328        [
2329            "header",
2330            "banner",
2331            "popup",
2332            "form",
2333            "digital_retailing_tool",
2334            "trade_tool",
2335            "chat_tool",
2336            "accessability_tool",
2337            "search_tool",
2338            "scheduling_tool",
2339            "gallery",
2340            "map",
2341            "footer",
2342            "video",
2343            "item_details",
2344            "pagination",
2345            "body",
2346            "menu",
2347        ].map((value) => [value.toUpperCase(), value])
2348    );
2349
2350    const ELEMENT_SUBTYPE = Object.fromEntries(
2351        [
2352            "cta_button",
2353            "cta_icon",
2354            "nav_button",
2355            "nav_icon",
2356            "nav_tab",
2357            "hamburger_menu",
2358            "mega_menu",
2359            "sidebar_menu",
2360            "inline_menu",
2361            "hyperlink",
2362            "content",
2363            "share_icon",
2364            "image",
2365            "input_field",
2366            "toggle",
2367            "dropdown",
2368            "kebab_menu",
2369            "hero_image",
2370        ].map((value) => [value.toUpperCase(), value])
2371    );
2372
2373    const EVENT_ACTION = Object.fromEntries(
2374        [
2375            "click",
2376            "swipe_left",
2377            "swipe_right",
2378            "swipe_up",
2379            "swipe_down",
2380            "hover",
2381            "view",
2382            "scroll",
2383            "field_input",
2384            "keystroke",
2385        ].map((value) => [value.toUpperCase(), value])
2386    );
2387
2388    const EVENT_ACTION_RESULT = Object.fromEntries(
2389        [
2390            "play",
2391            "restart",
2392            "vr_play",
2393            "spin",
2394            "popup",
2395            "slideout",
2396            "exit",
2397            "error",
2398            "search",
2399            "calc",
2400            "start",
2401            "stop",
2402            "redirect",
2403            "open",
2404            "close",
2405            "increase",
2406            "decrease",
2407            "clear",
2408            "check",
2409            "uncheck",
2410            "complete",
2411            "dropdown",
2412            "none",
2413            "shown",
2414            "next_step",
2415        ].map((value) => [value.toUpperCase(), value])
2416    );
2417
2418    const FORM_TYPE = Object.fromEntries(
2419        [
2420            "consumer_contact",
2421            "trade",
2422            "recall",
2423            "parts",
2424            "accessories",
2425            "service_appointment",
2426            "pre-order",
2427            "custom",
2428            "bodyshop",
2429            "finance/credit",
2430            "human_resources",
2431            "research",
2432            "surveys",
2433            "unknown",
2434            "alert",
2435            "offer",
2436            "quote",
2437            "download",
2438            "shipment",
2439            "purchase",
2440        ].map((value) => [value.toUpperCase(), value])
2441    );
2442
2443    const MEDIA_TYPE = Object.fromEntries(
2444        [
2445            "video",
2446            "image",
2447            "vr",
2448            "slider",
2449            "gallery",
2450            "offer",
2451            "360_platform",
2452            "map",
2453            "thumbnail",
2454        ].map((value) => [value.toUpperCase(), value])
2455    );
2456
2457    const PAGE_TYPE = Object.fromEntries(
2458        [
2459            "item",
2460            "itemlist",
2461            "contact",
2462            "directions",
2463            "service",
2464            "parts",
2465            "accessories",
2466            "body_shop",
2467            "home",
2468            "build_to_order",
2469            "custom",
2470            "complaint",
2471            "employment",
2472            "recall",
2473            "finance",
2474            "credit",
2475            "trade",
2476            "commercial",
2477            "showroom",
2478            "unknown",
2479            "thank_you",
2480            "specials",
2481            "custom_orders",
2482        ].map((value) => [value.toUpperCase(), value])
2483    );
2484
2485    const ASC_MAPPINGS = {
2486        EVENT,
2487        COMM_OUTCOME,
2488        COMM_STATUS,
2489        COMM_TYPE,
2490        DEPARTMENT,
2491        ELEMENT_POSITION,
2492        ELEMENT_STATE,
2493        ELEMENT_TYPE,
2494        ELEMENT_SUBTYPE,
2495        EVENT_ACTION,
2496        EVENT_ACTION_RESULT,
2497        FORM_TYPE,
2498        MEDIA_TYPE,
2499        PAGE_TYPE,
2500    };
2501
2502    const REQUIRED_PARAMS = {
2503        asc_pageview: ["event_owner", "page_type"],
2504        asc_retail_process: [
2505            "event_owner",
2506            "page_type",
2507            "flow_name",
2508            "element_title",
2509            "flow_outcome",
2510        ],
2511        asc_cta_interaction: [
2512            "event_owner",
2513            "page_type",
2514            "element_text",
2515            "element_type",
2516            "element_subtype",
2517            "event_action",
2518            "event_action_result",
2519            "link_url",
2520        ],
2521        asc_element_configuration: [
2522            "event_owner",
2523            "page_type",
2524            "event_action_result",
2525            "event_action",
2526            "element_state",
2527            "element_type",
2528            "element_subtype",
2529            "element_title",
2530            "element_text",
2531            "element_value",
2532        ],
2533        asc_menu_interaction: [
2534            "event_owner",
2535            "page_type",
2536            "element_text",
2537            "element_subtype",
2538            "element_type",
2539            "event_action",
2540        ],
2541        asc_media_interaction: [
2542            "event_owner",
2543            "page_type",
2544            "event_action",
2545            "event_action_result",
2546            "media_type",
2547            "link_url",
2548        ],
2549        asc_special_offer: ["event_owner", "page_type", "promotion_name"],
2550        asc_click_to_call: [
2551            "event_owner",
2552            "page_type",
2553            "comm_phone_number",
2554            "element_text",
2555            "department",
2556        ],
2557        asc_form_engagement: [
2558            "event_owner",
2559            "page_type",
2560            "comm_type",
2561            "comm_status",
2562            "element_text",
2563            "department",
2564            "form_name",
2565            "form_type",
2566        ],
2567        asc_form_submission: [
2568            "event_owner",
2569            "page_type",
2570            "comm_type",
2571            "comm_outcome",
2572            "submission_id",
2573            "element_text",
2574            "department",
2575            "form_name",
2576            "form_type",
2577        ],
2578        asc_form_submission_sales: [
2579            "event_owner",
2580            "page_type",
2581            "comm_type",
2582            "comm_outcome",
2583            "submission_id",
2584            "element_text",
2585            "department",
2586            "form_name",
2587            "form_type",
2588        ],
2589        asc_form_submission_parts: [
2590            "event_owner",
2591            "page_type",
2592            "comm_type",
2593            "comm_outcome",
2594            "submission_id",
2595            "element_text",
2596            "department",
2597            "form_name",
2598            "form_type",
2599        ],
2600        asc_form_submission_service: [
2601            "event_owner",
2602            "page_type",
2603            "comm_type",
2604            "comm_outcome",
2605            "submission_id",
2606            "element_text",
2607            "department",
2608            "form_name",
2609            "form_type",
2610        ],
2611        asc_form_submission_appt: [
2612            "event_owner",
2613            "page_type",
2614            "comm_type",
2615            "comm_outcome",
2616            "submission_id",
2617            "element_text",
2618            "department",
2619            "form_name",
2620            "form_type",
2621        ],
2622        asc_form_submission_other: [
2623            "event_owner",
2624            "page_type",
2625            "comm_type",
2626            "comm_outcome",
2627            "submission_id",
2628            "element_text",
2629            "department",
2630            "form_name",
2631            "form_type",
2632        ],
2633    };
2634
2635    const OPTIONAL_PARAMS = {
2636        asc_pageview: [
2637            "product_name",
2638            "item_id",
2639            "item_number",
2640            "item_price",
2641            "item_condition",
2642            "item_year",
2643            "item_make",
2644            "item_model",
2645            "item_variant",
2646            "item_color",
2647            "item_type",
2648            "item_category",
2649            "item_inventory_date",
2650            "item_results",
2651            "item_location",
2652        ],
2653        asc_retail_process: [
2654            "flow_stage",
2655            "product_name",
2656            "item_payment",
2657            "item_id",
2658            "item_number",
2659            "item_price",
2660            "item_condition",
2661            "item_year",
2662            "item_make",
2663            "item_model",
2664            "item_variant",
2665            "item_color",
2666            "item_type",
2667            "item_category",
2668            "item_inventory_date",
2669            "item_location",
2670        ],
2671        asc_cta_interaction: [
2672            "element_color",
2673            "element_order",
2674            "product_name",
2675            "affiliation",
2676            "item_id",
2677            "item_number",
2678            "item_price",
2679            "item_condition",
2680            "item_year",
2681            "item_make",
2682            "item_model",
2683            "item_variant",
2684            "item_color",
2685            "item_type",
2686            "item_category",
2687            "item_inventory_date",
2688            "item_location",
2689        ],
2690        asc_element_configuration: [
2691            "product_name",
2692            "element_color",
2693            "element_position",
2694            "element_order",
2695        ],
2696        asc_menu_interaction: ["product_name"],
2697        asc_media_interaction: [
2698            "affiliation",
2699            "creative_name",
2700            "product_name",
2701            "item_id",
2702            "item_number",
2703            "item_price",
2704            "item_condition",
2705            "item_year",
2706            "item_make",
2707            "item_model",
2708            "item_variant",
2709            "item_color",
2710            "item_type",
2711            "item_category",
2712            "item_inventory_date",
2713            "item_location",
2714        ],
2715        asc_special_offer: [
2716            "department",
2717            "product_name",
2718            "element_position",
2719            "flow_outcome",
2720        ],
2721        asc_click_to_call: [
2722            "affiliation",
2723            "product_name",
2724            "item_id",
2725            "item_number",
2726            "item_price",
2727            "item_condition",
2728            "item_year",
2729            "item_make",
2730            "item_model",
2731            "item_variant",
2732            "item_color",
2733            "item_type",
2734            "item_category",
2735            "item_inventory_date",
2736            "item_location",
2737        ],
2738        asc_form_engagement: [
2739            "product_name",
2740            "item_id",
2741            "item_number",
2742            "item_price",
2743            "item_condition",
2744            "item_year",
2745            "item_make",
2746            "item_model",
2747            "item_variant",
2748            "item_color",
2749            "item_type",
2750            "item_category",
2751            "item_inventory_date",
2752            "item_location",
2753        ],
2754        asc_form_submission: [
2755            "product_name",
2756            "item_id",
2757            "item_number",
2758            "item_price",
2759            "item_condition",
2760            "item_year",
2761            "item_make",
2762            "item_model",
2763            "item_variant",
2764            "item_color",
2765            "item_type",
2766            "item_category",
2767            "item_inventory_date",
2768            "item_location",
2769        ],
2770    };
2771
2772    // "public" functions
2773    AscTracking = {
2774        /**
2775         * Resets window.asc_datalayer.items to an empty array.
2776         */
2777        resetItems: function () {
2778            if (!_enableAscTracking) {
2779                return;
2780            }
2781
2782            window.asc_datalayer.items = [];
2783        },
2784
2785        /**
2786         * Adds the specified vehicle information to the window.asc_datalayer.items array.
2787         * @param {any} color The color of the vehicle.
2788         * @param {any} condition The condition of the vehicle.
2789         * @param {any} fuelType The fuel type of the vehicle.
2790         * @param {any} vin The vin of the vehicle.
2791         * @param {any} createdDate The date the vehicle was added to inventory.
2792         * @param {any} make The make of the vehicle.
2793         * @param {any} model The model of the vehicle.
2794         * @param {any} stockNumber The stock number of the vehicle.
2795         * @param {any} bodyStyle The body style of the vehicle.
2796         * @param {any} trim The trim of the vehicle.
2797         * @param {any} year The year of the vehicle.
2798         * @param {any} price The price of the vehicle.
2799         * @param {any} inventoryID The Inventory ID of the vehicle.
2800         */
2801        addVehicleToAscDatalayerItems: function (
2802            color,
2803            condition,
2804            fuelType,
2805            vin,
2806            createdDate,
2807            make,
2808            model,
2809            stockNumber,
2810            bodyStyle,
2811            trim,
2812            year,
2813            price,
2814            inventoryID,
2815            location
2816        ) {
2817            if (!_enableAscTracking) {
2818                return;
2819            }
2820
2821            window.asc_datalayer.items.push({
2822                item_category: "", // TODO: implement category - doc has "domestic" so we need a way of telling either foreign or domestic, example given shows the empty string
2823                item_color: color,
2824                item_condition: condition,
2825                item_fuel_type: fuelType,
2826                item_id: vin,
2827                item_inventory_date: createdDate,
2828                item_make: make,
2829                item_model: model,
2830                item_number: stockNumber,
2831                item_payment: "finance", // TODO: implement other options - doc states "Payment shown, or selected which is associated with the event", example given shows "finance"
2832                item_type: bodyStyle,
2833                item_variant: trim,
2834                item_year: year,
2835                item_price: price,
2836                item_inventory_id: inventoryID,
2837                item_location: location,
2838            });
2839        },
2840
2841        /**
2842         * Tracks a page view event.
2843         * @param {any} event Event data giving context to the page view event.
2844         */
2845        trackPageViewEvent: function (event) {
2846            if (!_enableAscTracking) {
2847                return;
2848            }
2849
2850            var ascEventData = {
2851                event: event.type,
2852                event_owner: window.asc_datalayer.affiliation,
2853                page_type:
2854                    window.asc_datalayer.page_type === "custom_page"
2855                        ? "custom"
2856                        : window.asc_datalayer.page_type,
2857            };
2858
2859            var errorCode = window.asc_datalayer.error_code;
2860            if (
2861                typeof errorCode === "number" &&
2862                errorCode >= 400
2863            ) {
2864                ascEventData.error_code = errorCode;
2865            } else if (
2866                typeof errorCode === "string" &&
2867                errorCode !== "none" &&
2868                !isNaN(Number(errorCode)) &&
2869                Number(errorCode) >= 400
2870            ) {
2871                ascEventData.error_code = Number(errorCode);
2872            }
2873
2874            var items = window.asc_datalayer.items || [];
2875            if (items.length > 0) {
2876                Object.assign(ascEventData, formatData(items));
2877                var paginationCount = Number(
2878                    $(".pagination__counts em:nth-child(3)").first().text()
2879                );
2880                if (!isNaN(paginationCount) && paginationCount > 0) {
2881                    ascEventData.item_results = paginationCount;
2882                } else {
2883                    ascEventData.item_results = items.length;
2884                }
2885            }
2886
2887            trackAscEvent(ascEventData);
2888        },
2889
2890        /**
2891         * Tracks a general layout interaction click event that occurs when various controls from the general layout of the UI are clicked.
2892         * @param {any} event Event data giving context to the general layout click event.
2893         */
2894        fireAscEvent: function (ascEventData) {
2895            trackAscEvent(ascEventData);
2896        },
2897
2898        trackGeneralLayoutInteractionClick: function (event) {
2899            if (!_enableAscTracking) {
2900                return;
2901            }
2902
2903            if (
2904                modules.AscDelegatedTracking &&
2905                modules.AscDelegatedTracking.trackDecoratedClick(
2906                    event.detail
2907                )
2908            ) {
2909                return;
2910            }
2911
2912            // helper function used to find the matching item in window.asc_datalayer.items
2913            const getMatchingItem = function ($el) {
2914                const truckIDTarget =
2915                    $el.data("truckId") || modules.utilities.getTruckID($el);
2916                const marketingData = truckIDTarget
2917                    ? modules.getClientData(
2918                          `#analytics-marketing-data-${truckIDTarget}`
2919                      ) // get data from the specific #analytics-marketing-data- section (VSP or I'm Interested Form)
2920                    : modules.getClientData(`#marketing-events-data`); // get data from the #marketing-events-data section (VDP)
2921                return marketingData
2922                    ? [
2923                          window.asc_datalayer.items.find(
2924                              (i) => i.item_id === marketingData.vin
2925                          ),
2926                      ]
2927                    : [];
2928            };
2929
2930            const currentTarget = event.detail;
2931            const $element = $(currentTarget);
2932            const elementText = modules.utilities.getInnerText($element);
2933
2934            var ascEventData = {
2935                // Required for CTA events
2936                event: event.type,
2937                page_type: window.asc_datalayer.page_type,
2938                event_owner: window.asc_datalayer.affiliation,
2939                element_text: elementText,
2940                element_title: $element.data("asc-element-title") || "",
2941                element_type:
2942                    event?.detail.elementType || ELEMENT_TYPE.ITEM_DETAILS,
2943                element_subtype:
2944                    event?.detail.elementSubtype || ELEMENT_SUBTYPE.NAV_BUTTON,
2945                event_action: event?.detail.eventAction || EVENT_ACTION.CLICK,
2946                event_action_result:
2947                    event?.detail.eventActionResult || EVENT_ACTION_RESULT.OPEN,
2948                link_url: event?.detail.linkUrl,
2949                // Optional
2950                items: window.asc_datalayer.items,
2951            };
2952
2953            if (currentTarget.href) {
2954                // handle all links: i.e. link back to dealer site, links to the VDP, or links in the footer
2955
2956                // test to see if this is a navigation link which is different than the links within the inventory grid
2957                var parentElement = null;
2958                var testElement = $element.parent();
2959                const elementOptions = [
2960                    "navigation__link",
2961                    "mobile-auth-link",
2962                    "return-home-link",
2963                    "breadcrumb",
2964                    "footer-nav",
2965                ];
2966
2967                for (option of elementOptions) {
2968                    if (testElement.hasClass(option)) {
2969                        parentElement = testElement;
2970                        break;
2971                    }
2972                }
2973
2974                if (!parentElement) {
2975                    if ($element.parents(".footer-nav").length) {
2976                        parentElement = $element.parents(".footer-nav").first();
2977                    }
2978                }
2979
2980                if (parentElement && parentElement.attr("class")) {
2981                    // header/footer link
2982                    ascEventData.element_type = parentElement.hasClass(
2983                        "footer-nav"
2984                    )
2985                        ? "footer"
2986                        : "header";
2987                    ascEventData.element_text =
2988                        modules.utilities.getInnerText($element) ||
2989                        $element[0].innerText;
2990
2991                    // Set element_title to parent dropdown text in header dropdown menus
2992                    if (ascEventData.element_type === "header") {
2993                        const dropdownLinkText =
2994                            $element
2995                                .closest(".dropdown")
2996                                .find(".dropdown__link")
2997                                .text()
2998                                .trim() || "";
2999                        ascEventData.element_title = dropdownLinkText;
3000                    }
3001
3002                    // Truck Pro Login button
3003                    if (ascEventData.element_text === "") {
3004                        if ($element.hasClass("auth--login")) {
3005                            ascEventData.element_text =
3006                                modules.utilities.getInnerText(
3007                                    $element
3008                                        .children(".log-in-out-text")
3009                                        .first()
3010                                );
3011                        }
3012                    }
3013
3014                    // Custom orders page type
3015                    if (
3016                        ascEventData.element_text.toLowerCase() ===
3017                        "custom orders"
3018                    ) {
3019                        ascEventData.page_type = PAGE_TYPE.CUSTOM;
3020                    }
3021
3022                    ascEventData.event = EVENT.ASC_MENU_INTERACTION;
3023                } else if ($element.hasClass("specifications__see-more")) {
3024                    // handles see more details link click to open the VDP from VSP
3025                    ascEventData.items = getMatchingItem($element);
3026                    ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3027                    ascEventData.link_url = $element[0].href;
3028                }
3028 else if ($element.is(".js-custom-orders__asc-event")) {
3029                    // handles see more body type details link on Custom Orders
3030                    ascEventData.element_text =
3031                        $element[0].innerText || ascEventData.element_title;
3032                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3033                    ascEventData.link_url = $element[0].href;
3034                } else if ($element.hasClass("truck-image")) {
3035                    // handles truck image click to open the VDP from VSP
3036                    ascEventData.event = EVENT.ASC_MEDIA_INTERACTION;
3037                    ascEventData.media_type = MEDIA_TYPE.IMAGE;
3038                    ascEventData.link_url = $element
3039                        .find(".truck-image__image")
3040                        .attr("src");
3041                    ascEventData.items = getMatchingItem($element);
3042                } else if ($element.hasClass("truck__link-text")) {
3043                    // handles truck title click to open the VDP from VSP
3044                    ascEventData.items = getMatchingItem($element);
3045                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3046                    ascEventData.link_url = $element[0].href;
3047                } else if ($element.hasClass("truck__phone-link")) {
3048                    // handles phone number click within the inventory grid item
3049                    ascEventData.event = EVENT.ASC_CLICK_TO_CALL;
3050                    ascEventData.comm_phone_number =
3051                        normalizeCommPhoneFromClick($element);
3052                    ascEventData.element_text = commPhoneElementText(
3053                        $element,
3054                        ascEventData.comm_phone_number
3055                    );
3056                    ascEventData.department = DEPARTMENT.SALES;
3057                    ascEventData.affiliation = window.asc_datalayer.affiliation;
3058                    ascEventData.items = getMatchingItem($element);
3059                } else if ($element.hasClass("call-us-now-phone")) {
3060                    // handles phone number click at bottom of I'm Interested form
3061                    ascEventData.event = EVENT.ASC_CLICK_TO_CALL;
3062                    ascEventData.comm_phone_number =
3063                        normalizeCommPhoneFromClick($element);
3064                    ascEventData.element_text = commPhoneElementText(
3065                        $element,
3066                        ascEventData.comm_phone_number
3067                    );
3068                    ascEventData.department = DEPARTMENT.SALES;
3069                    ascEventData.affiliation = window.asc_datalayer.affiliation;
3070                    ascEventData.items = getMatchingItem($element);
3071                } else if (
3072                    $element.hasClass("advanced-pricing-actions__phone-link")
3073                ) {
3074                    // handles phone number click directly on VDP
3075                    ascEventData.event = EVENT.ASC_CLICK_TO_CALL;
3076                    ascEventData.event_action = EVENT_ACTION.CLICK;
3077                    ascEventData.comm_phone_number =
3078                        normalizeCommPhoneFromClick($element);
3079                    ascEventData.element_text = commPhoneElementText(
3080                        $element,
3081                        ascEventData.comm_phone_number
3082                    );
3083                    ascEventData.department = DEPARTMENT.SALES;
3084                    ascEventData.affiliation = window.asc_datalayer.affiliation;
3085                    ascEventData.items = getMatchingItem($element);
3086                } else if ($element.hasClass("photo-gallery__open-image")) {
3087                    // handles open click from photo gallery
3088                    ascEventData.event = EVENT.ASC_MEDIA_INTERACTION;
3089                    ascEventData.media_type = MEDIA_TYPE.IMAGE;
3090                    ascEventData.link_url = currentTarget.href;
3091                    ascEventData.items = getMatchingItem($element);
3092                } else if (
3093                    $element.hasClass("wts-vehicle-carouse__lite-item-link")
3094                ) {
3095                    // handles vehicle click from vehicle carousel: either recently viewed or similar vehicles
3096                    ascEventData.event = EVENT.ASC_MEDIA_INTERACTION;
3097                    ascEventData.element_text = $element.attr("aria-label");
3098                    ascEventData.media_type = MEDIA_TYPE.IMAGE;
3099                    ascEventData.link_url = $element[0].href;
3100                    ascEventData.items = getMatchingItem($element);
3101                } else if ($element.hasClass("phone-link")) {
3102                    // handles generic phone number click
3103                    ascEventData.event = EVENT.ASC_CLICK_TO_CALL;
3104                    ascEventData.comm_phone_number =
3105                        normalizeCommPhoneFromClick($element);
3106                    ascEventData.element_text = commPhoneElementText(
3107                        $element,
3108                        ascEventData.comm_phone_number
3109                    );
3110                    ascEventData.department = DEPARTMENT.SALES;
3111                    ascEventData.affiliation = $element
3112                        .parent()
3113                        .siblings(".crestline-logo")
3114                        ? "crestline"
3115                        : window.asc_datalayer.affiliation;
3116                } else if ($element.hasClass("return-home-link")) {
3117                    // handles the return home link above the nav bar
3118                    ascEventData.items = getMatchingItem($element);
3119                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3120                    ascEventData.link_url = $element[0].href;
3121                } else if ($element[0].href.endsWith("/VanBuilder")) {
3122                    // handles van builder click
3123                    ascEventData.event = EVENT.ASC_MEDIA_INTERACTION;
3124                    ascEventData.media_type = MEDIA_TYPE.IMAGE;
3125                    ascEventData.link_url = $element[0].href;
3126                } else if (
3127                    $element.hasClass("additional-resources__dealer-video-link")
3128                ) {
3129                    // handles VDP view dealer video click
3130                    ascEventData.element_text = $element[0].innerText;
3131                    ascEventData.element_type = ELEMENT_TYPE.VIDEO;
3132                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3133                    ascEventData.link_url = "";
3134                } else if ($element.hasClass("resource")) {
3135                    // handles VDP view window sticker click
3136                    ascEventData.element_text =
3137                        $element.children(".resource-text")[0].innerText;
3138                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3139                    ascEventData.link_url = $element[0].href;
3140                } else if ($element.hasClass("dynamic-link")) {
3141                    // handles VDP dynamic link clicks
3142                    ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3143                    ascEventData.element_text = $element[0].textContent;
3144                    ascEventData.event_action_result =
3145                        EVENT_ACTION_RESULT.SEARCH;
3146                    ascEventData.link_url = $element[0].href;
3147                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3148                } else if ($element.hasClass("toggle-link")) {
3149                    ascEventData.element_text = $element[0].innerText;
3150                    ascEventData.element_type = ELEMENT_TYPE.ITEM_DETAILS;
3151                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3152                    ascEventData.link_url = "";
3153                } else if (
3154                    $element.attr("id") === "view-vehicle-options-link"
3155                ) {
3156                    ascEventData.element_text = $element[0].innerText;
3157                    ascEventData.element_type = ELEMENT_TYPE.ITEM_DETAILS;
3158                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3159                    ascEventData.link_url = "";
3160                } else if ($element.parent(".social").length) {
3161                    // handles social link clicks in footer
3162                    ascEventData.element_type = ELEMENT_TYPE.FOOTER;
3163                    ascEventData.element_text = $element.attr("aria-label");
3164                    ascEventData.event_action_result = EVENT_ACTION_RESULT.OPEN;
3165                    ascEventData.link_url = $element[0].href;
3166                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3167                } else if ($element.hasClass("ez-order-button--trigger")) {
3168                    // handles EZOrder modal launch events when the trigger is an 'a' tag
3169                    ascEventData.element_type = ELEMENT_TYPE.POPUP;
3170                    ascEventData.event_action_result = EVENT_ACTION_RESULT.OPEN;
3171                    ascEventData.link_url = $element[0].href;
3172                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3173                } else if ($element.hasClass("back-top")) {
3174                    // handles back to top click
3175                    ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3176                    ascEventData.element_text = $element[0].textContent.trim();
3177                    ascEventData.event_action_result =
3178                        EVENT_ACTION_RESULT.SEARCH;
3179                    ascEventData.link_url = $element[0].href;
3180                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3181                } else if ($element.hasClass("learn-more-link")) {
3182                    // handle Crestline button click
3183                    ascEventData.element_type =
3184                        ELEMENT_TYPE.DIGITAL_RETAILING_TOOL;
3185                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3186                    ascEventData.link_url = $element[0].href;
3187                } else if ($element.parent(".definition-primary-nav").length) {
3188                    // handles definitions A-Z nav bar click
3189                    ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3190                    ascEventData.element_text = $element[0].textContent.trim();
3191                    ascEventData.event_action_result =
3192                        EVENT_ACTION_RESULT.SEARCH;
3193                    ascEventData.link_url = $element[0].href;
3194                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3195                } else if (
3196                    $element.parents(".footer-static-links__list").length
3197                ) {
3198                    // handles static footer links click
3199                    ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3200                    ascEventData.element_text = $element[0].textContent.trim();
3201                    ascEventData.event_action_result =
3202                        EVENT_ACTION_RESULT.SEARCH;
3203                    ascEventData.link_url = $element[0].href;
3204                    ascEventData.element_subtype = ELEMENT_SUBTYPE.HYPERLINK;
3205                } else if ($element.hasClass("share-email")) {
3206                    ascEventData.element_text = $element[0].textContent.trim();
3207                    ascEventData.element_title =
3208                        $element.data("asc-element-title");
3209                } else {
3210                    // check if it is a link in a custom header (typically a "Back to somewhere.com" link)
3211                    let parentDiv;
3212
3213                    if (
3214                        $element.parents(".custom-header").length ||
3215                        $element.parents(".custom-secondary-header").length ||
3216                        $element.parents(".header-info").length
3217                    ) {
3218                        parentDiv =
3219                            $element.parents(".custom-header")[0] ||
3220                            $element.parents(".custom-secondary-header")[0] ||
3221                            $element.parents(".header-info")[0];
3222                    }
3223
3224                    if (parentDiv) {
3225                        ascEventData.element_type = ELEMENT_TYPE.HEADER;
3226                        ascEventData.element_text =
3227                            modules.utilities.getInnerText($element);
3228                        ascEventData.event_action_result =
3229                            EVENT_ACTION_RESULT.EXIT;
3230                        ascEventData.link_url = currentTarget.href;
3231                    } else {
3232                        // presently an unhandled event so return which means don't fire an event
3233                        return;
3234                    }
3235                }
3236            } else if ($element.hasClass("facet-range__input")) {
3237                var facetGroupText = (
3238                    $element
3239                        .parents(".facet-filters")
3240                        .first()
3241                        .children("h4")[0] ||
3242                    $element
3243                        .parents(".facets-theme.facet-filters")
3244                        .children(".facet-filters__heading")[0]
3245                ).innerText.trim();
3246
3247                // handle facet range changes (i.e. Year, Cab to Axle, GVRW)
3248                ascEventData.event = EVENT.ASC_ELEMENT_CONFIGURATION;
3249                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3250                ascEventData.event_action = EVENT_ACTION.FIELD_INPUT;
3251                ascEventData.element_text = $element.val();
3252                ascEventData.element_title = facetGroupText;
3253                ascEventData.element_value = ascEventData.element_text;
3254                ascEventData.element_state = ascEventData.element_text
3255                    ? ELEMENT_STATE.ACTIVE
3256                    : ELEMENT_STATE.INACTIVE;
3257                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3258                ascEventData.element_subtype = $element.parent(
3259                    "div.facet-range__control.combobox"
3260                ).length
3261                    ? ELEMENT_SUBTYPE.DROPDOWN
3262                    : ELEMENT_SUBTYPE.INPUT_FIELD;
3263            } else if ($element.hasClass("facet")) {
3264                // Find the closest parent container for the facet group.
3265                var $facetGroup = $element.closest(".facet-filters");
3266
3267                // Within that group, find the heading element.
3268                // This looks for either an <h4> or an element with the .facet-filters__heading class.
3269                var $heading = $facetGroup
3270                    .find("h4, .facet-filters__heading")
3271                    .first();
3272
3273                // Get the group's title from the heading's data attribute.
3274                var facetGroupText = $heading.data("asc-element-title");
3275
3276                // Get the selected filter's text.
3277                // It prioritizes the data attribute and falls back to the element's inner text.
3278                var facetText =
3279                    $element.data("asc-element-title") ||
3280                    modules.utilities.getInnerText($element);
3281
3282                var facetCount = window
3283                    .getComputedStyle($element[0], ":after")
3284                    .getPropertyValue("content")
3285                    .replace(/"/g, "")
3286                    .replace(/:/g, "")
3287                    .trim();
3288
3289                // if the pseudo element contains a facet count,
3290                // include it in the asc_element_text value.
3291                if (facetCount) {
3292                    facetCount = facetCount
3293                        .replace(/"/g, "")
3294                        .replace(/:/g, "")
3295                        .trim();
3296                    facetText = `${facetText} (${facetCount})`;
3297                }
3298
3299                // handle normal facet clicks
3300                ascEventData.event = EVENT.ASC_ELEMENT_CONFIGURATION;
3301                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3302                ascEventData.element_state = $element.hasClass("active")
3303                    ? ELEMENT_STATE.ACTIVE
3304                    : ELEMENT_STATE.INACTIVE;
3305                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3306                ascEventData.element_subtype = ELEMENT_SUBTYPE.TOGGLE;
3307                ascEventData.element_title = facetGroupText;
3308                ascEventData.element_text = facetText;
3309                ascEventData.element_value =
3310                    modules.utilities.getInnerText($element);
3311            } else if ($element.hasClass("facet-filters__clear")) {
3312                // handle clear search filters button
3313                ascEventData.element_text = $element[0].innerText.trim();
3314                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3315                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3316                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3317                ascEventData.link_url = null;
3318            } else if ($element.hasClass("facet-filters__heading")) {
3319                // handle facet filter expand/collapse clicks
3320                ascEventData.event = EVENT.ASC_ELEMENT_CONFIGURATION;
3321                ascEventData.event_action_result = $element
3322                    .parent()
3323                    .hasClass("collapsed")
3324                    ? EVENT_ACTION_RESULT.OPEN
3325                    : EVENT_ACTION_RESULT.CLOSE;
3326                ascEventData.event_action = EVENT_ACTION.CLICK;
3327                ascEventData.element_state = ELEMENT_STATE.ACTIVE;
3328                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3329                ascEventData.element_subtype = ELEMENT_SUBTYPE.DROPDOWN;
3330                //ascEventData.element_title = modules.utilities.getInnerText($element);
3331                ascEventData.element_text =
3332                    modules.utilities.getInnerText($element);
3333                ascEventData.element_value =
3334                    modules.utilities.getInnerText($element);
3335            } else if ($element.hasClass("pagination__list-item")) {
3336                // handle inventory grid pagination previous/next clicks
3337                var description;
3338                const eventProperty = $element.data("event");
3339
3340                if (eventProperty === "Go To Page") {
3341                    description = $element.data("value");
3342                } else {
3343                    description =
3344                        eventProperty.indexOf("Next") > -1 ? "Next" : "Prev";
3345                }
3346
3347                ascEventData.element_text = description;
3348                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3349                ascEventData.element_subtype = ELEMENT_SUBTYPE.NAV_BUTTON;
3350                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3351            } else if (
3352                $element.hasClass(
3353                    "wts-vehicle-carousel__navigation-action--left"
3354                )
3355            ) {
3356                // handle similar/recent carousel previous clicks
3357                const descriptionPrefix = modules.utilities
3358                    .getCarouselDescription($element)
3359                    .replaceAll("-", "_");
3360
3361                ascEventData.element_text = `${descriptionPrefix}_previous`;
3362                ascEventData.element_title = `${descriptionPrefix}_previous`;
3363                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3364                ascEventData.element_subtype = ELEMENT_SUBTYPE.NAV_BUTTON;
3365                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3366            } else if (
3367                $element.hasClass(
3368                    "wts-vehicle-carousel__navigation-action--right"
3369                )
3370            ) {
3371                // handle similar/recent carousel next clicks
3372                const descriptionPrefix = modules.utilities
3373                    .getCarouselDescription($element)
3374                    .replaceAll("-", "_");
3375
3376                ascEventData.element_text = `${descriptionPrefix}_next`;
3377                ascEventData.element_title = `${descriptionPrefix}_next`;
3378                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3379                ascEventData.element_subtype = ELEMENT_SUBTYPE.NAV_BUTTON;
3380                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3381            } else if (
3382                $element.hasClass("wts-vehicle-carouse__lite-item-link")
3383            ) {
3384                // handle clicking a vehicle tile within the similar/recent carousels
3385                ascEventData.element_text = $element
3386                    .find(".wts-vehicle-card__image-container")
3387                    .css("url");
3388                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3389                ascEventData.element_subtype = ELEMENT_SUBTYPE.IMAGE;
3390                ascEventData.link_url = $element.attr("href");
3391            } else if ($element.hasClass("truck-actions__interested")) {
3392                // handle I'm Interested clicks from the VSP
3393                ascEventData.element_type = ELEMENT_TYPE.FORM;
3394                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3395                ascEventData.element_text =
3396                    modules.utilities.getInnerText($element);
3397                ascEventData.items = getMatchingItem($element);
3398            } else if ($element.hasClass("im-interested__action")) {
3399                // handle I'm Interested clicks from the VDP
3400                ascEventData.element_text =
3401                    modules.utilities.getInnerText($element);
3402                ascEventData.element_type = ELEMENT_TYPE.POPUP;
3403                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3404                ascEventData.items = getMatchingItem($element);
3405            } else if (
3406                $element.hasClass("featured-photo-gallery__see-all-photos")
3407            ) {
3408                // handle See All Photos click from the VDP
3409                ascEventData.event = EVENT.ASC_MEDIA_INTERACTION;
3410                ascEventData.media_type = MEDIA_TYPE.GALLERY;
3411                ascEventData.items = getMatchingItem($element);
3412                ascEventData.affiliation = window.asc_datalayer.affiliation;
3413            } else if ($element.hasClass("featured-vehicles-button")) {
3414                // handle Featured Vehicles button click
3415                ascEventData.element_text = $element[0].textContent;
3416                ascEventData.element_type = ELEMENT_TYPE.HEADER;
3417                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3418                ascEventData.event_action_result = EVENT_ACTION_RESULT.SHOWN;
3419            } else if (
3420                $element.is(".action-link, .email-button, .call-button")
3421            ) {
3422                // handle email or call action link above vocational carousel
3423                ascEventData.element_text = $element[0].textContent;
3424                ascEventData.element_type = ELEMENT_TYPE.HEADER;
3425                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3426                ascEventData.event_action_result = EVENT_ACTION_RESULT.POPUP;
3427            } else if ($element.is(".support-link")) {
3428                // handle support modal in page footer
3429                ascEventData.element_type = ELEMENT_TYPE.FOOTER;
3430                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3431                ascEventData.event_action_result = EVENT_ACTION_RESULT.POPUP;
3432            } else if (
3433                $element.hasClass("crestline-transportation__get-quote")
3434            ) {
3435                // handle Crestline button click
3436                (ascEventData.element_text =
3437                    modules.utilities.getInnerText($element)),
3438                    (ascEventData.element_type =
3439                        ELEMENT_TYPE.DIGITAL_RETAILING_TOOL);
3440                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3441                ascEventData.event_action_result = EVENT_ACTION_RESULT.POPUP;
3442            } else if ($element.hasClass("crestline__generate-quote")) {
3443                // handle Crestline button click
3444                (ascEventData.element_text =
3445                    modules.utilities.getInnerText($element)),
3446                    (ascEventData.element_type =
3447                        ELEMENT_TYPE.DIGITAL_RETAILING_TOOL);
3448                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3449                ascEventData.event_action_result = EVENT_ACTION_RESULT.POPUP;
3450            } else if ($element.hasClass("tf-gridFinancingLink__button")) {
3451                // handle taycor financing button click
3452                (ascEventData.element_text =
3453                    modules.utilities.getInnerText($element)),
3454                    (ascEventData.element_type =
3455                        ELEMENT_TYPE.DIGITAL_RETAILING_TOOL);
3456                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3457            } else if ($element.parent(".body-type").parent(".item").length) {
3458                // handle Vocational Carousel wireframe click
3459                const parentDiv = $element.parent(".body-type").parent(".item");
3460
3461                ascEventData.event = EVENT.ASC_ELEMENT_CONFIGURATION;
3462                ascEventData.event_action_result = EVENT_ACTION_RESULT.SEARCH;
3463                ascEventData.element_state = parentDiv.hasClass("selected")
3464                    ? ELEMENT_STATE.ACTIVE
3465                    : ELEMENT_STATE.INACTIVE;
3466                ascEventData.element_type = ELEMENT_TYPE.SEARCH_TOOL;
3467                ascEventData.element_subtype = ELEMENT_SUBTYPE.IMAGE;
3468                //ascEventData.element_title = parentDiv.children("h3")[0].innerText;
3469                ascEventData.element_text =
3470                    parentDiv.children("h3")[0].innerText;
3471                ascEventData.element_value =
3472                    parentDiv.children("h3")[0].innerText;
3473            } else if ($element.hasClass("navigation-open-button")) {
3474                ascEventData.event = EVENT.ASC_MENU_INTERACTION;
3475                ascEventData.element_type = ELEMENT_TYPE.MENU;
3476                ascEventData.element_subtype = ELEMENT_SUBTYPE.HAMBURGER_MENU;
3477                ascEventData.element_text = "Open Mobile Menu";
3478                ascEventData.event_action_result = EVENT_ACTION_RESULT.OPEN;
3479            } else if ($element.hasClass("navigation-close-button")) {
3480                ascEventData.event = EVENT.ASC_MENU_INTERACTION;
3481                ascEventData.element_type = ELEMENT_TYPE.MENU;
3482                ascEventData.element_subtype = ELEMENT_SUBTYPE.HAMBURGER_MENU;
3483                ascEventData.element_text = "Close Mobile Menu";
3484                ascEventData.event_action_result = EVENT_ACTION_RESULT.CLOSE;
3485            } else if ($element.is(".ez-order-button--trigger")) {
3486                // handle EZOrder modal button clicks
3487                ascEventData.element_text = $element[0].textContent;
3488                ascEventData.element_type = ELEMENT_TYPE.POPUP;
3489                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3490                ascEventData.event_action_result = EVENT_ACTION_RESULT.POPUP;
3491            } else if ($element.offsetParent("wts-webbuy-buttons")) {
3492                (ascEventData.element_text =
3493                    modules.utilities.getInnerText($element)),
3494                    (ascEventData.element_type =
3495                        ELEMENT_TYPE.DIGITAL_RETAILING_TOOL);
3496                ascEventData.element_subtype = ELEMENT_SUBTYPE.CTA_BUTTON;
3497                ascEventData.event_action_result = EVENT_ACTION_RESULT.POPUP;
3498            } else {
3499                // presently an unhandled event so return which means don't fire an event
3500                return;
3501            }
3502
3503            // Route to specific event if currently set to asc_cta_interaction and properties indicate a better fit
3504            if (ascEventData.event === EVENT.ASC_CTA_INTERACTION) {
3505                if (ascEventData.element_type === ELEMENT_TYPE.MENU) {
3506                    ascEventData.event = EVENT.ASC_MENU_INTERACTION;
3507                } else if (
3508                    ascEventData.element_type === ELEMENT_TYPE.GALLERY ||
3509                    ascEventData.element_type === ELEMENT_TYPE.VIDEO ||
3510                    ascEventData.media_type
3511                ) {
3512                    ascEventData.event = EVENT.ASC_MEDIA_INTERACTION;
3513                } else if (ascEventData.element_type === ELEMENT_TYPE.FORM) {
3514                    ascEventData.event = EVENT.ASC_FORM_ENGAGEMENT;
3515                } else if (
3516                    ascEventData.element_type === ELEMENT_TYPE.CHAT_TOOL ||
3517                    ascEventData.comm_type
3518                ) {
3519                    ascEventData.event = EVENT.ASC_COMM_ENGAGEMENT;
3520                } else if (
3521                    ascEventData.element_state ||
3522                    ascEventData.element_type === ELEMENT_TYPE.PAGINATION ||
3523                    ascEventData.element_type === ELEMENT_TYPE.SEARCH_TOOL
3524                ) {
3525                    ascEventData.event = EVENT.ASC_ELEMENT_CONFIGURATION;
3526                }
3527            }
3528
3529            if (
3530                [
3531                    EVENT.ASC_CTA_INTERACTION,
3532                    EVENT.ASC_CLICK_TO_CALL,
3533                    EVENT.ASC_MENU_INTERACTION,
3534                ].includes(ascEventData.event)
3535            ) {
3536                ascEventData = {
3537                    ...ascEventData,
3538                    ...formatData(ascEventData.items),
3539                };
3540                delete ascEventData.items;
3541            }
3542
3543            trackAscEvent(ascEventData);
3544        },
3545
3546        /**
3547         * Tracks vocational carousel page change events.
3548         * @param {any} event Event data giving context to the vocational carousel's present state.
3549         */
3550        trackVocationalCarouselPageChange: function (event) {
3551            if (!_enableAscTracking) {
3552                return;
3553            }
3554
3555            // ignore carousel events triggered by window resize
3556            if (_isResizing) {
3557                return;
3558            }
3559
3560            const pageChangeEventData = event.detail;
3561            const pageNumber = pageChangeEventData.page.index;
3562            const pageSize = pageChangeEventData.page.size;
3563            const pageCount = pageChangeEventData.page.count;
3564
3565            // determine if the user clicked next or previous arrow
3566            var direction;
3567            if (_vocationalCarouselCurrentPage === 0) {
3568                if (pageNumber === pageSize) {
3569                    direction = "previous";
3570                    _vocationalCarouselCurrentPage = pageSize;
3571                } else {
3572                    direction = "next";
3573                    _vocationalCarouselCurrentPage = pageNumber;
3574                }
3575            } else {
3576                direction =
3577                    _vocationalCarouselCurrentPage <= pageNumber
3578                        ? "next"
3579                        : _vocationalCarouselCurrentPage === pageCount - 1 &&
3580                          pageNumber === 0
3581                        ? "next"
3582                        : "previous";
3583                _vocationalCarouselCurrentPage = pageNumber;
3584            }
3585
3586            var ascEventData = {
3587                event: event.type,
3588                page_type: window.asc_datalayer.page_type,
3589                event_owner: window.asc_datalayer.affiliation,
3590                element_type: ELEMENT_TYPE.SEARCH_TOOL,
3591                element_text: direction,
3592                element_title: direction,
3593                event_action: EVENT_ACTION.CLICK,
3594                event_action_result: EVENT_ACTION_RESULT.SEARCH,
3595                ...formatData(window.asc_datalayer.items),
3596            };
3597
3598            trackAscEvent(ascEventData);
3599        },
3600
3601        /**
3602         * Tracks pagination control change events.
3603         * @param {any} event Event data giving context ot the pagination control that changed.
3604         */
3605        trackPaginationControlChange: function (event) {
3606            if (!_enableAscTracking) {
3607                return;
3608            }
3609
3610            var $element = $(event.detail);
3611
3612            var ascEventData = {
3613                event: event.type,
3614                page_type: window.asc_datalayer.page_type,
3615                event_owner: window.asc_datalayer.affiliation,
3616                event_action_result: EVENT_ACTION_RESULT.SEARCH,
3617                ...formatData(window.asc_datalayer.items),
3618            };
3619
3620            // determine if the control is located in the top or bottom nav as well as which control is being interacted with
3621            const $parentElement = $element.parents(".pagination-top");
3622            const location = $parentElement.length ? "header" : "footer";
3623
3624            ascEventData.element_type = location;
3625            ascEventData.event_action = EVENT_ACTION.CLICK;
3626            ascEventData.element_text =
3627                $element[0].selectedOptions[0].innerText;
3628            ascEventData.element_value = $element[0].selectedOptions[0].value;
3629            ascEventData.element_title = $element.is("select")
3630                ? $($element[0].selectedOptions[0]).data("asc-element-title")
3631                : $element.data("asc-element-title");
3632            ascEventData.element_position = `${
3633                location === "header" ? "top" : "bottom"
3634            }_${
3635                $element.hasClass("items-per-page__input") ? "center" : "right"
3636            }`;
3637            ascEventData.element_state = $element.disabled
3638                ? "inactive"
3639                : "active";
3640            ascEventData.element_subtype = ELEMENT_SUBTYPE.DROPDOWN;
3641
3642            trackAscEvent(ascEventData);
3643        },
3644
3645        /**
3646         * Sets up the _formInteractions static member with the ID of the form being edited.
3647         * @param {any} event Event data giving context to the element within the form that was focused.
3648         */
3649        initializeFormStart: function (event) {
3650            if (!_enableAscTracking) {
3651                return;
3652            }
3653
3654            const $element = $(event.detail.currentTarget);
3655            const $customForm = $element.closest(".wts-custom-forms");
3656
3657            if ($customForm.length) {
3658                const formKey =
3659                    $customForm.attr("id") ||
3660                    "custom-form-" + $(".wts-custom-forms").index($customForm);
3661
3662                if (_customFormInteractions.has(formKey)) {
3663                    return;
3664                }
3665
3666                _customFormInteractions.set(formKey, new Map());
3667
3668                if (modules.AscDelegatedTracking) {
3669                    const ctx =
3670                        modules.AscDelegatedTracking.getFormContext($customForm);
3671                    const fieldTitle =
3672                        modules.AscDelegatedTracking.getFormFieldElementTitle(
3673                            $element
3674                        );
3675                    const fieldText =
3676                        modules.AscDelegatedTracking.getFormFieldElementText(
3677                            $element
3678                        );
3679                    const fieldValue =
3680                        modules.AscDelegatedTracking.getFormFieldElementValue(
3681                            $element
3682                        );
3683
3684                    trackAscEvent({
3685                        event: EVENT.ASC_FORM_ENGAGEMENT,
3686                        event_owner: window.asc_datalayer.affiliation,
3687                        page_type: ctx.page_type,
3688                        comm_type: COMM_TYPE.FORM,
3689                        comm_status: COMM_STATUS.START,
3690                        element_text: fieldText,
3691                        element_title: fieldTitle,
3692                        element_value: fieldValue,
3693                        department: ctx.department || DEPARTMENT.SALES,
3694                        form_name: ctx.form_name,
3695                        form_type: ctx.form_type,
3696                    });
3697                }
3698
3699                return;
3700            }
3701
3702            const parentFormID = $element
3703                .parents(".im-interested-form")
3704                .attr("id");
3705
3706            if (parentFormID) {
3707                // I'm Interested form
3708                const formInteraction = _formInteractions.get(parentFormID);
3709
3710                if (formInteraction) {
3711                    // don't fire duplicate form start events
3712                    return;
3713                }
3714
3715                _formInteractions.set(parentFormID, new Map());
3716            } else {
3717                // other contact forms (email, phone)
3718            }
3719        },
3720
3721        /**
3722         * Tracks user engagement with a form - when a form's input changes.
3723         * @param {any} event Event data giving context to the element within the form that was changed.
3724         */
3725        trackFormEngage: function (event) {
3726            if (!_enableAscTracking) {
3727                return;
3728            }
3729
3730            const $element = $(event.detail.currentTarget);
3731            const $customForm = $element.closest(".wts-custom-forms");
3732            const hiddenInventoryID = $(".hidden-inventory-id").val();
3733            const elementIdentifier =
3734                $element.attr("name") || $element.attr("id");
3735            let elementText = "";
3736            let elementTitle = $element.data("asc-element-title") || "";
3737            let elementValue = "";
3738            let foundItems = [
3739                window.asc_datalayer.items.find(
3740                    (i) => i.item_inventory_id === hiddenInventoryID
3741                ),
3742            ];
3743
3744            var customFormPageType = window.asc_datalayer.page_type;
3745            var customFormName = "";
3746            var customFormType = FORM_TYPE.CONSUMER_CONTACT;
3747            var customFormDepartment = DEPARTMENT.SALES;
3748
3749            if (modules.AscDelegatedTracking) {
3750                elementTitle =
3751                    modules.AscDelegatedTracking.getFormFieldElementTitle(
3752                        $element
3753                    ) ||
3754                    elementTitle ||
3755                    "";
3756                elementText =
3757                    modules.AscDelegatedTracking.getFormFieldElementText(
3758                        $element
3759                    );
3760                elementValue =
3761                    modules.AscDelegatedTracking.getFormFieldElementValue(
3762                        $element
3763                    );
3764            }
3765
3766            if ($customForm.length && modules.AscDelegatedTracking) {
3767                const customCtx =
3768                    modules.AscDelegatedTracking.getFormContext($customForm);
3769                customFormPageType = customCtx.page_type;
3770                customFormName = customCtx.form_name;
3771                customFormType = customCtx.form_type;
3772                customFormDepartment = customCtx.department || DEPARTMENT.SALES;
3773            }
3774
3775            const ascEventData = {
3776                // Required
3777                event: event.type,
3778                event_owner: window.asc_datalayer.affiliation,
3779                page_type: customFormPageType,
3780                comm_type: COMM_TYPE.FORM,
3781                comm_status: event.d
3781etail.commStatus || COMM_STATUS.ENGAGE,
3782                element_text: elementText || "",
3783                element_title: elementTitle,
3784                element_value: elementValue,
3785                department: customFormDepartment,
3786                form_name:
3787                    customFormName ||
3788                    event.detail.formName ||
3789                    $element
3790                        .parents("section")
3791                        .siblings(".modal-heading")[0]
3792                        ?.innerText.trim() ||
3793                    ($element.attr("id") &&
3794                    $element.attr("id").startsWith("ez-order")
3795                        ? "EZ Order"
3796                        : "") ||
3797                    ($element.attr("id") &&
3798                    $element.attr("id").startsWith("NoResultsForm")
3799                        ? "No Results"
3800                        : ""),
3801                form_type: $customForm.length
3802                    ? customFormType
3803                    : FORM_TYPE.CONSUMER_CONTACT,
3804                // Optional
3805                ...formatData(foundItems),
3806            };
3807
3808            // Customize for callback form start
3809            if (
3810                event.detail.formName === "Callback Form" ||
3811                event.detail.eventName === "Request a Callback"
3812            ) {
3813                ascEventData.element_text = "Request a Callback";
3814                ascEventData.element_title =
3815                    $element.data("asc-element-title") || "";
3816                ascEventData.form_name = "Callback Form";
3817                ascEventData.form_type = FORM_TYPE.CONSUMER_CONTACT;
3818                ascEventData.department = DEPARTMENT.SALES;
3819                ascEventData.comm_status = COMM_STATUS.START; // Ensure start for callback reveal
3820            }
3821
3822            const parentFormID = $element
3823                .parents(".im-interested-form")
3824                .attr("id");
3825
3826            if (parentFormID) {
3827                const formInteraction = _formInteractions.get(parentFormID);
3828
3829                if (!formInteraction) {
3830                    return;
3831                }
3832
3833                var elementInteraction = formInteraction.get(elementIdentifier);
3834
3835                if (elementInteraction) {
3836                    // don't duplicate events for the same control
3837                    return;
3838                }
3839
3840                formInteraction.set(elementIdentifier, 1);
3841            }
3842
3843            if ($customForm.length) {
3844                const formKey =
3845                    $customForm.attr("id") ||
3846                    "custom-form-" +
3847                        $(".wts-custom-forms").index($customForm);
3848                const formInteraction = _customFormInteractions.get(formKey);
3849
3850                if (!formInteraction) {
3851                    return;
3852                }
3853
3854                if (formInteraction.get(elementIdentifier)) {
3855                    return;
3856                }
3857
3858                formInteraction.set(elementIdentifier, 1);
3859            }
3860
3861            trackAscEvent(ascEventData);
3862        },
3863
3864        /**
3865         * Tracks a user submitting a form.
3866         * @param {any} event Event data giving context to the form that was submitted.
3867         */
3868        trackFormSubmission: function (event) {
3869            if (!_enableAscTracking) {
3870                return;
3871            }
3872
3873            let foundItems = [
3874                window.asc_datalayer.items.find(
3875                    (i) => i.item_inventory_id === event.detail.inventoryID
3876                ),
3877            ];
3878
3879            var submissionPageType = window.asc_datalayer.page_type;
3880            var submissionFormName = event.detail.messageType;
3881            var submissionFormType = FORM_TYPE.CONSUMER_CONTACT;
3882            var submissionDepartment = DEPARTMENT.SALES;
3883
3884            if (event.detail.formElement && modules.AscDelegatedTracking) {
3885                const ctx = modules.AscDelegatedTracking.getFormContext(
3886                    event.detail.formElement
3887                );
3888                submissionPageType = ctx.page_type;
3889                submissionFormName = ctx.form_name || submissionFormName;
3890                submissionFormType = ctx.form_type;
3891                submissionDepartment = ctx.department || DEPARTMENT.SALES;
3892            }
3893
3894            const ascEventData = {
3895                event: event.type,
3896                event_owner: window.asc_datalayer.affiliation,
3897                page_type: submissionPageType,
3898                comm_type: COMM_TYPE.FORM,
3899                comm_outcome: COMM_OUTCOME.OPPORTUNITY,
3900                submission_id: event.detail.uniqueID,
3901                element_text: submissionFormName,
3902                department: submissionDepartment,
3903                form_name: submissionFormName,
3904                form_type: submissionFormType,
3905                ...formatData(foundItems),
3906            };
3907
3908            trackAscEvent(ascEventData);
3909
3910            if (modules.AscDelegatedTracking) {
3911                var secondaryEvent =
3912                    modules.AscDelegatedTracking.resolveSecondarySubmissionEvent(
3913                        submissionDepartment,
3914                        submissionFormType
3915                    );
3916                if (secondaryEvent !== EVENT.ASC_FORM_SUBMISSION) {
3917                    trackAscEvent({
3918                        ...ascEventData,
3919                        event: secondaryEvent,
3920                    });
3921                }
3922            }
3923        },
3924
3925        trackFormClose: function (event) {
3926            if (!_enableAscTracking) {
3927                return;
3928            }
3929
3930            let foundItems = [
3931                window.asc_datalayer.items.find(
3932                    (i) => i.item_inventory_id === event.detail.inventoryID
3933                ),
3934            ];
3935
3936            const ascEventData = {
3937                // Required
3938                event: event.type,
3939                event_owner: window.asc_datalayer.affiliation,
3940                page_type: window.asc_datalayer.page_type,
3941                comm_type: COMM_TYPE.FORM,
3942                comm_status: COMM_STATUS.CLOSE,
3943                comm_outcome: COMM_OUTCOME.NOT_OPPORTUNITY,
3944                submission_id: event?.detail?.uniqueID,
3945                element_text: event.detail.eventName,
3946                department: DEPARTMENT.SALES,
3947                form_name: event.detail.formName,
3948                form_type: FORM_TYPE.CONSUMER_CONTACT,
3949                // Optional
3950                ...formatData(foundItems),
3951            };
3952            trackAscEvent(ascEventData);
3953        },
3954
3955        /**
3956         * Tracks a media interaction click.
3957         * @param {any} event Event data giving context to the interaction click.
3958         */
3959        trackMediaInteractionClick: function (event) {
3960            if (!_enableAscTracking) {
3961                return;
3962            }
3963
3964            let foundItems = [
3965                window.asc_datalayer.items.find(
3966                    (i) => i.item_inventory_id === event.detail.inventoryID
3967                ),
3968            ];
3969
3970            const ascEventData = {
3971                // Required
3972                event: event.type,
3973                event_owner: window.asc_datalayer.affiliation,
3974                page_type: window.asc_datalayer.page_type,
3975                event_action: event.detail.eventAction,
3976                event_action_result: event.detail.eventActionResult,
3977                media_type: event.detail.mediaType,
3978                link_url: event.detail.linkUrl,
3979                // Optional
3980                affilition: window.asc_datalayer.affiliation,
3981                ...formatData(foundItems),
3982            };
3983
3984            //check if ad
3985            $element = $(event.detail.currentTarget);
3986            if (
3987                $element.hasClass("dealership-top-image") ||
3988                $element.hasClass("dealership-ads-left-panel-image")
3989            ) {
3990                ascEventData.media_type = MEDIA_TYPE.IMAGE;
3991                ascEventData.link_url = $element
3992                    .children()
3993                    .filter("a")[0]?.href;
3994                ascEventData.event_action = EVENT_ACTION.CLICK;
3995                ascEventData.event_action_result = EVENT_ACTION_RESULT.OPEN;
3996            }
3997
3998            trackAscEvent(ascEventData);
3999        },
4000
4001        /**
4002         * Tracks a generic interaction click event.
4003         * @param {any} event Event data giving context to the interaction click.
4004         */
4005        trackInteractionClick: function (event) {
4006            if (!_enableAscTracking) {
4007                return;
4008            }
4009
4010            let foundItems = event.detail.inventoryID
4011                ? [
4012                      window.asc_datalayer.items.find(
4013                          (i) =>
4014                              i.item_inventory_id === event.detail.inventoryID
4015                      ),
4016                  ]
4017                : window.asc_datalayer.items;
4018
4019            var ascEventData = {
4020                // Required
4021                event: event.type,
4022                event_owner: window.asc_datalayer.affiliation,
4023                page_type: window.asc_datalayer.page_type,
4024                element_text: event.detail.elementText,
4025                element_title: event.detail.elementTitle,
4026                element_type: event.detail.elementType,
4027                element_subtype: event.detail.elementSubtype,
4028                event_action: event.detail.eventAction,
4029                event_action_result: event.detail.eventActionResult,
4030                ...formatData(foundItems),
4031            };
4032
4033            // Route to specific event if applicable
4034            let eventName = ascEventData.event;
4035            if (
4036                ascEventData.element_type ===
4037                AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.MENU
4038            ) {
4039                eventName = AscTracking.ASC_MAPPINGS.EVENT.ASC_MENU_INTERACTION;
4040            } else if (
4041                ascEventData.element_type ===
4042                    AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY ||
4043                ascEventData.element_type ===
4044                    AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.VIDEO ||
4045                ascEventData.media_type
4046            ) {
4047                eventName =
4048                    AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION;
4049            } else if (
4050                ascEventData.element_type ===
4051                AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.FORM
4052            ) {
4053                eventName = AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT;
4054            } else if (
4055                ascEventData.element_type ===
4056                    AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.CHAT_TOOL ||
4057                ascEventData.comm_type
4058            ) {
4059                eventName = AscTracking.ASC_MAPPINGS.EVENT.ASC_COMM_ENGAGEMENT;
4060            } else if (
4061                ascEventData.element_state ||
4062                ascEventData.element_type ===
4063                    AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.PAGINATION ||
4064                ascEventData.element_type ===
4065                    AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.SEARCH_TOOL
4066            ) {
4067                eventName =
4068                    AscTracking.ASC_MAPPINGS.EVENT.ASC_ELEMENT_CONFIGURATION;
4069            }
4070
4071            ascEventData.event = eventName;
4072
4073            trackAscEvent(ascEventData);
4074        },
4075    };
4076
4077    // "private static" functions:
4078
4079    /**
4080     * Helper method used to actually push event data into window.dataLayer via the gtag function (see _GoogleAnalytics.cshtml).
4081     * @param {any} ascEventData Event data giving context to something that needs to be tracked in GA.
4082     */
4083    trackAscEvent = function (ascEventData) {
4084        if (!_enableAscTracking) {
4085            return;
4086        }
4087
4088        if (_enableDebug) {
4089            validateParams(ascEventData);
4090        }
4091
4092        window._logGA("", ascEventData.event, "", ascEventData);
4093        ascEventData.send_to = _ascMeasurementIDs;
4094        ascEventData = {
4095            ...ascEventData,
4096            ...{ experiment_variant: window.getExperimentVariant() },
4097        };
4098        gtag("event", ascEventData.event, ascEventData);
4099
4100        window.asc_datalayer.events.unshift(ascEventData);
4101    };
4102
4103    validateParams = function (ascEventData) {
4104        if (!_enableAscTracking && !ascEventData) {
4105            return;
4106        }
4107
4108        const requiredParams = REQUIRED_PARAMS[ascEventData.event];
4109        //const optionalParams = OPTIONAL_PARAMS[ascEventData.event];
4110
4111        if (requiredParams?.length) {
4112            const missing = requiredParams.filter(
4113                (key) =>
4114                    ascEventData[key] === undefined ||
4115                    ascEventData[key] === null
4116            );
4117
4118            console.clear();
4119
4120            if (missing.length > 0) {
4121                console.error(
4122                    `${ascEventData.event} is missing required fields: ${missing}`
4123                );
4124            }
4125        }
4126
4127        const formatted = Object.fromEntries(
4128            Object.entries(ascEventData).map(([key, value]) => [
4129                key,
4130                (value === null ||
4131                    value === "" ||
4132                    typeof value === "undefined") &&
4133                requiredParams.includes(key)
4134                    ? "❌ MISSING REQUIRED"
4135                    : Array.isArray(value)
4136                    ? JSON.stringify(value)
4137                    : value,
4138            ])
4139        );
4140
4141        console.table(formatted);
4142    };
4143
4144    formatData = function (data) {
4145        if (!data) return;
4146
4147        const rules = {
4148            item_category: { dedupe: true, sort: true },
4149            item_color: { dedupe: true, sort: true },
4150            item_condition: { dedupe: true, sort: true },
4151            item_fuel_type: { dedupe: true, sort: true },
4152            item_id: { dedupe: false, sort: false },
4153            item_inventory_date: {
4154                dedupe: true,
4155                sort: true,
4156                isDate: true,
4157                range: true,
4158            },
4159            item_make: { dedupe: true, sort: true },
4160            item_model: { dedupe: true, sort: true },
4161            item_number: { dedupe: false, sort: false },
4162            item_payment: { dedupe: true, sort: true },
4163            item_type: { dedupe: true, sort: true },
4164            item_variant: { dedupe: true, sort: true },
4165            item_year: { dedupe: true, sort: true, range: true },
4166            item_price: { dedupe: true, sort: true, range: true },
4167            item_inventory_id: { dedupe: false, sort: false },
4168            item_location: {
4169                dedupe: true,
4170                sort: true,
4171                dedupeCaseInsensitive: true,
4172            },
4173        };
4174
4175        // Step 1: Collect values
4176        const intermediate = {};
4177
4178        for (const obj of data) {
4179            for (const key in obj) {
4180                const value = obj[key];
4181
4182                // Skip nulls and empty strings
4183                if (value === null || value === "") continue;
4184
4185                const { dedupe = true, dedupeCaseInsensitive = false } =
4186                    rules[key] || {};
4187
4188                if (!intermediate[key]) {
4189                    intermediate[key] = dedupe
4190                        ? dedupeCaseInsensitive
4191                            ? { seen: new Set(), values: [] }
4192                            : new Set()
4193                        : [];
4194                }
4195
4196                if (!dedupe) {
4197                    intermediate[key].push(value);
4198                } else if (dedupeCaseInsensitive) {
4199                    const lc =
4200                        typeof value === "string" ? value.toLowerCase() : value;
4201                    if (!intermediate[key].seen.has(lc)) {
4202                        intermediate[key].seen.add(lc);
4203                        intermediate[key].values.push(value);
4204                    }
4205                } else {
4206                    intermediate[key].add(value);
4207                }
4208            }
4209        }
4210
4211        // Step 2: Format output
4212        const condensed = {};
4213
4214        for (const key in intermediate) {
4215            const {
4216                sort = true,
4217                isDate = false,
4218                dedupe = true,
4219                dedupeCaseInsensitive = false,
4220                range = false,
4221            } = rules[key] || {};
4222            let values;
4223
4224            if (!dedupe) {
4225                values = intermediate[key];
4226            } else if (dedupeCaseInsensitive) {
4227                values = intermediate[key].values;
4228            } else {
4229                values = Array.from(intermediate[key]);
4230            }
4231
4232            if (sort) {
4233                values.sort((a, b) => {
4234                    const valA = normalizeDate(a);
4235                    const valB = normalizeDate(b);
4236
4237                    if (isDate && isDateLike(valA) && isDateLike(valB)) {
4238                        return new Date(valA) - new Date(valB);
4239                    }
4240
4241                    if (typeof valA === "number" && typeof valB === "number") {
4242                        return valA - valB;
4243                    }
4244
4245                    return String(valA).localeCompare(String(valB));
4246                });
4247            }
4248
4249            if (range && values.length > 1) {
4250                if (isDate) {
4251                    condensed[key] = `${formatDate(values[0])}-${formatDate(
4252                        values[values.length - 1]
4253                    )}`;
4254                } else {
4255                    condensed[key] = `${values[0]}-${
4256                        values[values.length - 1]
4257                    }`;
4258                }
4259            } else {
4260                condensed[key] = values.join("|");
4261            }
4262        }
4263
4264        return condensed;
4265    };
4266
4267    // Helpers
4268    normalizeDate = function (value) {
4269        return typeof value === "string" ? value.replace(/\//g, "-") : value;
4270    };
4271
4272    isDateLike = function (value) {
4273        return (
4274            typeof value === "string" &&
4275            !isNaN(Date.parse(normalizeDate(value)))
4276        );
4277    };
4278
4279    formatDate = function (value) {
4280        const date = new Date(normalizeDate(value));
4281        const yyyy = date.getFullYear();
4282        const mm = String(date.getMonth() + 1).padStart(2, "0"); // months are 0-based
4283        const dd = String(date.getDate()).padStart(2, "0");
4284        return `${yyyy}/${mm}/${dd}`;
4285    };
4286
4287    // exports
4288    modules.AscTracking = AscTracking;
4289    modules.AscTracking.ASC_MAPPINGS = ASC_MAPPINGS;
4290})(window.wts, window.wts.modules);
4291;
4292(function (wts, modules) {
4293    var AscTracking = modules.AscTracking;
4294    var ASC = AscTracking.ASC_MAPPINGS;
4295
4296    var ATTR_TO_PARAM = {
4297        "page-type": "page_type",
4298        type: "element_type",
4299        title: "element_title",
4300        subtype: "element_subtype",
4301        color: "element_color",
4302        "form-name": "form_name",
4303        "form-type": "form_type",
4304        department: "department",
4305        "promotion-name": "promotion_name",
4306        "element-title": "element_title",
4307    };
4308
4309    var PII_FIELD_NAMES = {
4310        FirstName: true,
4311        LastName: true,
4312        Lastname: true,
4313        Email: true,
4314        EmailAddress: true,
4315        Phone: true,
4316        MobilePhone: true,
4317        OfficePhone: true,
4318    };
4319
4320    function findAscTarget(el) {
4321        if (!el) {
4322            return null;
4323        }
4324        var node = el.nodeType === 1 ? el : el.parentElement;
4325        while (node && node !== document.body) {
4326            if (node.hasAttribute && node.hasAttribute("data-track-asc")) {
4327                return node;
4328            }
4329            node = node.parentElement;
4330        }
4331        return null;
4332    }
4333
4334    function normalizePageType(pageType) {
4335        if (pageType === "custom_page") {
4336            return "custom";
4337        }
4338        return pageType;
4339    }
4340
4341    function resolvePageType(el) {
4342        var node = el && (el.no
4342deType === 1 ? el : el.parentElement);
4343        while (node && node !== document.body) {
4344            if (node.hasAttribute && node.hasAttribute("data-asc-page-type")) {
4345                return normalizePageType(node.getAttribute("data-asc-page-type"));
4346            }
4347            node = node.parentElement;
4348        }
4349        return normalizePageType(
4350            window.asc_datalayer && window.asc_datalayer.page_type
4351        );
4352    }
4353
4354    function kebabToParam(key) {
4355        if (ATTR_TO_PARAM[key]) {
4356            return ATTR_TO_PARAM[key];
4357        }
4358        return key.replace(/-/g, "_");
4359    }
4360
4361    function collectDataAscAttributes(el) {
4362        var $el = $(el);
4363        var params = {};
4364
4365        var title =
4366            $el.attr("data-asc-title") ||
4367            $el.attr("data-asc-element-title") ||
4368            $el.data("ascElementTitle");
4369        if (title) {
4370            params.element_title = String(title).trim();
4371        }
4372
4373        if (!el.attributes) {
4374            return params;
4375        }
4376
4377        for (var i = 0; i < el.attributes.length; i++) {
4378            var attr = el.attributes[i];
4379            if (attr.name.indexOf("data-asc-") !== 0) {
4380                continue;
4381            }
4382            var key = attr.name.slice("data-asc-".length);
4383            if (key === "page-type") {
4384                continue;
4385            }
4386            var param = kebabToParam(key);
4387            if (attr.value !== undefined && attr.value !== "") {
4388                params[param] = attr.value;
4389            }
4390        }
4391
4392        return params;
4393    }
4394
4395    function getElementText($el) {
4396        var aria = $el.attr("aria-label");
4397        if (aria && String(aria).trim()) {
4398            return String(aria).trim();
4399        }
4400        if ($el.is("input[type=submit], input[type=button], button")) {
4401            return ($el.val() || $el.text() || "").trim();
4402        }
4403        if ($el.is("img")) {
4404            return ($el.attr("alt") || "").trim();
4405        }
4406        if ($el.is("a")) {
4407            return modules.utilities.getAggregateText($el) || "";
4408        }
4409        return modules.utilities.getInnerText($el) || "";
4410    }
4411
4412    function normalizeCommPhone($element) {
4413        var hrefRaw = ($element.attr("href") || "").replace(/^tel:/i, "").trim();
4414        var fromHref = modules.utilities.phoneToE164(hrefRaw);
4415        if (fromHref) {
4416            return fromHref;
4417        }
4418        var dataComm = $element.attr("data-comm-phone-number");
4419        if (dataComm) {
4420            var fromData = modules.utilities.phoneToE164(dataComm);
4421            if (fromData) {
4422                return fromData;
4423            }
4424        }
4425        var text = modules.utilities.getInnerText($element);
4426        return modules.utilities.phoneToE164(text) || text || "";
4427    }
4428
4429    function isTelElement(el) {
4430        var href = (el && el.href) || $(el).attr("href") || "";
4431        return /^tel:/i.test(href);
4432    }
4433
4434    function resolveDecoratedLinkUrl($el, clickTarget) {
4435        if ($el[0] && $el[0].href) {
4436            return $el[0].href;
4437        }
4438        if (clickTarget && clickTarget.closest) {
4439            var anchor = clickTarget.closest("a");
4440            if (anchor && anchor.href) {
4441                return anchor.href;
4442            }
4443        }
4444        if (clickTarget && clickTarget.href) {
4445            return clickTarget.href;
4446        }
4447        return null;
4448    }
4449
4450    function routeDecoratedEvent(ascEventData) {
4451        if (ascEventData.event !== ASC.EVENT.ASC_CTA_INTERACTION) {
4452            return ascEventData;
4453        }
4454        if (ascEventData.element_type === ASC.ELEMENT_TYPE.MENU) {
4455            ascEventData.event = ASC.EVENT.ASC_MENU_INTERACTION;
4456        } else if (
4457            ascEventData.element_type === ASC.ELEMENT_TYPE.GALLERY ||
4458            ascEventData.element_type === ASC.ELEMENT_TYPE.VIDEO ||
4459            ascEventData.media_type
4460        ) {
4461            ascEventData.event = ASC.EVENT.ASC_MEDIA_INTERACTION;
4462        } else if (ascEventData.element_type === ASC.ELEMENT_TYPE.FORM) {
4463            ascEventData.event = ASC.EVENT.ASC_FORM_ENGAGEMENT;
4464        } else if (
4465            ascEventData.element_type === ASC.ELEMENT_TYPE.CHAT_TOOL ||
4466            ascEventData.comm_type
4467        ) {
4468            ascEventData.event = ASC.EVENT.ASC_COMM_ENGAGEMENT;
4469        } else if (
4470            ascEventData.element_state ||
4471            ascEventData.element_type === ASC.ELEMENT_TYPE.PAGINATION ||
4472            ascEventData.element_type === ASC.ELEMENT_TYPE.SEARCH_TOOL
4473        ) {
4474            ascEventData.event = ASC.EVENT.ASC_ELEMENT_CONFIGURATION;
4475        }
4476        return ascEventData;
4477    }
4478
4479    function buildAscPayloadFromElement($el, eventName, clickTarget) {
4480        var attrParams = collectDataAscAttributes($el[0]);
4481        var $click = $(clickTarget || $el[0]);
4482        var explicitSubtype = ($el.attr("data-asc-subtype") || "").trim();
4483
4484        var ascEventData = {
4485            event: eventName,
4486            event_owner: window.asc_datalayer.affiliation,
4487            page_type: resolvePageType($el[0]),
4488            event_action: ASC.EVENT_ACTION.CLICK,
4489            event_action_result: ASC.EVENT_ACTION_RESULT.OPEN,
4490            element_text: getElementText($el),
4491            element_type:
4492                attrParams.element_type || ASC.ELEMENT_TYPE.ITEM_DETAILS,
4493            element_subtype:
4494                explicitSubtype ||
4495                attrParams.element_subtype ||
4496                ASC.ELEMENT_SUBTYPE.CTA_BUTTON,
4497        };
4498
4499        Object.keys(attrParams).forEach(function (key) {
4500            ascEventData[key] = attrParams[key];
4501        });
4502
4503        if (!ascEventData.element_title && ascEventData.element_text) {
4504            ascEventData.element_title = ascEventData.element_text;
4505        }
4506
4507        var href = resolveDecoratedLinkUrl($el, clickTarget);
4508        if (href) {
4509            ascEventData.link_url = href;
4510        }
4511
4512        if (isTelElement(clickTarget || $el[0])) {
4513            ascEventData.event = ASC.EVENT.ASC_CLICK_TO_CALL;
4514            ascEventData.comm_phone_number = normalizeCommPhone($click);
4515            ascEventData.department =
4516                ascEventData.department || ASC.DEPARTMENT.SALES;
4517            if (!ascEventData.element_text && ascEventData.comm_phone_number) {
4518                ascEventData.element_text = ascEventData.comm_phone_number;
4519            }
4520        }
4521
4522        return routeDecoratedEvent(ascEventData);
4523    }
4524
4525    function trackDecoratedClick(currentTarget) {
4526        if (!AscTracking || !window.asc_datalayer) {
4527            return false;
4528        }
4529
4530        var decorated = findAscTarget(currentTarget);
4531        if (!decorated) {
4532            return false;
4533        }
4534
4535        var $el = $(decorated);
4536        var trackAsc = ($el.attr("data-track-asc") || "").trim();
4537        if (!trackAsc) {
4538            return false;
4539        }
4540
4541        var eventNames = trackAsc
4542            .split(",")
4543            .map(function (name) {
4544                return name.trim();
4545            })
4546            .filter(Boolean);
4547
4548        eventNames.forEach(function (eventName) {
4549            var payload = buildAscPayloadFromElement(
4550                $el,
4551                eventName,
4552                currentTarget
4553            );
4554            AscTracking.fireAscEvent(payload);
4555        });
4556
4557        return true;
4558    }
4559
4560    function getFormContext($form) {
4561        var $f = $form instanceof jQuery ? $form : $($form);
4562        return {
4563            form_name:
4564                $f.attr("data-asc-form-name") ||
4565                $f.find(".wts-custom-form-name").val() ||
4566                window.customPageName ||
4567                "",
4568            form_type: $f.attr("data-asc-form-type") || ASC.FORM_TYPE.CUSTOM,
4569            department: $f.attr("data-asc-department") || ASC.DEPARTMENT.SALES,
4570            page_type: resolvePageType($f[0]),
4571        };
4572    }
4573
4574    function getCustomFormFieldLabel($element) {
4575        return getFormFieldVisibleLabel($element);
4576    }
4577
4578    function getFormFieldObjectTitle($element) {
4579        var $el = $element instanceof jQuery ? $element : $($element);
4580        return (
4581            $el.attr("data-asc-title") ||
4582            $el.attr("data-asc-element-title") ||
4583            $el.data("ascElementTitle") ||
4584            ($el.data("label") && String($el.data("label")).trim()) ||
4585            ""
4586        );
4587    }
4588
4589    function getFormFieldVisibleLabel($element) {
4590        var $el = $element instanceof jQuery ? $element : $($element);
4591        var $group = $el.closest(".form-group");
4592        var labelText = $group
4593            .find(".wts-custom-form-label")
4594            .first()
4595            .text()
4596            .replace(/\*/g, "")
4597            .trim();
4598        if (labelText) {
4599            return labelText;
4600        }
4601        var siblingLabel = $el
4602            .siblings("label")
4603            .text()
4604            .replace(/\*/g, "")
4605            .trim();
4606        if (siblingLabel) {
4607            return siblingLabel;
4608        }
4609        return ($el.data("label") && String($el.data("label")).trim()) || "";
4610    }
4611
4612    function isPiiField($el) {
4613        var name = ($el.attr("name") || $el.attr("id") || "").trim();
4614        if (!name) {
4615            return false;
4616        }
4617        if (PII_FIELD_NAMES[name]) {
4618            return true;
4619        }
4620        var lower = name.toLowerCase();
4621        return (
4622            lower.indexOf("email") >= 0 ||
4623            lower.indexOf("phone") >= 0 ||
4624            lower === "name" ||
4625            lower.indexOf("firstname") >= 0 ||
4626            lower.indexOf("lastname") >= 0
4627        );
4628    }
4629
4630    function getFormFieldElementTitle($element) {
4631        return getFormFieldObjectTitle($element);
4632    }
4633
4634    function getFormFieldDisplayValue($element) {
4635        var $el = $element instanceof jQuery ? $element : $($element);
4636        var type = ($el.attr("type") || "").toLowerCase();
4637
4638        if (type === "checkbox") {
4639            return $el.prop("checked") ? "checked" : "unchecked";
4640        }
4641        if (type === "radio") {
4642            if (!$el.prop("checked")) {
4643                return "";
4644            }
4645            return ($el.val() || "checked").trim();
4646        }
4647        if ($el.is("select")) {
4648            var selected = $el.find("option:selected");
4649            return (selected.text() || selected.val() || "").trim();
4650        }
4651        return ($el.val() || "").trim();
4652    }
4653
4654    function getFormFieldElementText($element) {
4655        return getFormFieldVisibleLabel($element);
4656    }
4657
4658    function getFormFieldElementValue($element) {
4659        var $el = $element instanceof jQuery ? $element : $($element);
4660        var displayValue = getFormFieldDisplayValue($el);
4661        if (!displayValue) {
4662            return "";
4663        }
4664        if (isPiiField($el)) {
4665            return "entered";
4666        }
4667        return displayValue;
4668    }
4669
4670    function resolveSecondarySubmissionEvent(department, formType) {
4671        if (formType === ASC.FORM_TYPE.SERVICE_APPOINTMENT) {
4672            return ASC.EVENT.ASC_FORM_SUBMISSION_APPT;
4673        }
4674        if (department === ASC.DEPARTMENT.PARTS || formType === ASC.FORM_TYPE.PARTS) {
4675            return ASC.EVENT.ASC_FORM_SUBMISSION_PARTS;
4676        }
4677        if (department === ASC.DEPARTMENT.SERVICE) {
4678            return ASC.EVENT.ASC_FORM_SUBMISSION_SERVICE;
4679        }
4680        if (department === ASC.DEPARTMENT.SALES) {
4681            return ASC.EVENT.ASC_FORM_SUBMISSION_SALES;
4682        }
4683        if (
4684            formType === ASC.FORM_TYPE.CONSUMER_CONTACT ||
4685            formType === ASC.FORM_TYPE.CUSTOM ||
4686            formType === ASC.FORM_TYPE.QUOTE ||
4687            formType === ASC.FORM_TYPE.OFFER ||
4688            formType === ASC.FORM_TYPE["PRE-ORDER"]
4689        ) {
4690            return ASC.EVENT.ASC_FORM_SUBMISSION_SALES;
4691        }
4692        return ASC.EVENT.ASC_FORM_SUBMISSION_OTHER;
4693    }
4694
4695    modules.AscDelegatedTracking = {
4696        findAscTarget: findAscTarget,
4697        resolvePageType: resolvePageType,
4698        buildAscPayloadFromElement: buildAscPayloadFromElement,
4699        trackDecoratedClick: trackDecoratedClick,
4700        getFormContext: getFormContext,
4701        getCustomFormFieldLabel: getCustomFormFieldLabel,
4702        getFormFieldElementTitle: getFormFieldElementTitle,
4703        getFormFieldDisplayValue: getFormFieldDisplayValue,
4704        getFormFieldElementText: getFormFieldElementText,
4705        getFormFieldElementValue: getFormFieldElementValue,
4706        resolveSecondarySubmissionEvent: resolveSecondarySubmissionEvent,
4707    };
4708})(window.wts, window.wts.modules);
4709;
4710(function (wts, modules) {
4711    // imports
4712    var getClientData = modules.getClientData;
4713    var utilities = modules.utilities;
4714
4715    // initialization of the module
4716    var $adobeAnalyticsSetup = getClientData("#adobe-analytics-setup");
4717
4718    // "private static" members
4719    var _vocationalCarouselCurrentPage = 0;
4720    var _formInteractions = new Map();
4721    // Guard against null setup data (e.g., when Ford Direct Analytics is active)
4722    var _isEnabled = $adobeAnalyticsSetup && $adobeAnalyticsSetup.isEnabled;
4723
4724    // "public static" functions
4725    const AdobeAnalytics = {
4726        /**
4727         * Gets common data that is embedded within the page and other places for interaction clicks.  Used for Adobe Analytics.
4728         * @param event Contains context around the event that was fired.
4729         * @returns An object containing data necessary for tracking interaction clicks or null if the module is disabled.
4730         */
4731        getAdobeInteractionClickEventData: function (event) {
4732            if (!_isEnabled) {
4733                return null;
4734            }
4735
4736            const siteDelimiter = window.location.hostname.indexOf(".");
4737            const site =
4738                siteDelimiter === -1
4739                    ? window.location.hostname
4740                    : window.location.hostname.substring(0, siteDelimiter);
4741
4742            var adobeInteractionClickData = {
4743                interaction: {
4744                    site: site,
4745                    type: event.data.type,
4746                    page:
4747                        event.data.pageName ||
4748                        `shop:${resolveStellantisPageName()}`,
4749                    location: event.data.location,
4750                    description: event.data.description,
4751                },
4752            };
4753
4754            if (!event.data.hideVehicle) {
4755                const marketingData =
4756                    getClientData(
4757                        `#analytics-marketing-data-${event.data.truckID}`
4758                    ) || getClientData("#marketing-events-data");
4759                if (marketingData) {
4760                    adobeInteractionClickData.interaction.description =
4761                        event.data.description.replace(
4762                            "<vehicleModel>",
4763                            marketingData.model
4764                        );
4765                    adobeInteractionClickData.vehicle = {
4766                        make: marketingData.make,
4767                        model: marketingData.model,
4768                        year: marketingData.year,
4769                        trim: marketingData.trim,
4770                        vin: marketingData.vin,
4771                    };
4772
4773                    if (marketingData.vin) {
4774                        adobeInteractionClickData.vehicle.vin =
4775                            marketingData.vin;
4776                    }
4777                } else if (event.data.vin) {
4778                    adobeInteractionClickData.vehicle = {
4779                        vin: event.data.vin,
4780                    };
4781                }
4782            }
4783
4784            return adobeInteractionClickData;
4785        },
4786
4787        /**
4788         * Gets common data necessary for tracking adobe form start click events.
4789         * @param event Contains context around the event that was fired.
4790         * @returns An object containing data necessary for tracking form start clicks or null if the module is disabled.
4791         */
4792        getAdobeFormStartClickEventData: function (event) {
4793            if (!_isEnabled) {
4794                return null;
4795            }
4796
4797            var adobeFormStartClickEventData = {
4798                form: {
4799                    formDescription: event.data.formDescription,
4800                    formType: event.data.formType,
4801                    displayType: event.data.displayType,
4802                    displayFormat: event.data.displayFormat,
4803                },
4804            };
4805
4806            const marketingData =
4807                getClientData(
4808                    `#analytics-marketing-data-${event.data.truckID}`
4809                ) || getClientData("#marketing-events-data");
4810            if (marketingData) {
4811                adobeFormStartClickEventData.vehicle = {
4812                    make: marketingData.make,
4813                    model: marketingData.model,
4814                    year: marketingData.year,
4815                    trim: marketingData.trim,
4816                    vin: marketingData.vin,
4817                };
4818            }
4819
4820            return adobeFormStartClickEventData;
4821        },
4822
4823        /**
4824         * Gets common data necessary for tracking adobe form submit events.
4825         * @param event Contains context around the event that was fired.
4826         * @returns An object containing data necessary for tracking form submit clicks or null if the module is disabled.
4827         */
4828        getAdobeFormSubmitData: function (event) {
4829            if (!_isEnabled) {
4830                return null;
4831            }
4832
4833            var adobeFormSubmitData = {
4834                form: {
4835                    formDescription: event.data.formDescription,
4836                    formType: event.data.formType,
4837                    displayType: event.data.displayType,
4838                    displayFormat: event.data.displayFormat,
4839                    hashedEmail: event.data.hashedEmail,
4840                    leadId: event.data.uniqueID,
4841                },
4842            };
4843
4844            const marketingData =
4845                getClientData(
4846                    `#analytics-marketing-data-${event.data.truckID}`
4847                ) || getClientData("#marketing-events-data");
4848            if (marketingData) {
4849                adobeFormSubmitData.vehicle = {
4850                    make: marketingData.make,
4851                    model: marketingData.model,
4852                    year: marketingData.year,
4853                    trim: marketingData.trim,
4854                    vin: marketingData.vin,
4855                };
4856            }
4857
4858            return adobeFormSubmitData;
4859        },
4860
4861        /**
4862         * Builds an eventData object containing interaction click event data based on information from a General Layout's currentTarget.
4863         * @param {any} currentTarget  The General Layout target that fired the event.
4864         * @returns null if the event isn't handled or the module is disabled OR the built eventData object.
4865         */
4866        getAdobeEventDataFromGeneralLayoutTarget: function (currentTarget) {
4867            if (!_isEnabled) {
4868                return null;
4869            }
4870
4871            var $element = $(currentTarget);
4872            var eventData = {
4873                truckID: null,
4874                type: null,
4875                location: null,
4876                description: null,
4877                pageName: null,
4878            };
4879
4880            if (currentTarget.href) {
4881                // handle all links: i.e. link back to dealer site, links to the VDP, or links in the footer
4882                eventData.truckID = modules.utilities.getTruckID($element);
4883
4884                // test to see if this is a navigation link which is different than the links within the inventory grid
4885                var parentElement = $element.parents(".navigation__link");
4886                if (!parentElement.length) {
4887                    // if not check if the link was present in the footer
4888                    parentElement = $element.parents(".footer-nav").first();
4889                }
4890
4891                if (parentElement && parentElement.attr("class")) {
4892                    // header/footer link
4893                    eventData.type = "nav";
4894                    eventData.location = parentElement.hasClass("footer-nav")
4895                        ? "footer"
4896                        : "header";
4897                    eventData.description = $element
4898                        .text()
4899                        .trim()
4900                        .replace(/\s+/g, "-")
4901                        .toLowerCase();
4902                } else if ($element.hasClass("specifications__see-more")) {
4903                    // see more details link click to open the VDP from VSP
4904                    eventData.type = "cta";
4905                    eventData.location = "vehicle-details";
4906                    eventData.description = "see-more-details-<vehicleModel>";
4907                } else if ($element.hasClass("truck-image")) {
4908                    // truck image click to open the VDP from VSP
4909                    eventData.type = "cta";
4910                    eventData.location = "select-vehicle";
4911                    eventData.description = "truck-image-<vehicleModel>";
4912                } else if ($element.hasClass("truck__link-text")) {
4913                    // truck title click to open the VDP from VSP
4914                    eventData.type = "cta";
4915                    eventData.location = "vehicle-details";
4916                    eventData.description = "truck-title-<vehicleModel>";
4917                } else if ($element.hasClass("truck__phone-link")) {
4918                    // phone number click within the VSP inventory grid item
4919                    eventData.type = "cta";
4920                    eventData.pageName = "landing";
4921                    eventData.location = "select-vehicle";
4922                    eventData.description = "call-phone-number-<vehicleModel>";
4923                } else if ($element.hasClass("call-us-now-phone")) {
4924                    // phone number click at bottom of I'm Interested form
4925                    eventData.truckID = $(".hidden-inventory-id").val();
4926                    eventData.type = "cta";
4927                    eventData.pageName = "landing";
4928                    eventData.location = "select-vehicle";
4929                    eventData.description = "call-us-now-<vehicleModel>";
4930                } else if (
4931                    $element.hasClass("advanced-pricing-actions__phone-link")
4932                ) {
4933                    // phone number click directly on VDP
4934                    eventData.type = "cta";
4935                    eventData.location = "vehicle-container";
4936                    eventData.description = "call-phone-number-<vehicleModel>";
4937                } else if ($element.hasClass("photo-gallery__open-image")) {
4938                    // open in new tab click from photo gallery
4939                    eventData.type = "func";
4940                    eventData.location = "thumbnail-gallery";
4941                    eventData.description = "open-in-new-tab";
4942                } else if (
4943                    $element.hasClass("wts-vehicle-carouse__lite-item-link")
4944                ) {
4945                    eventData.type = "slider";
4946                    eventData.location = "carousel-page-bottom";
4947                    eventData.truckID = $element.data("truckId");
4948
4949                    const descriptionPrefix =
4950                        modules.utilities.getCarouselDescription($element);
4951                    eventData.description = `${descriptionPrefix}-${$element.data(
4952                        "slideNumber"
4953                    )}`;
4954                } else {
4955                    // check if it is a link in a custom header (typically a "Back to somewhere.com" link)
4956                    const parentDiv =
4957                        $element.parents(".custom-header") ||
4958                        $element.parents(".custom-secondary-header");
4959
4960                    if (parentDiv) {
4961                        eventData.type = "nav";
4962                        eventData.location = "topnav";
4963                        eventData.description = $element
4964                            .text()
4965                            .trim()
4966                            .replace(/\s+/g, "-")
4967                            .toLowerCase();
4968                    } else {
4969                        // presently an unhandled event so return null which means don't fire an event
4970                        return null;
4971                    }
4972                }
4973            } else if ($element.hasClass("facet-range__input")) {
4974                // handle facet range changes (i.e. Year, Cab to Axle, GVRW)
4975                eventData.type = "tool";
4976
4977                const $container = $element.closest(".facet-range--container");
4978                eventData.location = $container
4979                    .data("key")
4980                    .trim()
4981                    .replace(/\s+/g, "-")
4982                    .toLowerCase();
4983                eventData.description = `${$container
4984                    .find(".facet-range__input")
4985                    .first()
4986                    .val()}-${$container
4987                    .find(".facet-range__input")
4988                    .last()
4989                    .val()}`;
4990            } else if ($element.hasClass("facet")) {
4991                // handle normal facet clicks
4992                eventData.type = "tool";
4993
4994                // get the title for the facet, trim it, replace any spaces with hyphens, and lowercase that puppy
4995                const headingTitle = $element
4996                    .closest(".facet-filters")
4997                    .find(".collapsible__title")
4998                    .html()
4999                    ?.trim()
5000                    .replace(/\s+/g, "-")
5001                    .toLowerCase();
5002                eventData.location = headingTitle;
5003
5004                eventData.description = `${window.wts.modules.utilities
5005                    .getInnerText($element)
5006                    .toLowerCase()}-${
5007                    $element.hasClass("checked") ? "checked" : "unchecked"
5008                }`;
5009            } else if ($element.hasClass("pagination__list-item")) {
5010                // handle inventory grid pagination previous/next clicks
5011                eventData.type = "func";
5012                eventData.location = "bottomnav";
5013
5014                var description;
5015                const event = $element.data("event");
5016
5017                if (event === "Go To Page") {
5018                    description = `pagination-${$element.data("value")}`;
5019                } else {
5020                    description =
5021                        event.indexOf("Next") > -1
5022                            ? "pagination-next"
5023                            : "pagination-prev";
5024                }
5025
5026                eventData.description = description;
5027            } else if (
5028                $element.hasClass(
5029                    "wts-vehicle-carousel__navigation-action--left"
5030                )
5031            ) {
5032                // handle similar/recent carousel previous clicks
5033                eventData.type = "slider";
5034                eventData.location = "carousel-page-bottom";
5035                eventData.hideVehicle = true;
5036
5037                const descriptionPrefix =
5038                    modules.utilities.getCarouselDescription($element);
5039                eventData.description = `${descriptionPrefix}-previous`;
5040            } else if (
5041                $element.hasClass(
5042                    "wts-vehicle-carousel__navigation-action--right"
5043                )
5044            ) {
5045                // handle similar/recent carousel next clicks
5046                eventData.type = "slider";
5047                eventData.location = "carousel-page-bottom";
5048                eventData.hideVehicle = true;
5049
5050                const descriptionPrefix =
5051                    modules.utilities.getCarouselDescription($element);
5052                eventData.description = `${descriptionPrefix}-next`;
5053            } else if (
5054                $element.hasClass("wts-vehicle-carouse__lite-item-link")
5055            ) {
5056                // handle clicking a vehicle tile within the similar/recent carousels
5057                eventData.type = "slider";
5058                eventData.location = "carousel-page-bottom";
5059                eventData.description = `${modules.utilities.getCarouselDescription(
5060                    $element
5061                )}-tile`;
5062            } else if ($element.hasClass("truck-actions__interested")) {
5063                // handle I'm Interested clicks from the VSP
5064                eventData.truckID = modules.utilities.getTruckID($element);
5065                eventData.type = "cta";
5066                eventData.pageName = "landing";
5067                eventData.location = "select-vehicle";
5068                eventData.description = "contact-your-dealer-<vehicleModel>";
5069            } else if ($element.hasClass("im-interested__action")) {
5070                // handle I'm Interested clicks from the VDP
5071                eventData.type = "cta";
5072                eventData.location = "vehicle-container";
5073                eventData.description = "Interested-<vehicleModel>";
5074            } else if (
5075                $element.hasClass("featured-photo-gallery__see-all-photos")
5076            ) {
5077                // handle See All Photos click from the VDP
5078                eventData.type = "tool";
5079                eventData.location = "vehicle-container";
5080                eventData.description = "gallery-seeAllPhotos";
5081            } else {
5082                // presently an unhandled event so return null which means don't fire an event
5083                return null;
5084            }
5085
5086            return { data: eventData };
5087        },
5088
5089        /**
5090         * Builds an eventData object based on information from currentTarget which is a specific pagination control.
5091         * @param {any} currentTarget The specific pagination control that fired the event.
5092         * @returns An eventData object containing information derived from the pagination control that fired the event or null if the module is disabled.
5093         */
5094        getAdobeEventDataFromPaginationControls: function (currentTarget) {
5095            if (!_isEnabled) {
5096                return null;
5097            }
5098
5099            const $element = $(currentTarget);
5100            var eventData = {
5101                type: "func",
5102                location: "",
5103                description: "",
5104            };
5105
5106            // determine if the control is located in the top or bottom nav as well as which control is being interacted with
5107            const $parentElement = $element.parents(".pagination-top");
5108            const location = $parentElement.length ? "topnav" : "bottomnav";
5109            eventData.location = location;
5110
5111            const selectedText = $element
5112                .find(":selected")
5113                .text()
5114                .replace(/\s+/g, "-")
5115                .replace(/\(/g, "")
5116                .replace(/\)/g, "")
5117                .toLowerCase();
5118            eventData.description = `${
5119                $element.hasClass("items-per-page__input")
5120                    ? "items-per-page"
5121                    : "sort-by"
5122            }-${selectedText}`;
5123
5124            return { data: eventData };
5125        },
5126
5127        /**
5128         * Builds an eventData object based on information from pageChangeEventData.
5129         * @param {any} pageChangeEventData The specific pageChangeEventData to build the eventData from.
5130         * @returns An eventData object containing information derived from the pageChangeEventData or null if the module is disabled.
5131         */
5132        getAdobeEventDataFromVocationalPageChange: function (
5133            pageChangeEventData
5134        ) {
5135            if (!_isEnabled) {
5136                return null;
5137            }
5138
5139            var eventData = {
5140                type: "filter",
5141                location: "main-filter-carousel",
5142                description: "",
5143            };
5144            const pageNumber = pageChangeEventData.page.index;
5145            const pageSize = pageChangeEventData.page.size;
5146            const pageCount = pageChangeEventData.page.count;
5147
5148            // determine if the user clicked next or previous arrow
5149            var direction;
5150            if (_vocationalCarouselCurrentPage === 0) {
5151                if (pageNumber === pageSize) {
5152                    direction = "previous";
5153                    _vocationalCarouselCurrentPage = pageSize;
5154                } else {
5155                    direction = "next";
5156                    _vocationalCarouselCurrentPage = pageNumber;
5157                }
5158            } else {
5159                direction =
5160                    _vocationalCarouselCurrentPage <= pageNumber
5161                        ? "next"
5162                        : _vocationalCarouselCurrentPage === pageCount - 1 &&
5163                          pageNumber === 0
5164                        ? "next"
5165                        : "previous";
5166                _vocationalCarouselCurrentPage = pageNumber;
5167            }
5168
5169            eventData.description = `filter-${pageNumber + 1}:${direction}`;
5170            return { data: eventData };
5171        },
5172
5173        /**
5174         * Builds an eventData object based on form control information derived from currentTarget.
5175         * @param {any} currentTarget The specific element inside a form that fired the event.
5176         * @returns null if the parentFormID cannot be derived or if the form has already been interacted with or the module is disabled.
5177         * An eventData object containing information derived from currentTarget otherwise.
5178         */
5179        getAdobeEventDataFromFormStart: function (currentTarget) {
5180            if (!_isEnabled) {
5181                return null;
5182            }
5183
5184            const $element = $(currentTarget);
5185            const parentFormID = $element
5186                .parents(".im-interested-form")
5187                .attr("id");
5188
5189            if (!parentFormID) {
5190                return null;
5191            }
5192
5193            var formInteraction = _formInteractions.get(parentFormID);
5194            if (formInteraction) {
5195                // the user has already interacted with the form so don't fire an event
5196                return null;
5197            }
5198
5199            formInteraction = new Map();
5200            _formInteractions.set(parentFormID, formInteraction);
5201
5202            var eventData = {
5203                truckID: $(".hidden-inventory-id").val(),
5204                formDescription: "lead",
5205                formType: "ram-wts-form",
5206                displayType: "page",
5207                displayFormat: "in-page",
5208            };
5209            return { data: eventData };
5210        },
5211
5212        /**
5213         * Gets common data that is embedded within the page and other places for page loads.  Used for Adobe Analytics.
5214         * @param event Contains context around the event that was fired.
5215         * @returns An object containing data necessary for tracking page loads or null if the module is disabled.
5216         */
5217        getAdobePageLoadEventData: function (event) {
5218            if (!_isEnabled) {
5219                return null;
5220            }
5221
5222            const userData = getClientData("#user-data");
5223            const location = window.wts.user.location.get();
5224            const pageName = resolveStellantisPageName();
5225            var adobePageLoadData = {
5226                platform: {
5227                    technology: ".NET",
5228                },
5229                user: {
5230                    language: userData.language,
5231                    responsiveState: detectDeviceType(),
5232                    zipCode: location ? location.ZipCode : "",
5233                },
5234                page: {
5235                    name: `shop:${pageName}`,
5236                    type: pageName,
5237                },
5238            };
5239
5240            const marketingEventsData = getClientData("#marketing-events-data");
5241            if (marketingEventsData) {
5242                adobePageLoadData.vehicle = {
5243                    make: marketingEventsData.make,
5244                    model: marketingEventsData.model,
5245                    year: marketingEventsData.year,
5246                    trim: marketingEventsData.trim,
5247                    vin: marketingEventsData.vin,
5248                };
5249            }
5250
5251            const siteData = getClientData("#site-data");
5252            adobePageLoadData.dealer = {
5253                code: siteData.DealerCode,
5254            };
5255
5256            return adobePageLoadData;
5257        },
5258    };
5259
5260    // "private static" functions
5261
5262    /**
5263     * Detects the device type by observing screen width.
5264     * Uses the shared utility from modules.utilities.
5265     * @returns "desktop", "tablet", or "mobile" depending on the screen width of the window.
5266     */
5267    function detectDeviceType() {
5268        return utilities.detectDeviceType();
5269    }
5270
5271    /**
5272     * Attempts to map the pageType from the page-context-data into a value Stellantis expects.
5273     * @returns the resolved pageName
5274     */
5275    function resolveStellantisPageName() {
5276        const pageContextData =
5277            window.wts.modules.getClientData("#page-context-data");
5278        if (!pageContextData) {
5279            return null;
5280        }
5281
5282        switch (pageContextData.pageType) {
5283            case "SearchResults":
5284                return "search-inventory";
5285            case "TruckDetails":
5286                return "vehicle-details";
5287                break;
5288            default:
5289                return "not-handled";
5290        }
5291    }
5292
5293    // exports
5294    modules.AdobeAnalytics = AdobeAnalytics;
5295})(window.wts, window.wts.modules);
5296;
5297/**
5298 * Ford Direct Analytics Module
5299 *
5300 * Implements Ford Direct's Adobe DTM tagging requirements.
5301 * Builds window.digitaldata dynamically for ALL pages when enabled.
5302 *
5303 * This module is mutually exclusive with the existing Adobe Analytics (Stellantis) implementation.
5304 * When FordDirectAnalyticsScriptURL is set, this module handles analytics events.
5305 */
5306(function (wts, modules) {
5307    // imports
5308    var getClientData = modules.getClientData;
5309    var utilities = modules.utilities;
5310
5311    // initialization of module - get setup data from JSON embedded in page
5312    var fordDirectSetup = getClientData("#ford-direct-analytics-setup");
5313    var userData = getClientData("#user-data");
5314
5315    // Check if user is TruckPro or above (staff/admin) - Ford Direct only tracks buyer-facing pages
5316    var isTruckProOrAbove = userData && userData.isTruckProOrAbove;
5317
5318    // Check if FDAF data is complete (required for Ford Direct user object)
5319    var hasFdafData = fordDirectSetup && fordDirectSetup.hasFdafData;
5320
5321    // Disable for TruckPro/Staff/Admin users - only track anonymous buyer-facing pages
5322    // Also disable if FDAF data is incomplete (Salesforce sync may not have completed)
5323    var _isEnabled =
5324        fordDirectSetup && fordDirectSetup.isEnabled && hasFdafData && !isTruckProOrAbove;
5325
5326    // Enable debug logging via ?debug-fd=1 query param
5327    var enableDebug = fordDirectSetup && fordDirectSetup.enableDebug;
5328
5329    // Store last PAGE_LOAD digitaldata and page context key for use in onclick events
5330    var lastPageLoadData = null;
5331    var lastPageContextKey = null;
5332
5333    // Open global-overlay (2c) entries, innermost last. Kept separate from lastPageLoadData so
5334    // form-submit keeps baselining against the real page load while an overlay is open.
5335    // Scoped to the document: navigation tears it down, and closeOverlay pops entries. It is
5336    // deliberately NOT reset in trackPageLoad, which re-fires on the live document for
5337    // VSP_SEARCH_COMPLETE and VDP print and would discard an overlay the user still has open.
5338    var activeOverlayStack = [];
5339
5340    if (isTruckProOrAbove && enableDebug) {
5341        console.log(
5342            "[Ford Direct Analytics] Disabled for TruckPro/Staff users"
5343        );
5344    }
5345
5346    // Log warning if Ford Direct is configured but FDAF data is missing
5347    if (fordDirectSetup && fordDirectSetup.isEnabled && !hasFdafData && !isTruckProOrAbove) {
5348        var missingFields = [];
5349        if (!fordDirectSetup.paCode) missingFields.push("paCode");
5350        if (!fordDirectSetup.fdafCode) missingFields.push("fdafCode");
5351        if (!fordDirectSetup.fdafRegion) missingFields.push("fdafRegion");
5352        console.warn(
5353            "[Ford Direct Analytics] Disabled - missing FDAF data:",
5354            missingFields.join(", "),
5355            "(Salesforce sync may not have completed)"
5356        );
5357    }
5358
5359    // Page context mapping from VehicleDisplayContext to internal context keys
5360    var PAGE_CONTEXT_MAP = {
5361        None: "unknown",
5362        TruckDetails: "VDP",
5363        SearchResults: "VSP",
5364        MyGarage: "MyGarage",
5365        Sitemap: "Sitemap",
5366        About: "About",
5367        CustomOrders: "CustomOrders",
5368        CommercialCatalog: "CommercialCatalog",
5369        // Special contexts passed via event data or detected from URL
5370        Home: "Home",
5371        PrivacyPolicy: "PrivacyPolicy",
5372        Definitions: "Definitions",
5373        VDPPRINT: "VDPPRINT",
5374        CustomPage: "CustomPage",
5375    };
5376
5377    // Page path suffix for pageName format: dc:wts:{pagePath}
5378    // Format from Ford spec - omit nameplate for list views
5379    var PAGE_PATH_MAP = {
5380        Home: "home",
5381        VSP: "si:vls",
5382        VDP: "si:vehicle details", // nameplate appended dynamically for VDP
5383        VDPPRINT: "si:vehicle details:print", // nameplate appended dynamically
5384        MyGarage: "my garage",
5385        Sitemap: "help:sitemap",
5386        About: "about",
5387        CustomOrders: "custom order",
5388        CommercialCatalog: "commercial catalog",
5389        PrivacyPolicy: "help:privacy",
5390        Definitions: "help:definitions",
5391        CustomPage: "custom", // page title appended dynamically
5392    };
5393
5394    // Site section mapping based on page context (from Ford spec)
5395    var SITE_SECTION_MAP = {
5396        Home: "home",
5397        VSP: "search inventory",
5398        VDP: "search inventory",
5399        VDPPRINT: "search inventory",
5400        MyGarage: "my garage",
5401        Sitemap: "help",
5402        About: "about",
5403        CustomOrders: "custom order",
5404        CommercialCatalog: "commercial catalog",
5405        PrivacyPolicy: "help",
5406        Definitions: "help",
5407        CustomPage: "custom",
5408    };
5409
5410    // Page context path mapping for dynamic onclick link names
5411    // Maps internal page context keys to their onclick path prefix per Ford spec
5412    var ONCLICK_PAGE_CONTEXT_MAP = {
5413        Home: "home",
5414        VSP: "si:vls",
5415        VDP: "si:vehicle details",
5416        VDPPRINT: "si:vehicle details",
5417        About: "about",
5418        CustomOrders: "custom order",
5419        CommercialCatalog: "commercial catalog",
5420        MyGarage: "my garage",
5421        Sitemap: "help",
5422        PrivacyPolicy: "help",
5423        Definitions: "help",
5424        CustomPage: "custom",
5425    };
5426
5427    // Dynamic action mapping for page-context-aware onclick link names
5428    // These actions require both page context AND modal context
5429    // Format: wts:{pageContext}:{modalContext}:content:{action}
5430    // The page context is derived from lastPageLoadData at click time
5431    // The modal context is passed from the event source
5432    var DYNAMIC_ACTION_MAP = {
5433        // Call Us Now - requires modal context (call popup, i am interested, etc.)
5434        CALL_US_NOW_IM_INTERESTED: "click to call",
5435    };
5436
5437    // Modal context path segments for Call Us Now events per Ford spec
5438    // The modal type determines the middle segment of the onclick link name
5439    // Quote modal contexts are absent here on purpose - their linknames come from
5440    // QUOTE_MODAL_ONCLICKS because Ford drops the ":content:" segment from c5 for those rows.
5441    var MODAL_CONTEXT_MAP = {
5442        "call-popup": "call popup",
5443        "i-am-interested": "i am interested",
5444        "see-all-photos-i-am-interested": "see all photos:i am interested",
5445    };
5446
5447    // Milestone 3 form-submit: B&G contexts use build-and-get-quote prefix even on VDP
5448    var BUILD_AND_GET_QUOTE_FORM_CONTEXTS = {
5449        "info:select upfit": true,
5450        "request more info": true,
5451        "get a quote for this build": true,
5452    };
5453
5454    var FORM_SUBMIT_PREFIX_BY_PAGE_GROUP = {
5455        VSP: {
5456            pageNamePrefix: "si:vls",
5457            leadPrefix: "vls",
5458            siteSection: "search inventory",
5459            pageType: "search inventory",
5460        },
5461        VDP: {
5462            pageNamePrefix: "si:vehicle details",
5463            leadPrefix: "vehicle details",
5464            siteSection: "search inventory",
5465            pageType: "search inventory",
5466        },
5467        About: {
5468            pageNamePrefix: "about",
5469            leadPrefix: "about",
5470            siteSection: "about",
5471            pageType: "about",
5472        },
5473        BuildAndGetQuote: {
5474            pageNamePrefix: "build and get quote",
5475            leadPrefix: "build and get quote",
5476            siteSection: "build and get quote",
5477            pageType: "build and get quote",
5478        },
5479        CustomOrders: {
5480            pageNamePrefix: "custom order",
5481            leadPrefix: "custom order",
5482            siteSection: "custom order",
5483            pageType: "custom order",
5484        },
5485    };
5486
5487    var FORD_USER_LANGUAGE = "eng";
5488    var FORD_CLIENT = "ford";
5489
5490    // VDP content-action onclick events (Page Variables row 83) use distinct c5 vs pev2 values
5491    var VDP_CONTENT_ACTION_EVENT_KEYS = {
5492        PHOTO_GALLERY_NEXT_PHOTO: true,
5493        PHOTO_GALLERY_PREVIOUS_PHOTO: true,
5494        PHOTO_GALLERY_CLICK_THUMBNAIL: true,
5495        FEATURED_PHOTO_GALLERY_NEXT_PHOTO: true,
5496        FEATURED_PHOTO_GALLERY_PREVIOUS_PHOTO: true,
5497        FEATURED_PHOTO_GALLERY_VIEW_PHOTO: true,
5498        CLICK_FEATURED_PHOTO_THUMB: true,
5499        VEHICLE_DETAILS_TRACK_DEALER_VIDEO_EVENT: true,
5500    };
5501
5502    // Onclick link name mapping for Ford Direct onclick events
5503    // Maps CTA element titles and event types to Ford spec onclickLinkName values
5504    // NOTE: Page-context-aware actions requiring modal context are in DYNAMIC_ACTION_MAP above
5505    var ONCLICK_LINK_NAME_MAP = {
5506        // Footer links (via GENERAL_LAYOUT_INTERACTION_CLICK + CTA element title)
5507        "Truck Pro Login": "wts:help:truck pro login",
5508        FAQs: "wts:help:faqs",
5509        "Learning Portal": "wts:help:faqs",
5510        Definitions: "wts:help:definitions",
5511        // Form submission
5512        SUBMIT_SUPPORT_FORM: "wts:help:support",
5513        // Photo/Video - per spec Row 83: wts:si:vehicle details:{action}
5514        PHOTO_GALLERY_NEXT_PHOTO: "wts:si:vehicle details:photo scroll",
5515        PHOTO_GALLERY_PREVIOUS_PHOTO: "wts:si:vehicle details:photo scroll",
5516        PHOTO_GALLERY_CLICK_THUMBNAIL: "wts:si:vehicle details:photo click",
5517        FEATURED_PHOTO_GALLERY_VIEW_PHOTO: "wts:si:vehicle details:photo click",
5518        VEHICLE_DETAILS_TRACK_DEALER_VIDEO_EVENT: "wts:si:vehicle details:video play",
5519        // Commercial catalog content actions - Page Variables row 77
5520        COMMERCIAL_CATALOG_VIEW_PHOTO: "wts:commercial catalog:photo click",
5521        COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_NEXT:
5522            "wts:commercial catalog:photo scroll",
5523        COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_PREVIOUS:
5524            "wts:commercial catalog:photo scroll",
5525        COMMERCIAL_CATALOG_VIEW_BODY_LINE_VIDEO:
5526            "wts:commercial catalog:video play",
5527    };
5528
5529    // Commercial catalog onclick events sharing the single row 77 linkname.
5530    var CATALOG_CONTENT_ACTION_EVENT_KEYS = {
5531        COMMERCIAL_CATALOG_VIEW_PHOTO: true,
5532        COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_NEXT: true,
5533        COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_PREVIOUS: true,
5534        COMMERCIAL_CATALOG_VIEW_BODY_LINE_VIDEO: true,
5535    };
5536
5537    // Call Us Now inside the Crestline quote forms - Page Variables rows 48 and 50.
5538    // These drop the ":content:" segment from c5 that other modal call flows use.
5539    var QUOTE_MODAL_ONCLICKS = {
5540        "get-quote": {
5541            c5: "wts:si:vehicle details:get quote:click to call",
5542            pev2: "dc:wts:si:vehicle details:get quote:content:action",
5543        },
5544        "get-quote-vehicle-transportation": {
5545            c5: "wts:si:vehicle details:get quote:vehicle transportation quote:click to call",
5546            pev2: "dc:wts:si:vehicle details:get quote:vehicle transportation quote:content:action",
5547        },
5548    };
5549
5550    // window-sticker (4c) and global-download (7c). Both inherit channel and hierarchy from the
5551    // last page load and carry a tool/toolDescriptor pair plus an event action.
5552    var TOOL_EVENTS = {
5553        "window-sticker": {
5554            trackName: "window-sticker",
5555            c5: "wts:si:vehicle details:view window sticker",
5556            pev2: "dc:wts:si:vehicle details:view window sticker",
5557            appendNameplate: true,
5558            tool: "event:window sticker",
5559            toolDescriptor: "vehicle details:window sticker",
5560            eventAction: "window sticker|tool",
5561        },
5562        "download-vehicle-details": {
5563            trackName: "global-download",
5564            c5: "wts:si:vehicle details:brochure download",
5565            pev2: "dc:wts:si:vehicle details:download",
5566            tool: "event:pdf brochure",
5567            toolDescriptor: "vehicle details:pdf brochure",
5568            eventAction: "pdf brochure|tool",
5569        },
5570        "download-build-and-get-quote": {
5571            trackName: "global-download",
5572            c5: "wts:build and get quote:specs download",
5573            pev2: "dc:wts:build and get quote:download",
5574            tool: "event:pdf brochure",
5575            toolDescriptor: "build and get quote:pdf brochure",
5576            eventAction: "pdf brochure|tool",
5577        },
5578        "download-commercial-catalog": {
5579            trackName: "global-download",
5580            c5: "wts:commercial catalog:brochure download",
5581            pev2: "dc:wts:commercial catalog:download",
5582            tool: "event:pdf brochure",
5583            toolDescriptor: "commercial catalog:pdf brochure",
5584            eventAction: "pdf brochure|tool",
5585        },
5586    };
5587
5588    // pev2 (onclickLinkName) where Ford spec requires a value distinct from c5.
5589    // Truck Pro Login is "dc:help:..." with no "wts" segment - Page Variables row 9 and the
5590    // 07/23 issue sheet agree on that, so it is not a typo.
5591    var ONCLICK_PEV2_MAP = {
5592        "Truck Pro Login": "dc:help:truck pro login",
5593        FAQs: "dc:wts:help:faqs",
5594        "Learning Portal": "dc:wts:help:faqs",
5595        Definitions: "dc:wts:help:definitions",
5596        SUBMIT_SUPPORT_FORM: "dc:wts:help:support",
5597    };
5598
5599    // Page fields Ford requires an onclick to override rather than inherit from the last page load.
5600    var ONCLICK_PAGE_FIELD_OVERRIDES = {
5601        "Truck Pro Login": {
5602            siteSection: "help",
5603            hierarchy: "help:truck pro login",
5604        },
5605        FAQs: {
5606            siteSection: "help",
5607            hierarchy: "help:support",
5608            pageNameNoVehicle: "dc:wts:help:faqs",
5609        },
5610        "Learning Portal": {
5611            siteSection: "help",
5612            hierarchy: "help:support",
5613            pageNameNoVehicle: "dc:wts:help:faqs",
5614        },
5615        Definitions: {
5616            siteSection: "help",
5617            hierarchy: "help:support",
5618            pageNameNoVehicle: "dc:wts:help:definitions",
5619        },
5620        SUBMIT_SUPPORT_FORM: {
5621            siteSection: "help",
5622            hierarchy: "help:support",
5623            tool: "event:updates opt-in",
5624            toolDescriptor: "updates opt-in:global",
5625        },
5626    };
5627
5628    // Milestone 4 global-overlay (2c) definitions - 22 in-scope Page Variables rows.
5629    // pagePath is appended to "dc:wts:"; hierarchyBase gets ":{vehicle category}:{nameplate}"
5630    // appended when vehicleSuffix is true and a vehicle resolves.
5631    // Keys that differ per page group are nested under the group resolved from the last page load.
5632    // iaiFamily drives the hierarchy half of the onclick merge (issue sheet rows 26, 30, 36).
5633    var OVERLAY_CATALOG = {
5634        "call-popup": {
5635            VSP: {
5636                pagePath: "si:vls:call popup",
5637                siteSection: "search inventory",
5638                hierarchyBase: "shopping tools:call popup",
5639                vehicleSuffix: true,
5640            },
5641            About: {
5642                pagePath: "about:call popup",
5643                siteSection: "about",
5644                hierarchyBase: "about:call popup",
5645                vehicleSuffix: true,
5646            },
5647        },
5648        "email-popup": {
5649            VSP: {
5650                pagePath: "si:vls:email popup",
5651                siteSection: "search inventory",
5652                hierarchyBase: "shopping tools:email popup",
5653                vehicleSuffix: true,
5654            },
5655            About: {
5656                pagePath: "about:email popup",
5657                siteSection: "about",
5658                hierarchyBase: "about:email popup",
5659                vehicleSuffix: true,
5660            },
5661        },
5662        "i-am-interested": {
5663            iaiFamily: true,
5664            VSP: {
5665                pagePath: "si:vls:i am interested",
5666                siteSection: "search inventory",
5667                hierarchyBase: "shopping tools:i am interested",
5668                vehicleSuffix: true,
5669            },
5670            VDP: {
5671                pagePath: "si:vehicle details:i am interested",
5672                siteSection: "search inventory",
5673                hierarchyBase: "shopping tools:i am interested",
5674                vehicleSuffix: true,
5675            },
5676        },
5677        "see-all-photos": {
5678            VSP: {
5679                pagePath: "si:vls:see all photos",
5680                siteSection: "search inventory",
5681                hierarchyBase: "shopping tools:see all photos",
5682                vehicleSuffix: true,
5683            },
5684            VDP: {
5685                pagePath: "si:vehicle details:see all photos",
5686                siteSection: "search inventory",
5687                hierarchyBase: "shopping tools:see all photos",
5688                vehicleSuffix: true,
5689            },
5690            // Same #photo-gallery-modal, different Ford row when opened from the catalog.
5691            CommercialCatalog: {
5692                pagePath: "commercial catalog:gallery popup",
5693                siteSection: "commercial catalog",
5694                hierarchyBase: "commercial catalog",
5695                vehicleSuffix: true,
5696            },
5697        },
5698        "see-all-photos-i-am-interested": {
5699            iaiFamily: true,
5700            VSP: {
5701                pagePath: "si:vls:see all photos:i am interested",
5702                siteSection: "search inventory",
5703                hierarchyBase: "shopping tools:i am interested",
5704                vehicleSuffix: true,
5705            },
5706            VDP: {
5707                pagePath: "si:vehicle details:see all photos:i am interested",
5708                siteSection: "search inventory",
5709                hierarchyBase: "shopping tools:i am interested",
5710                vehicleSuffix: true,
5711            },
5712        },
5713        save: {
5714            VDP: {
5715                pagePath: "si:vehicle details:save",
5716                siteSection: "search inventory",
5717                hierarchyBase: "shopping tools:save",
5718                vehicleSuffix: true,
5719            },
5720        },
5721        // Rows 47 and 49 omit the "get quote" segment from hierarchy - implemented as written.
5722        // Both are steps of the same Crestline modal, so they replace rather than nest.
5723        "get-quote": {
5724            family: "get-quote",
5725            VDP: {
5726                pagePath: "si:vehicle details:get quote",
5727                siteSection: "search inventory",
5728                hierarchyBase: "shopping tools",
5729                vehicleSuffix: true,
5730            },
5731        },
5732        "get-quote-vehicle-transportation": {
5733            family: "get-quote",
5734            VDP: {
5735                pagePath:
5736                    "si:vehicle details:get quote:vehicle transportation quote",
5737                siteSection: "search inventory",
5738                hierarchyBase: "shopping tools",
5739                vehicleSuffix: true,
5740            },
5741        },
5742        // Build & Get Quote overlays carry their own site section regardless of host page.
5743        // Row 53 omits the ":info" segment from hierarchy - implemented as written.
5744        "bg-info": {
5745            any: {
5746                pagePath: "build and get quote:info",
5747                siteSection: "build and get quote",
5748                hierarchyBase: "build and get quote",
5749            },
5750        },
5751        "bg-select-upfit": {
5752            any: {
5753                pagePath: "build and get quote:info:select upfit",
5754                siteSection: "build and get quote",
5755                hierarchyBase: "build and get quote:info:select upfit",
5756            },
5757        },
5758        "bg-request-more-info": {
5759            any: {
5760                pagePath: "build and get quote:request more info",
5761                siteSection: "build and get quote",
5762                hierarchyBase: "build and get quote:request more info",
5763            },
5764        },
5765        "bg-get-a-quote": {
5766            any: {
5767                pagePath: "build and get quote:get a quote for this build",
5768                siteSection: "build and get quote",
5769                hierarchyBase: "build and get quote:get a quote for this build",
5770            },
5771        },
5772        "ez-order-form": {
5773            family: "ez-order-form",
5774            any: {
5775                pagePath: "custom order:ez order form",
5776                siteSection: "custom order",
5777                hierarchyBase: "custom order:ez order form",
5778                vehicleSuffix: true,
5779            },
5780        },
5781        "ez-order-form-step-1": {
5782            family: "ez-order-form",
5783            any: {
5784                pagePath: "custom order:ez order form:step 1",
5785                siteSection: "custom order",
5786                hierarchyBase: "custom order:ez order form:step 1",
5787                vehicleSuffix: true,
5788            },
5789        },
5790        "ez-order-form-step-2": {
5791            family: "ez-order-form",
5792            any: {
5793                pagePath: "custom order:ez order form:step 2",
5794                siteSection: "custom order",
5795                hierarchyBase: "custom order:ez order form:step 2",
5796                vehicleSuffix: true,
5797            },
5798        },
5799        "ez-order-form-step-3": {
5800            family: "ez-order-form",
5801            any: {
5802                pagePath: "custom order:ez order form:step 3",
5803                siteSection: "custom order",
5804                hierarchyBase: "custom order:ez order form:step 3",
5805                vehicleSuffix: true,
5806            },
5807        },
5808    };
5809
5810    // Page-load context key to overlay catalog page group.
5811    var OVERLAY_PAGE_GROUP_MAP = {
5812        VSP: "VSP",
5813        VDP: "VDP",
5814        VDPPRINT: "VDP",
5815        About: "About",
5816        CustomOrders: "CustomOrders",
5817        CommercialCatalog: "CommercialCatalog",
5818    };
5819
5820    /**
5821     * Determines the device type based on screen width.
5822     * Uses the shared utility from modules.utilities.
5823     * @returns {string} "desktop", "tablet", or "mobile"
5824     */
5825    function getExperienceType() {
5826        return utilities.detectDeviceType();
5827    }
5828
5829    /**
5830     * QA helper - logs track name and optional label on one line, matching testing-doc
5831     * "Expected payload" format (track: form-submit  (label: …)).
5832     *
5833     * @param {string} trackName - DTM track name (e.g. form-submit, click-to-call)
5834     * @param {string} [label] - Form context or descriptor for QA verification
5835     * @param {Object} digitaldata - Payload object (logged separately for DevTools expansion)
5836     */
5837    function logTrackEventDebug(trackName, label, digitaldata) {
5838        var header = "[Ford Direct Analytics] track: " + trackName;
5839        if (label) {
5840            header += "  (label: " + label + ")";
5841        }
5842        console.log(header);
5843        console.log(digitaldata);
5844    }
5845
5846    /**
5847     * QA helper - logs a labeled mapping of our digitaldata fields to the
5848     * Adobe Analytics variables Ford expects per the WTS Page Variables spec.
5849     * Ford's Adobe Launch/DTM reads our named fields and assigns them to the
5850     * corresponding s.pageName / s.eVar* variables. Only invoked when
5851     * ?debug-fd=1 is present (enableDebug is true).
5852     *
5853     * @param {Object} digitaldata - The fully-built digitaldata object
5854     */
5855    function logEVarSourceMap(digitaldata) {
5856        var page = (digitaldata && digitaldata.page) || {};
5857        var user = (digitaldata && digitaldata.user) || {};
5858        var vehicle = (digitaldata && digitaldata.vehicle) || null;
5859        var eventData = (digitaldata && digitaldata.event) || {};
5860        var onclick = (digitaldata && digitaldata.onclick) || {};
5861        var noVehicle = "(n/a - non-vehicle page)";
5862
5863        console.log(
5864            "[Ford Direct Analytics] Adobe variable source map (per Ford spec):"
5865        );
5866        console.log(
5867            "[Ford Direct Analytics] Page context and event type determine which rows below are populated (e.g. vehicle and searchDepth on vehicle-scoped VSP forms; onclick fields on click-to-call only)."
5868        );
5869        console.table([
5870            { Variable: "s.pageName",                     "Source field": "page.pageName",            Value: page.pageName },
5871            { Variable: "s.hierarchy (h1)",               "Source field": "page.hierarchy",           Value: page.hierarchy },
5872            { Variable: "s.channel (Site Section)",       "Source field": "page.siteSection",         Value: page.siteSection },
5873            { Variable: "s.eVar1 (P&A Code)",             "Source field": "user.paCode",              Value: user.paCode },
5874            { Variable: "s.eVar2 (User Ford Region)",     "Source field": "user.fordRegion",          Value: user.fordRegion },
5875            { Variable: "s.eVar3 (Dealer FDAF)",          "Source field": "user.fdafCode",            Value: user.fdafCode },
5876            { Variable: "s.eVar4 (User Language)",        "Source field": "page.userLanguage",        Value: page.userLanguage },
5877            { Variable: "s.eVar11 (Pagename No Vehicle)", "Source field": "page.pageNameNoVehicle",   Value: page.pageNameNoVehicle },
5878            { Variable: "s.eVar12 (Model Year)",          "Source field": "vehicle.modelYear",        Value: vehicle ? vehicle.modelYear : noVehicle },
5879            { Variable: "s.eVar14 (Client)",              "Source field": "page.client",              Value: page.client },
5880            { Variable: "s.eVar15 (Site)",                "Source field": "page.site",                Value: page.site },
5881            { Variable: "s.eVar16 (Nameplate)",           "Source field": "vehicle.nameplate",        Value: vehicle ? vehicle.nameplate : noVehicle },
5882            { Variable: "s.eVar18 (Body Model Trim)",     "Source field": "vehicle.bodyModelTrim",    Value: vehicle ? vehicle.bodyModelTrim : noVehicle },
5883            { Variable: "s.eVar19 (Color Code)",          "Source field": "vehicle.extIntColorCode",  Value: vehicle ? vehicle.extIntColorCode : noVehicle },
5884            { Variable: "s.eVar24 (Engine/Trans)",        "Source field": "vehicle.engineTrans",      Value: vehicle ? vehicle.engineTrans : noVehicle },
5885            { Variable: "s.eVar25 (Price)",               "Source field": "vehicle.price",            Value: vehicle ? vehicle.price : noVehicle },
5886            { Variable: "s.eVar28 (Lead Type)",           "Source field": "user.leadType",            Value: user.leadType },
5887            { Variable: "s.eVar35 (Search Depth)",        "Source field": "page.searchDepth",         Value: page.searchDepth },
5888            { Variable: "s.eVar48 (Tool)",                "Source field": "page.tool",                Value: page.tool },
5889            { Variable: "s.eVar49 (Tool Descriptor)",     "Source field": "page.toolDescriptor",      Value: page.toolDescriptor },
5890            { Variable: "s.eVar129 (PageType)",           "Source field": "page.pageType",            Value: page.pageType },
5891            { Variable: "s.eVar133 (ExperienceType)",     "Source field": "page.experienceType",      Value: page.experienceType },
5892            { Variable: "event.action",                   "Source field": "event.action",             Value: eventData.action },
5893            { Variable: "onclick.onclick (c5)",           "Source field": "onclick.onclick",          Value: onclick.onclick },
5894            { Variable: "onclick.onclickLinkName (pev2)", "Source field": "onclick.onclickLinkName",  Value: onclick.onclickLinkName },
5895            { Variable: "onclick.referredTo",             "Source field": "onclick.referredTo",       Value: onclick.referredTo },
5896        ]);
5897    }
5898
5899    /**
5900     * Checks if the current page is the home page.
5901     * Home page is detected by checking if the URL path is "/" or empty.
5902     * @returns {boolean} True if on home page
5903     */
5904    function isHomePage() {
5905        var path = window.location.pathname;
5906        return path === "/" || path === "";
5907    }
5908
5909    /**
5910     * /definitions has no VehicleDisplayContext enum; pathname is the source of truth.
5911     * @returns {boolean}
5912     */
5913    function isDefinitionsPage() {
5914        var path = (window.location.pathname || "").replace(/\/+$/, "");
5915        return path.toLowerCase() === "/definitions";
5916    }
5917
5918    /**
5919     * Many dealers serve VSP at the root URL (/). Server page context wins over pathname.
5920     * @returns {boolean}
5921     */
5922    function isVspPage() {
5923        var pageData = getClientData("#page-context-data");
5924        if (pageData && pageData.isVSP) {
5925            return true;
5926        }
5927        return (
5928            fordDirectSetup &&
5929            fordDirectSetup.pageContext === "SearchResults"
5930        );
5931    }
5932
5933    /**
5934     * Gets the page context from event data or ford-direct-analytics-setup JSON.
5935     * @param {Object} event - The visor event object
5936     * @returns {string} The page context identifier (enum name string)
5937     */
5938    function getPageContext(event) {
5939        var pageContext;
5940
5941        // Check if pageContext was passed via event data (for special pages like Home, PrivacyPolicy, VDPPRINT)
5942        if (event && event.data && event.data.pageContext) {
5943            pageContext = event.data.pageContext;
5944            if (enableDebug) {
5945                console.log(
5946                    "[Ford Direct Analytics] getPageContext from event data:",
5947                    pageContext
5948                );
5949            }
5950            return pageContext;
5951        }
5952
5953        // Use our setup data which has pageContext as a string (enum name)
5954        if (fordDirectSetup && fordDirectSetup.pageContext) {
5955            pageContext = fordDirectSetup.pageContext;
5956
5957            // Special case: If pageContext is "None" and we're on the home page
5958            if (pageContext === "None" && isHomePage()) {
5959                if (enableDebug) {
5960                    console.log(
5961                        "[Ford Direct Analytics] getPageContext: Detected Home page (custom page at root URL)"
5962                    );
5963                }
5964                return "Home";
5965            }
5966
5967            if (pageContext === "None" && isDefinitionsPage()) {
5968                return "Definitions";
5969            }
5970
5971            if (enableDebug) {
5972                console.log(
5973                    "[Ford Direct Analytics] getPageContext from setup:",
5974                    pageContext
5975                );
5976            }
5977            return pageContext;
5978        }
5979
5980        if (enableDebug) {
5981            console.log(
5982                "[Ford Direct Analytics] getPageContext: None (no context found)"
5983            );
5984        }
5985        return "None";
5986    }
5987
5988    /**
5989     * Gets vehicle data from the page if available.
5990     * @returns {Object|null} Vehicle data object or null if not available
5991     */
5992    function getVehicleData() {
5993        var marketingData = getClientData("#marketing-events-data");
5994        if (!marketingData) {
5995            if (enableDebug) {
5996                console.log(
5997                    "[Ford Direct Analytics] getVehicleData: No marketing-events-data found"
5998                );
5999            }
6000            return null;
6001        }
6002
6003        if (!marketingData.inventoryID && !marketingData.model && !marketingData.year) {
6004            if (enableDebug) {
6005                console.log(
6006                    "[Ford Direct Analytics] getVehicleData: marketing-events-data lacks inventory context"
6007                );
6008            }
6009            return null;
6010        }
6011
6012        var vehicleData = buildVehicleDataFromMarketingJson(marketingData, {
6013            inventoryID: marketingData.inventoryID,
6014        });
6015        // VDP marketing-events-data keeps display-case model in nameplate until form-submit lowercases
6016        vehicleData.nameplate = marketingData.model || "";
6017
6018        if (enableDebug) {
6019            console.log("[Ford Direct Analytics] getVehicleData:", vehicleData);
6020        }
6021        return vehicleData;
6022    }
6023
6024    /**
6025     * Gets the page context key stored during the last page load.
6026     * Set by buildDigitalData when trackPageLoad is called.
6027     * @returns {string} The page context key (e.g., "Home", "VSP", "VDP", "About")
6028     */
6029    function getPageContextKeyFromLastLoad() {
6030        return lastPageContextKey || "unknown";
6031    }
6032
6033    /**
6034     * @param {string} [fuelType]
6035     * @param {string} [transmissionType]
6036     * @returns {string}
6037     */
6038    function buildEngineTrans(fuelType, transmissionType) {
6039        return [fuelType, transmissionType]
6040            .filter(function (value) {
6041                return value && String(value).trim();
6042            })
6043            .join("|");
6044    }
6045
6046    /**
6047     * @param {Object} marketingData
6048     * @param {string} inventoryID
6049     * @returns {Object|null}
6050     */
6051    function findAscItemForMarketingData(marketingData, inventoryID) {
6052        if (!window.asc_datalayer || !window.asc_datalayer.items) {
6053            return null;
6054        }
6055
6056        return (
6057            window.asc_datalayer.items.find(function (item) {
6058                return (
6059                    (marketingData.vin && item.item_id === marketingData.vin) ||
6060                    String(item.item_inventory_id) === String(inventoryID)
6061                );
6062            }) || null
6063        );
6064    }
6065
6066    /**
6067     * @param {Object} marketingData
6068     * @param {Object} [options]
6069     * @param {string} [options.inventoryID]
6070     * @param {string} [options.bodyStyle]
6071     * @param {Object} [options.ascItem]
6072     * @returns {Object}
6073     */
6074    function buildVehicleDataFromMarketingJson(marketingData, options) {
6075        options = options || {};
6076        var bodyStyle = options.bodyStyle || marketingData.bodyStyle || "truck";
6077        var extIntColorCode = marketingData.color || "";
6078        var price =
6079            marketingData.price !== undefined &&
6080            marketingData.price !== null &&
6081            marketingData.price !== ""
6082                ? String(marketingData.price)
6083                : "";
6084        var engineTrans = buildEngineTrans(
6085            marketingData.fuelType,
6086            marketingData.transmissionType
6087        );
6088
6089        var ascItem =
6090            options.ascItem ||
6091            (options.inventoryID
6092                ? findAscItemForMarketingData(
6093                      marketingData,
6094                      options.inventoryID
6095                  )
6096                : null);
6097        if (ascItem) {
6098            if (!marketingData.bodyStyle) {
6099                if (ascItem.item_type) {
6100                    bodyStyle = ascItem.item_type;
6101                } else if (ascItem.item_category) {
6102                    bodyStyle = ascItem.item_category;
6103                }
6104            }
6105            if (!extIntColorCode && ascItem.item_color) {
6106                extIntColorCode = ascItem.item_color;
6107            }
6108            if (
6109                !price &&
6110                ascItem.item_price !== undefined &&
6111                ascItem.item_price !== null &&
6112                ascItem.item_price !== ""
6113            ) {
6114                price = String(ascItem.item_price);
6115            }
6116            if (!engineTrans && ascItem.item_fuel_type) {
6117                engineTrans = buildEngineTrans(ascItem.item_fuel_type, "");
6118            }
6119        }
6120
6121        return {
6122            modelYear: marketingData.year || "",
6123            nameplate: (marketingData.model || "").toLowerCase(),
6124            bodyModelTrim: [
6125                bodyStyle,
6126                marketingData.model,
6127                marketingData.trim,
6128            ]
6129                .filter(Boolean)
6130                .join("|"),
6131            extIntColorCode: extIntColorCode,
6132            engineTrans: engineTrans,
6133            price: price,
6134        };
6135    }
6136
6137    /**
6138     * @param {string} inventoryID
6139     * @returns {Object|null}
6140     */
6141    function getVehicleDataFromInventoryID(inventoryID) {
6142        if (!inventoryID) {
6143            return null;
6144        }
6145
6146        var marketingData = getClientData(
6147            "#analytics-marketing-data-" + inventoryID
6148        );
6149        if (!marketingData || (!marketingData.model && !marketingData.year)) {
6150            return null;
6151        }
6152
6153        return buildVehicleDataFromMarketingJson(marketingData, {
6154            inventoryID: inventoryID,
6155        });
6156    }
6157
6158    /**
6159     * Client-side page context when M1 page load has not yet populated lastPageContextKey.
6160     * @returns {string}
6161     */
6162    function resolvePageContextKeyForFormSubmit() {
6163        if (isVspPage()) {
6164            return "VSP";
6165        }
6166
6167        var pageContextKey = getPageContextKeyFromLastLoad();
6168        if (pageContextKey && pageContextKey !== "unknown") {
6169            return pageContextKey;
6170        }
6171
6172        var pageData = getClientData("#page-context-data");
6173        if (pageData && pageData.isVSP) {
6174            return "VSP";
6175        }
6176        if (typeof window.isAbout !== "undefined" && window.isAbout) {
6177            return "About";
6178        }
6179        if (
6180            (typeof window.isEZOrder !== "undefined" && window.isEZOrder) ||
6181            (typeof window.isCustomOrder !== "undefined" && window.isCustomOrder)
6182        ) {
6183            return "CustomOrders";
6184        }
6185        if (window.inventoryID) {
6186            return "VDP";
6187        }
6188
6189        return pageContextKey || "unknown";
6190    }
6191
6192    /**
6193     * Baseline user/page context for form-submit when lastPageLoadData is unavailable.
6194     * @returns {Object|null}
6195     */
6196    function getFormSubmitBaselineData() {
6197        if (lastPageLoadData) {
6198            return lastPageLoadData;
6199        }
6200        if (!_isEnabled || !fordDirectSetup) {
6201            return null;
6202        }
6203
6204        return {
6205            page: {},
6206            user: {
6207                paCode: fordDirectSetup.paCode || "",
6208                fordRegion: fordDirectSetup.fdafRegion || "",
6209                fdafCode: fordDirectSetup.fdafCode || "",
6210            },
6211        };
6212    }
6213
6214    /**
6215     * @param {Object} [event]
6216     * @returns {Object|null}
6217     */
6218    function resolveVehicleDataForFormSubmit(event) {
6219        var vehicleData = getVehicleData();
6220        if (vehicleData) {
6221            return vehicleData;
6222        }
6223
6224        var inventoryID =
6225            event &&
6226            event.data &&
6227            (event.data.inventoryID || event.data.truckID);
6228        return getVehicleDataFromInventoryID(inventoryID);
6229    }
6230
6231    /**
6232     * @param {string} formContext
6233     * @returns {Object}
6234     */
6235    function resolveFormSubmitPageGroup(formContext) {
6236        if (BUILD_AND_GET_QUOTE_FORM_CONTEXTS[formContext]) {
6237            return FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.BuildAndGetQuote;
6238        }
6239        if (formContext === "ez order form") {
6240            return FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.CustomOrders;
6241        }
6242
6243        var pageContextKey = resolvePageContextKeyForFormSubmit();
6244        if (pageContextKey === "CustomOrders") {
6245            return FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.CustomOrders;
6246        }
6247        if (pageContextKey === "About") {
6248            return FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.About;
6249        }
6250        if (pageContextKey === "VSP") {
6251            return FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.VSP;
6252        }
6253        return FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.VDP;
6254    }
6255
6256    /**
6257     * @param {string} formContext
6258     * @param {Object} pageGroup
6259     * @param {string} vehicleCategory
6260     * @param {string} nameplate
6261     * @returns {string}
6262     */
6263    function buildFormSubmitHierarchy(
6264        formContext,
6265        pageGroup,
6266        vehicleCategory,
6267        nameplate
6268    ) {
6269        var vehicleSuffix =
6270            vehicleCategory && nameplate
6271                ? ":" + vehicleCategory + ":" + nameplate
6272                : "";
6273
6274        if (pageGroup === FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.BuildAndGetQuote) {
6275            return "build and get quote:" + formContext;
6276        }
6277        if (pageGroup === FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.CustomOrders) {
6278            return (
6279                "custom order:ez order form" +
6280                (vehicleSuffix ? vehicleSuffix : "")
6281            );
6282        }
6283        if (pageGroup === FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.About) {
6284            return "about:" + formContext + vehicleSuffix;
6285        }
6286
6287        var hierarchySegment = formContext;
6288        if (formContext === "see all photos:i am interested") {
6289            hierarchySegment = "i am interested";
6290        }
6291
6292        return "shopping tools:" + hierarchySegment + vehicleSuffix;
6293    }
6294
6295    /**
6296     * @param {string} formContext
6297     * @param {Object} event
6298     * @returns {Object|null}
6299     */
6300    function buildFormSubmitDigitalData(formContext, event) {
6301        var baselineData = getFormSubmitBaselineData();
6302        if (!formContext || !baselineData) {
6303            return null;
6304        }
6305
6306        var pageGroup = resolveFormSubmitPageGroup(formContext);
6307        var vehicleData = resolveVehicleDataForFormSubmit(event);
6308        var nameplate =
6309            vehicleData && vehicleData.nameplate
6310                ? vehicleData.nameplate.toLowerCase()
6311                : "";
6312        var vehicleCategory = vehicleData
6313            ? (vehicleData.bodyModelTrim || "").split("|")[0] || "truck"
6314            : "";
6315        if (vehicleCategory) {
6316            vehicleCategory = vehicleCategory.toLowerCase();
6317        }
6318
6319        var descriptor = pageGroup.leadPrefix + ":" + formContext;
6320        var pagePath =
6321            pageGroup.pageNamePrefix +
6322            ":" +
6323            formContext +
6324            ":thank you" +
6325            (nameplate ? ":" + nameplate : "");
6326        var pageName = "dc:wts:" + pagePath;
6327        var pageNameNoVehicle =
6328            "dc:wts:" +
6329            pageGroup.pageNamePrefix +
6330            ":" +
6331            formContext +
6332            ":thank you";
6333
6334        var experienceType = getExperienceType();
6335        var digitaldata = {
6336            page: {
6337                pageName: pageName,
6338                siteSection: pageGroup.siteSection,
6339                hierarchy: buildFormSubmitHierarchy(
6340                    formContext,
6341                    pageGroup,
6342                    vehicleCategory,
6343                    nameplate
6344                ),
6345                userLanguage: FORD_USER_LANGUAGE,
6346                pageNameNoVehicle: pageNameNoVehicle,
6347                client: FORD_CLIENT,
6348                site: "dc:worktrucksolutions:us",
6349                radUIVersion:
6350                    window.radUIVersion || "ui:rad:" + experienceType,
6351                pageDomain: window.location.hostname,
6352                pageType: pageGroup.pageType,
6353                experienceType: "ui:rad:" + experienceType,
6354                tool: "default form",
6355                toolDescriptor: descriptor,
6356            },
6357            user: JSON.parse(JSON.stringify(baselineData.user || {})),
6358            event: {
6359                action: "lead submitted",
6360            },
6361        };
6362
6363        digitaldata.user.leadType = descriptor;
6364
6365        if (vehicleData && nameplate) {
6366            digitaldata.vehicle = vehicleData;
6367        }
6368
6369        if (pageGroup === FORM_SUBMIT_PREFIX_BY_PAGE_GROUP.VSP) {
6370            var searchDepth =
6371                baselineData.page && baselineData.page.searchDepth;
6372            if (!searchDepth) {
6373                var totalResults = 0;
6374                var pageData = getClientData("#page-context-data");
6375                if (pageData && typeof pageData.totalResults !== "undefined") {
6376                    totalResults = pageData.totalResults;
6377                }
6378                searchDepth = "0:" + totalResults;
6379            }
6380            digitaldata.page.searchDepth = searchDepth;
6381        }
6382
6383        return digitaldata;
6384    }
6385
6386    /**
6387     * @param {Object} event
6388     * @returns {Object}
6389     */
6390    function buildClickToCallDigitalData(event) {
6391        var source =
6392            (event.data && event.data.clickToCallSource) || "global";
6393        var isVehicleDetails = source === "vehicle-details";
6394
6395        var onclickC5 = isVehicleDetails
6396            ? "wts:si:vehicle details:click to call"
6397            : "wts:help:click to call";
6398        var onclickPev2 = isVehicleDetails
6399            ? "dc:wts:si:vehicle details:click to call"
6400            : "dc:wts:help:click to call";
6401        var toolDescriptor = isVehicleDetails
6402            ? "click to call:vehicle details"
6403            : "click to call:global";
6404
6405        var digitaldata = {
6406            page: lastPageLoadData
6407                ? JSON.parse(JSON.stringify(lastPageLoadData.page))
6408                : {},
6409            onclick: {
6410                onclick: onclickC5,
6411                contentType: "cta",
6412                contentSubType: "link",
6413                onclickLinkName: onclickPev2,
6414            },
6415            event: {
6416                action: "click to call",
6417            },
6418        };
6419
6420        digitaldata.page.tool = "event:click to call";
6421        digitaldata.page.toolDescriptor = toolDescriptor;
6422        normalizeOnclickPageFields(digitaldata);
6423
6424        var vehicleData = getVehicleData();
6425        if (!vehicleData && event.data && event.data.inventoryID) {
6426            vehicleData = getVehicleDataFromInventoryID(event.data.inventoryID);
6427        }
6428        if (vehicleData) {
6429            digitaldata.vehicle = vehicleData;
6430        }
6431
6432        return digitaldata;
6433    }
6434
6435    /**
6436     * Builds a dynamic onclick link name for actions requiring page and modal context.
6437     *
6438     * Format varies by page context per Ford spec:
6439     * - Home (Row 66):  wts:home:content:{action}  (modal context ignored)
6440     * - About + modal (Row 114): wts:{modalContext}:content:{action}  (no page prefix)
6441     * - Other + modal (Rows 71,76,80,86,95): wts:{pageContext}:{modalContext}:content:{action}
6442     * - Other, no modal (Row 83): wts:{pageContext}:{action}  (no :content: segment)
6443     *
6444     * @param {string} actionKey - The action key to look up in DYNAMIC_ACTION_MAP
6445     * @param {string} [modalContext] - Optional modal context key (e.g., "call-popup", "i-am-interested")
6446     * @returns {string|null} The full onclick link name, or null if not a dynamic action
6447     */
6448    function buildDynamicOnclickLinkName(actionKey, modalContext) {
6449        var action = DYNAMIC_ACTION_MAP[actionKey];
6450        if (!action) {
6451            return null;
6452        }
6453
6454        // Get page context from lastPageLoadData
6455        var pageContextKey = getPageContextKeyFromLastLoad();
6456        var contextPath = ONCLICK_PAGE_CONTEXT_MAP[pageContextKey] || "unknown";
6457
6458        var onclickLinkName;
6459        var modalPath;
6460
6461        // Homepage: always wts:home:content:{action}, ignore modal context (Row 66)
6462        if (pageContextKey === "Home") {
6463            onclickLinkName = "wts:" + contextPath + ":content:" + action;
6464        }
6465        // About + modal: wts:{modalContext}:content:{action} (Row 114)
6466        else if (
6467            modalContext &&
6468            MODAL_CONTEXT_MAP[modalContext] &&
6469            pageContextKey === "About"
6470        ) {
6471            modalPath = MODAL_CONTEXT_MAP[modalContext];
6472            onclickLinkName = "wts:" + modalPath + ":content:" + action;
6473        }
6474        // Other page + modal: wts:{pageContext}:{modalContext}:content:{action} (Rows 71,76,80,86,95)
6475        else if (modalContext && MODAL_CONTEXT_MAP[modalContext]) {
6476            modalPath = MODAL_CONTEXT_MAP[modalContext];
6477            onclickLinkName =
6478                "wts:" + contextPath + ":" + modalPath + ":content:" + action;
6479        }
6480        // No modal, non-homepage: wts:{pageContext}:{action} (Row 83, no :content:)
6481        // Direct page actions like VDP phone links, photo scroll, etc.
6482        else {
6483            onclickLinkName = "wts:" + contextPath + ":" + action;
6484        }
6485
6486        if (enableDebug) {
6487            console.log(
6488                "[Ford Direct Analytics] buildDynamicOnclickLinkName:",
6489                actionKey,
6490                "->",
6491                onclickLinkName,
6492                "(pageContext:",
6493                pageContextKey,
6494                ", modalContext:",
6495                modalContext || "none",
6496                ")"
6497            );
6498        }
6499
6500        return onclickLinkName;
6501    }
6502
6503    /**
6504     * Returns VSP hierarchy suffix when a single chassis-model facet is active.
6505     * Format: {vehicle category}:{nameplate}
6506     *
6507     * @param {Object} [event] - Optional visor event with searchedBodyType/searchedChassisModel in data
6508     * @returns {string} Suffix without leading colon, or empty string
6509     */
6510    function getVspHierarchySuffix(event) {
6511        var chassisModel = "";
6512        var bodyType = "";
6513
6514        if (event && event.data) {
6515            if (event.data.searchedChassisModel) {
6516                chassisModel = event.data.searchedChassisModel;
6517            }
6518            if (event.data.searchedBodyType) {
6519                bodyType = event.data.searchedBodyType;
6520            }
6521        }
6522
6523        if (!chassisModel) {
6524            var pageData = getClientData("#page-context-data");
6525            if (pageData) {
6526                chassisModel = pageData.searchedChassisModel || "";
6527                bodyType = bodyType || pageData.searchedBodyType || "";
6528            }
6529        }
6530
6531        if (!chassisModel) {
6532            return "";
6533        }
6534
6535        var category = (bodyType || "truck").toLowerCase();
6536        return category + ":" + chassisModel.toLowerCase();
6537    }
6538
6539    /**
6540     * pev2 onclickLinkName for VDP content actions (Page Variables row 83).
6541     * @returns {string}
6542     */
6543    function buildVdpContentActionPev2() {
6544        return (
6545            "dc:wts:si:vehicle details:content:action" +
6546            getLastLoadNameplateSuffix()
6547        );
6548    }
6549
6550    /**
6551     * ":{nameplate}" from the last page load, or "" when no vehicle is in context.
6552     * @returns {string}
6553     */
6554    function getLastLoadNameplateSuffix() {
6555        var nameplate =
6556            lastPageLoadData &&
6557            lastPageLoadData.vehicle &&
6558            lastPageLoadData.vehicle.nameplate;
6559        return nameplate ? ":" + nameplate.toLowerCase() : "";
6560    }
6561
6562    /**
6563     * pev2 onclickLinkName for modal call flows with a distinct linkname per Ford spec.
6564     * @param {string} modalContextKey
6565     * @returns {string|null}
6566     */
6567    function buildModalCallPev2(modalContextKey) {
6568        var pageContextKey = getPageContextKeyFromLastLoad();
6569        var contextPath = ONCLICK_PAGE_CONTEXT_MAP[pageContextKey] || "unknown";
6570        var modalPath = MODAL_CONTEXT_MAP[modalContextKey];
6571
6572        if (!modalPath) {
6573            return null;
6574        }
6575
6576        // Call popup is page-context independent: Page Variables rows 21 (VSP) and 64 (About)
6577        // both specify the same linkname with no site-section segment.
6578        if (modalContextKey === "call-popup") {
6579            return "dc:wts:call popup:content:action";
6580        }
6581
6582        return "dc:wts:" + contextPath + ":" + modalPath + ":content:action";
6583    }
6584
6585    /**
6586     * Vehicle category segment used in Ford hierarchy strings.
6587     * @param {Object} vehicleData
6588     * @returns {string}
6589     */
6590    function getVehicleCategory(vehicleData) {
6591        if (!vehicleData) {
6592            return "";
6593        }
6594        return (
6595            (vehicleData.bodyModelTrim || "").split("|")[0] || "truck"
6596        ).toLowerCase();
6597    }
6598
6599    /**
6600     * Resolves an overlay key to its catalog definition for the current page group.
6601     * @param {string} overlayKey
6602     * @returns {Object|null}
6603     */
6604    function resolveOverlayDefinition(overlayKey) {
6605        var entry = OVERLAY_CATALOG[overlayKey];
6606        if (!entry) {
6607            return null;
6608        }
6609
6610        var pageGroup =
6611            OVERLAY_PAGE_GROUP_MAP[getPageContextKeyFromLastLoad()] || "";
6612        var definition = entry.any || entry[pageGroup];
6613
6614        if (!definition) {
6615            if (enableDebug) {
6616                console.warn(
6617                    "[Ford Direct Analytics] No overlay definition for",
6618                    overlayKey,
6619                    "on page group",
6620                    pageGroup || "(unresolved)"
6621                );
6622            }
6623            return null;
6624        }
6625
6626        return {
6627            overlayKey: overlayKey,
6628            family: entry.family || overlayKey,
6629            iaiFamily: !!entry.iaiFamily,
6630            pagePath: definition.pagePath,
6631            siteSection: definition.siteSection,
6632            hierarchyBase: definition.hierarchyBase,
6633            vehicleSuffix: !!definition.vehicleSuffix,
6634        };
6635    }
6636
6637    /**
6638     * Builds the digitaldata object for a global-overlay (2c) event and the stack entry
6639     * that later onclicks read from.
6640     *
6641     * @param {Object} definition Resolved overlay definition
6642     * @param {string} [inventoryID] Listing whose vehicle the overlay describes
6643     * @returns {Object}
6644     */
6645    function buildOverlayDigitalData(definition, inventoryID) {
6646        var experienceType = getExperienceType();
6647        var paCode = (fordDirectSetup && fordDirectSetup.paCode) || "";
6648        var fdafCode = (fordDirectSetup && fordDirectSetup.fdafCode) || "";
6649        var fdafRegion = (fordDirectSetup && fordDirectSetup.fdafRegion) || "";
6650
6651        // Prefer the page-load vehicle — on VDP that is the full marketing-events-data block,
6652        // while analytics-marketing-data-{id} injected by the i-am-interested form carries only
6653        // make/model/year/trim. Only VDP sets lastPageLoadData.vehicle, so VSP falls through.
6654        var vehicleData = (lastPageLoadData && lastPageLoadData.vehicle) || null;
6655        if (!vehicleData && inventoryID) {
6656            vehicleData = getVehicleDataFromInventoryID(inventoryID);
6657        }
6658        if (!vehicleData) {
6659            vehicleData = getVehicleData();
6660        }
6661
6662        var nameplate =
6663            vehicleData && vehicleData.nameplate
6664                ? vehicleData.nameplate.toLowerCase()
6665                : "";
6666
6667        var pageNameNoVehicle = "dc:wts:" + definition.pagePath;
6668        var pageName = pageNameNoVehicle + (nameplate ? ":" + nameplate : "");
6669
6670        var hierarchy = definition.hierarchyBase;
6671        if (definition.vehicleSuffix && nameplate) {
6672            hierarchy =
6673                hierarchy +
6674                ":" +
6675                getVehicleCategory(vehicleData) +
6676                ":" +
6677                nameplate;
6678        }
6679
6680        // Per DL variant 2c. No onclick block: an overlay open has no c5.
6681        // No tool/toolDescriptor: those columns are blank on every Milestone 4 overlay row.
6682        var digitaldata = {
6683            page: {
6684                pageName: pageName,
6685                siteSection: definition.siteSection,
6686                hierarchy: hierarchy,
6687                userLanguage: FORD_USER_LANGUAGE,
6688                pageNameNoVehicle: pageNameNoVehicle,
6689                client: FORD_CLIENT,
6690                site: "dc:worktrucksolutions:us",
6691                radUIVersion: window.radUIVersion || "ui:rad:" + experienceType,
6692                pageDomain: window.location.hostname,
6693                pageType: definition.siteSection,
6694                experienceType: "ui:rad:" + experienceType,
6695            },
6696            user: {
6697                paCode: paCode,
6698                fordRegion: fdafRegion,
6699                fdafCode: fdafCode,
6700            },
6701        };
6702
6703        if (vehicleData) {
6704            digitaldata.vehicle = vehicleData;
6705        }
6706
6707        return digitaldata;
6708    }
6709
6710    /**
6711     * Builds the digitaldata object for a window-sticker (4c) or global-download (7c) event.
6712     * Channel and hierarchy are inherited from the last page load per both Ford rows.
6713     *
6714     * @param {Object} definition Entry from TOOL_EVENTS
6715     * @param {string} [inventoryID]
6716     * @returns {Object}
6717     */
6718    function buildToolEventDigitalData(definition, inventoryID) {
6719        var vehicleData =
6720            (inventoryID && getVehicleDataFromInventoryID(inventoryID)) ||
6721            getVehicleData() ||
6722            (lastPageLoadData && lastPageLoadData.vehicle) ||
6723            null;
6724
6725        var nameplate =
6726            vehicleData && vehicleData.nameplate
6727                ? vehicleData.nameplate.toLowerCase()
6728                : "";
6729        var suffix = definition.appendNameplate && nameplate ? ":" + nameplate : "";
6730
6731        var digitaldata = {
6732            page: lastPageLoadData
6733                ? JSON.parse(JSON.stringify(lastPageLoadData.page))
6734                : {},
6735            onclick: {
6736                onclick: definition.c5 + suffix,
6737                contentType: "cta",
6738                contentSubType: "link",
6739                onclickLinkName: definition.pev2 + suffix,
6740            },
6741            user: lastPageLoadData ? lastPageLoadData.user : {},
6742            event: { action: definition.eventAction },
6743        };
6744
6745        digitaldata.page.userLanguage = FORD_USER_LANGUAGE;
6746        digitaldata.page.client = FORD_CLIENT;
6747        digitaldata.page.tool = definition.tool;
6748        digitaldata.page.toolDescriptor = definition.toolDescriptor;
6749
6750        if (vehicleData) {
6751            digitaldata.vehicle = vehicleData;
6752        }
6753
6754        return digitaldata;
6755    }
6756
6757    /**
6758     * Innermost open overlay, or null.
6759     * @returns {Object|null}
6760     */
6761    function getActiveOverlay() {
6762        return activeOverlayStack.length
6763            ? activeOverlayStack[activeOverlayStack.length - 1]
6764            : null;
6765    }
6766
6767    /**
6768     * Applies Ford global page fields on onclick payloads, then any per-action overrides.
6769     * @param {Object} digitaldata
6770     * @param {string} [actionKey]
6771     */
6772    function normalizeOnclickPageFields(digitaldata, actionKey) {
6773        if (!digitaldata.page) {
6774            digitaldata.page = {};
6775        }
6776        digitaldata.page.userLanguage = FORD_USER_LANGUAGE;
6777        digitaldata.page.client = FORD_CLIENT;
6778
6779        // Call Us Now copies overlay eVar11 always. Hierarchy only for I'm Interested family,
6780        // call-popup (PV 21/64), and get-quote (PV 48/50). VDP pricing phones fire this event
6781        // with no modalContext; copying every overlay would retag them from a modal that is open.
6782        var overlay =
6783            actionKey === "CALL_US_NOW_IM_INTERESTED" ? getActiveOverlay() : null;
6784        if (overlay) {
6785            digitaldata.page.pageNameNoVehicle = overlay.pageNameNoVehicle;
6786            if (
6787                overlay.iaiFamily ||
6788                overlay.overlayKey === "call-popup" ||
6789                overlay.overlayKey === "get-quote" ||
6790                overlay.overlayKey === "get-quote-vehicle-transportation"
6791            ) {
6792                digitaldata.page.hierarchy = overlay.hierarchy;
6793            }
6794        }
6795
6796        var overrides = actionKey && ONCLICK_PAGE_FIELD_OVERRIDES[actionKey];
6797        if (overrides) {
6798            Object.keys(overrides).forEach(function (field) {
6799                digitaldata.page[field] = overrides[field];
6800            });
6801        }
6802    }
6803
6804    /**
6805     * Builds the window.digitaldata object according to Ford spec.
6806     *
6807     * pageName format: dc:wts:{pagePath}
6808     * - For VDP/VDPPRINT: dc:wts:si:vehicle details:{nameplate}
6809     * - For list views (VSP, etc.): nameplate is omitted
6810     *
6811     * @param {Object} event - The visor event object
6812     * @returns {Object} The digitaldata object
6813     */
6814    function buildDigitalData(event) {
6815        var experienceType = getExperienceType();
6816
6817        // Get site/user data
6818        var userData = getClientData("#user-data");
6819        var dealerCode = fordDirectSetup ? fordDirectSetup.dealerCode : "";
6820        var paCode = fordDirectSetup ? fordDirectSetup.paCode : "";
6821        var fdafCode = fordDirectSetup ? fordDirectSetup.fdafCode : "";
6822        var fdafRegion = fordDirectSetup ? fordDirectSetup.fdafRegion : "";
6823
6824        // Determine the page context key
6825        var pageContextKey;
6826        var customPageTitle = "";
6827
6828        // Root URL is Home or VSP — many dealers serve inventory search at /
6829        if (
6830            isHomePage() &&
6831            !(event && event.data && event.data.pageContext)
6832        ) {
6833            pageContextKey = isVspPage() ? "VSP" : "Home";
6834            if (enableDebug) {
6835                console.log(
6836                    "[Ford Direct Analytics] buildDigitalData: Root URL resolved to",
6837                    pageContextKey
6838                );
6839            }
6840        }
6841        // Check if pageContext was passed via event data (for special pages like PrivacyPolicy, VDPPRINT)
6842        else if (event && event.data && event.data.pageContext) {
6843            var eventPageContext = event.data.pageContext;
6844            pageContextKey =
6845                PAGE_CONTEXT_MAP[eventPageContext] || eventPageContext;
6846            if (enableDebug) {
6847                console.log(
6848                    "[Ford Direct Analytics] buildDigitalData using event override:",
6849                    pageContextKey
6850                );
6851            }
6852        }
6853        // Use server-resolved pageContext
6854        else if (fordDirectSetup && fordDirectSetup.pageContext) {
6855            var serverContext = fordDirectSetup.pageContext;
6856            pageContextKey = PAGE_CONTEXT_MAP[serverContext] || serverContext;
6857
6858            if (isHomePage() && pageContextKey !== "VSP") {
6859                pageContextKey = "Home";
6860            }
6861            // Check for Home page (custom page at root URL)
6862            else if (pageContextKey === "unknown" && isHomePage()) {
6863                pageContextKey = "Home";
6864            }
6865            // Check for custom internal page
6866            else if (pageContextKey === "unknown" && window.isCustomPage) {
6867                pageContextKey = "CustomPage";
6868                customPageTitle = (window.customPageName || "").replace(
6869                    /^Custom Page:\s*/i,
6870                    ""
6871                );
6872            }
6873
6874            if (enableDebug) {
6875                console.log(
6876                    "[Ford Direct Analytics] buildDigitalData using server context:",
6877                    pageContextKey
6878                );
6879            }
6880        }
6881        // Fall back to client-side detection
6882        else {
6883            var pageContext = getPageContext(event);
6884            pageContextKey = PAGE_CONTEXT_MAP[pageContext] || pageContext;
6885            if (enableDebug) {
6886                console.log(
6887                    "[Ford Direct Analytics] buildDigitalData using client mapping:",
6888                    pageContextKey
6889                );
6890            }
6891        }
6892
6893        if (
6894            (pageContextKey === "unknown" || pageContextKey === "None") &&
6895            isDefinitionsPage()
6896        ) {
6897            pageContextKey = "Definitions";
6898        }
6899
6900        // Store the resolved page context key for use by onclick events
6901        lastPageContextKey = pageContextKey;
6902
6903        // Get the page path, site section, and page type
6904        // If pageContextKey is "unknown" but server provided valid values (from URL detection), use them
6905        var pagePath, siteSection, pageType;
6906        if (
6907            pageContextKey === "unknown" &&
6908            fordDirectSetup &&
6909            fordDirectSetup.pagePath &&
6910            fordDirectSetup.pagePath !== "unknown"
6911        ) {
6912            // Use server-calculated values (from URL detection in _FordDirectAnalytics.cshtml)
6913            pagePath = fordDirectSetup.pagePath;
6914            siteSection = fordDirectSetup.siteSection || "unknown";
6915            pageType = fordDirectSetup.pageType || "unknown";
6916            if (enableDebug) {
6917                console.log(
6918                    "[Ford Direct Analytics] Using server-provided page values:",
6919                    pagePath
6920                );
6921            }
6922        } else {
6923            // Use JS-side maps for known page contexts
6924            pagePath = PAGE_PATH_MAP[pageContextKey] || "unknown";
6925            siteSection = SITE_SECTION_MAP[pageContextKey] || "unknown";
6926            pageType = SITE_SECTION_MAP[pageContextKey] || "unknown";
6927        }
6928
6929        // Get vehicle data for VDP/VDPPRINT pages
6930        var vehicleData = null;
6931        var nameplate = "";
6932        if (pageContextKey === "VDP" || pageContextKey === "VDPPRINT") {
6933            vehicleData = getVehicleData();
6934            if (vehicleData && vehicleData.nameplate) {
6935                nameplate = vehicleData.nameplate.toLowerCase();
6936            }
6937        }
6938
6939        // Build pageName in Ford spec format: dc:wts:{pagePath}
6940        // Note: "dc" is a literal prefix per Ford spec, paCode is sent separately in user object
6941        var pageName = "dc:wts:" + pagePath;
6942        var pageNameNoVehicle = pageName;
6943
6944        // For VDP/VDPPRINT, append nameplate to pageName
6945        if (
6946            (pageContextKey === "VDP" || pageContextKey === "VDPPRINT") &&
6947            nameplate
6948        ) {
6949            pageName = pageName + ":" + nameplate;
6950        }
6951
6952        // For CustomPage, append the page title
6953        if (pageContextKey === "CustomPage" && customPageTitle) {
6954            pageName =
6955                pageName +
6956                ":" +
6957                customPageTitle.toLowerCase().replace(/\s+/g, " ");
6958            pageNameNoVehicle = "dc:wts:custom";
6959        }
6960
6961        // Build hierarchy string per Ford spec
6962        // Format varies by page type - see spec for exact patterns
6963        var hierarchy;
6964        if (pageContextKey === "VSP") {
6965            hierarchy = "shopping tools:search inventory-vls";
6966            var vspSuffix = getVspHierarchySuffix(event);
6967            if (vspSuffix) {
6968                hierarchy = hierarchy + ":" + vspSuffix;
6969            }
6970        } else if (pageContextKey === "VDP" || pageContextKey === "VDPPRINT") {
6971            // VDP hierarchy includes vehicle category and nameplate
6972            var vehicleCategory = vehicleData
6973                ? (vehicleData.bodyModelTrim || "").split("|")[0] || "truck"
6974                : "truck";
6975            hierarchy =
6976                "shopping tools:search inventory-vls:" +
6977                vehicleCategory.toLowerCase() +
6978                (nameplate ? ":" + nameplate : "");
6979        } else if (pageContextKey === "Home") {
6980            hierarchy = "home";
6981        } else if (pageContextKey === "Sitemap") {
6982            hierarchy = "help:sitemap";
6983        } else if (pageContextKey === "PrivacyPolicy") {
6984            hierarchy = "help:privacy";
6985        } else if (pageContextKey === "Definitions") {
6986            hierarchy = "help:definitions";
6987        } else if (pageContextKey === "About") {
6988            hierarchy = "about";
6989        } else if (pageContextKey === "CustomOrders") {
6990            hierarchy = "custom order";
6991        } else if (pageContextKey === "CommercialCatalog") {
6992            hierarchy = "commercial catalog";
6993        } else if (pageContextKey === "MyGarage") {
6994            hierarchy = "my garage";
6995        } else if (pageContextKey === "CustomPage") {
6996            hierarchy =
6997                "custom:" +
6998                (customPageTitle || "page").toLowerCase().replace(/\s+/g, "-");
6999        } else if (
7000            pageContextKey === "unknown" &&
7001            fordDirectSetup &&
7002            fordDirectSetup.hierarchy &&
7003            fordDirectSetup.hierarchy !== "unknown"
7004        ) {
7005            // Use server-provided hierarchy for URL-detected pages (TPLogin, FAQs, etc.)
7006            hierarchy = fordDirectSetup.hierarchy;
7007        } else {
7008            hierarchy = siteSection;
7009        }
7010
7011        var digitaldata = {
7012            page: {
7013                pageName: pageName,
7014                siteSection: siteSection,
7015                hierarchy: hierarchy,
7016                userLanguage: FORD_USER_LANGUAGE,
7017                pageNameNoVehicle: pageNameNoVehicle,
7018                client: FORD_CLIENT,
7019                site: "dc:worktrucksolutions:us",
7020                radUIVersion: window.radUIVersion || "ui:rad:" + experienceType,
7021                pageDomain: window.location.hostname,
7022                pageType: pageType,
7023                experienceType: "ui:rad:" + experienceType,
7024            },
7025            user: {
7026                paCode: paCode,
7027                fordRegion: fdafRegion,
7028                fdafCode: fdafCode,
7029            },
7030        };
7031
7032        // Add vehicle data and VDP lead type for VDP and VDPPRINT pages
7033        if (pageContextKey === "VDP" || pageContextKey === "VDPPRINT") {
7034            digitaldata.user.leadType = "si:vls";
7035            if (vehicleData) {
7036                digitaldata.vehicle = vehicleData;
7037            }
7038        }
7039
7040        // Add searchDepth for VSP pages (format: "0:{totalVehicles}")
7041        // The total is passed via event data from VSP_SEARCH_COMPLETE
7042        if (pageContextKey === "VSP") {
7043            var totalResults = 0;
7044            if (
7045                event &&
7046                event.data &&
7047                typeof event.data.totalResults !== "undefined"
7048            ) {
7049                totalResults = event.data.totalResults;
7050            } else {
7051                // Try to get from page data on initial load
7052                var pageData = getClientData("#page-context-data");
7053                if (pageData && typeof pageData.totalResults !== "undefined") {
7054                    totalResults = pageData.totalResults;
7055                }
7056            }
7057            digitaldata.page.searchDepth = "0:" + totalResults;
7058        }
7059
7060        return digitaldata;
7061    }
7062
7063    /**
7064     * Builds the window.digitaldata object for onclick events.
7065     * Uses lastPageLoadData for page, vehicle, and user context.
7066     *
7067     * @param {string} onclickC5 - The onclick link name (c5) for Ford spec
7068     * @param {string} onclickPev2 - The onclick link name (pev2), or null to reuse c5
7069     * @param {string} [actionKey] - Drives per-action page field overrides
7070     * @returns {Object} The digitaldata object for onclick events
7071     */
7072    function buildOnclickDigitalData(onclickC5, onclickPev2, actionKey) {
7073        var digitaldata = {
7074            page: lastPageLoadData
7075                ? JSON.parse(JSON.stringify(lastPageLoadData.page))
7076                : {},
7077            onclick: {
7078                onclick: onclickC5,
7079                contentType: "cta",
7080                contentSubType: "link",
7081                onclickLinkName: onclickPev2 || onclickC5,
7082            },
7083            vehicle: lastPageLoadData ? lastPageLoadData.vehicle : {},
7084            user: lastPageLoadData ? lastPageLoadData.user : {},
7085        };
7086        normalizeOnclickPageFields(digitaldata, actionKey);
7087        return digitaldata;
7088    }
7089
7090    /**
7091     * Same-host, non-http(s), and non-navigating hrefs are not Ford 6c referral exits.
7092     * @param {string} href
7093     * @returns {boolean}
7094     */
7095    function isSkippableReferralHref(href) {
7096        if (!href) {
7097            return true;
7098        }
7099        var trimmed = String(href).trim();
7100        var lower = trimmed.toLowerCase();
7101        if (
7102            lower.indexOf("javascript:") === 0 ||
7103            lower.indexOf("tel:") === 0 ||
7104            lower.indexOf("mailto:") === 0 ||
7105            lower === "#"
7106        ) {
7107            return true;
7108        }
7109        try {
7110            var resolved = new URL(trimmed, window.location.href);
7111            if (resolved.protocol !== "http:" && resolved.protocol !== "https:") {
7112                return true;
7113            }
7114            return resolved.hostname === window.location.hostname;
7115        } catch (err) {
7116            return true;
7117        }
7118    }
7119
7120    /**
7121     * Spec 6c c5 / referredTo. Homepage uses the literal home:homepage row; other pages
7122     * keep the same shape with the current page path so the exit is attributable.
7123     * @returns {{c5: string, referredTo: string}}
7124     */
7125    function getReferralExitPaths() {
7126        var pageContextKey = getPageContextKeyFromLastLoad();
7127        if (pageContextKey === "Home") {
7128            return {
7129                c5: "wts:referral:home:homepage",
7130                referredTo: "home:homepage",
7131            };
7132        }
7133        var pagePath = PAGE_PATH_MAP[pageContextKey] || "unknown";
7134        return {
7135            c5: "wts:referral:" + pagePath,
7136            referredTo: pagePath,
7137        };
7138    }
7139
7140    /**
7141     * Builds digitaldata for _satellite.track("referral-exit"). Inherits last page load.
7142     * @returns {Object}
7143     */
7144    function buildReferralExitDigitalData() {
7145        var paths = getReferralExitPaths();
7146        var digitaldata = {
7147            page: lastPageLoadData
7148                ? JSON.parse(JSON.stringify(lastPageLoadData.page))
7149                : {},
7150            onclick: {
7151                onclick: paths.c5,
7152                contentType: "cta",
7153                contentSubType: "link",
7154                referredTo: paths.referredTo,
7155                onclickLinkName: "referral:exit",
7156            },
7157            user: lastPageLoadData ? lastPageLoadData.user : {},
7158            event: { action: "brand referral" },
7159        };
7160        digitaldata.page.userLanguage = FORD_USER_LANGUAGE;
7161        digitaldata.page.client = FORD_CLIENT;
7162        if (lastPageLoadData && lastPageLoadData.vehicle) {
7163            digitaldata.vehicle = lastPageLoadData.vehicle;
7164        }
7165        return digitaldata;
7166    }
7167
7168    // Third-party exits only. Body-type Helpful Resources and the Comvoy manufacturer header
7169    // are out of scope: Ford filed neither, and Helpful Resources links to WTS article pages.
7170    var REFERRAL_EXIT_SELECTOR = ["a.learn-more-link"].join(",");
7171
7172    // REQ-P01 / OQ-02: wait only for global-load. Launch tag has no id — poll is required.
7173    var SATELLITE_WAIT_MS = 5000;
7174    var SATELLITE_POLL_MS = 50;
7175
7176    function isSatelliteTrackCallable() {
7177        return typeof _satellite !== "undefined" && typeof _satellite.track === "function";
7178    }
7179
7180    function findLaunchScript() {
7181        var scripts = document.getElementsByTagName("script");
7182        var i;
7183        var src;
7184        for (i = 0; i < scripts.length; i++) {
7185            src = scripts[i].src || "";
7186            if (src.indexOf("adobedtm.com") !== -1 || /launch-[^/]+\.min\.js/i.test(src)) {
7187                return scripts[i];
7188            }
7189        }
7190        return null;
7191    }
7192
7193    /**
7194     * Calls onReady once: true when _satellite.track is callable, false when the 5s cap expires.
7195     */
7196    function waitForSatelliteTrack(onReady) {
7197        var settled = false;
7198        var deadline = Date.now() + SATELLITE_WAIT_MS;
7199        var launchScript = findLaunchScript();
7200
7201        function settle(ready) {
7202            if (settled) {
7203                return;
7204            }
7205            settled = true;
7206            onReady(ready);
7207        }
7208
7209        function poll() {
7210            if (settled) {
7211                return;
7212            }
7213            if (isSatelliteTrackCallable()) {
7214                settle(true);
7215                return;
7216            }
7217            if (Date.now() >= deadline) {
7218                settle(false);
7219                return;
7220            }
7221            setTimeout(poll, SATELLITE_POLL_MS);
7222        }
7223
7224        if (launchScript) {
7225            launchScript.addEventListener("load", poll);
7226            launchScript.addEventListener("error", poll);
7227        }
7228        poll();
7229    }
7230
7231    /**
7232     * Public API - FordDirectAnalytics object
7233     */
7234    var FordDirectAnalytics = {
7235        /**
7236         * Tracks a page load event for Ford Direct Analytics.
7237         *
7238         * ALWAYS builds the full digitaldata object from JS - this is the single source of truth.
7239         * Server-side only sets a minimal placeholder to prevent DTM timing errors.
7240         *
7241         * @param {Object} event - The visor PAGE_LOAD event object
7242         */
7243        trackPageLoad: function (event) {
7244            // NOOP if Ford Direct Analytics is not enabled
7245            if (!_isEnabled) {
7246                if (enableDebug) {
7247                    console.log(
7248                        "[Ford Direct Analytics] Disabled - skipping PAGE_LOAD"
7249                    );
7250                }
7251                return;
7252            }
7253
7254            if (enableDebug) {
7255                console.log(
7256                    "[Ford Direct Analytics] trackPageLoad called",
7257                    event
7258                );
7259            }
7260
7261            // Build digitaldata first to check if page is mapped
7262            var digitaldata = buildDigitalData(event);
7263
7264            // Store a deep copy for use in onclick events (must happen before early return)
7265            lastPageLoadData = JSON.parse(JSON.stringify(digitaldata));
7266
7267            // Skip tracking for truly unmapped pages (pageContext "None" that isn't Home or CustomPage)
7268            // This keeps analytics data clean by not sending "unknown" values to Ford.
7269            // All known page types (Home, VSP, VDP, CustomPage, etc.) are explicitly mapped.
7270            // pageName format is dc:wts:{pagePath}, so check if pagePath portion is "unknown"
7271            if (digitaldata.page.pageName.indexOf(":wts:unknown") !== -1) {
7272                if (enableDebug) {
7273                    console.log(
7274                        "[Ford Direct Analytics] Skipping PAGE_LOAD for unmapped page type"
7275                    );
7276                }
7277                return;
7278            }
7279
7280            // Set global digitaldata for DTM
7281            window.digitaldata = digitaldata;
7282
7283            if (enableDebug) {
7284                // QA helper: show how our digitaldata fields map to the
7285                // Adobe Analytics variables Ford expects per the WTS Page
7286                // Variables spec. Ford's Adobe Launch/DTM reads these named
7287                // fields and assigns them to the corresponding s.eVar numbers.
7288                logEVarSourceMap(window.digitaldata);
7289            }
7290
7291            function fireGlobalLoad() {
7292                if (!isSatelliteTrackCallable()) {
7293                    if (enableDebug) {
7294                        console.warn(
7295                            "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7296                        );
7297                    }
7298                    return;
7299                }
7300                // Re-bind this invocation if a later trackPageLoad overlapped the wait.
7301                window.digitaldata = digitaldata;
7302                if (enableDebug) {
7303                    logTrackEventDebug("global-load", null, window.digitaldata);
7304                    console.log(
7305                        "[Ford Direct Analytics] Calling _satellite.track('global-load')"
7306                    );
7307                }
7308                _satellite.track("global-load");
7309            }
7310
7311            if (isSatelliteTrackCallable()) {
7312                fireGlobalLoad();
7313                return;
7314            }
7315
7316            waitForSatelliteTrack(function (ready) {
7317                if (!ready) {
7318                    if (enableDebug) {
7319                        console.warn(
7320                            "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7321                        );
7322                    }
7323                    return;
7324                }
7325                fireGlobalLoad();
7326            });
7327        },
7328
7329        /**
7330         * Returns whether Ford Direct Analytics is enabled.
7331         * @returns {boolean} True if enabled, false otherwise
7332         */
7333        isEnabled: function () {
7334            return _isEnabled;
7335        },
7336
7337        /**
7338         * Tracks a click event for Ford Direct Analytics.
7339         *
7340         * @param {Object} event - The visor event object
7341         * @param {string} eventType - The type of event (e.g., "GENERAL_LAYOUT_INTERACTION_CLICK")
7342         * @param {string} [modalContext] - Optional modal context for Call Us Now events
7343         *                                  (e.g., "call-popup", "i-am-interested", "see-all-photos-i-am-interested")
7344         */
7345        trackClick: function (event, eventType, modalContext) {
7346            if (!_isEnabled) {
7347                if (enableDebug) {
7348                    console.log(
7349                        "[Ford Direct Analytics] Disabled - skipping onclick"
7350                    );
7351                }
7352                return;
7353            }
7354
7355            var onclickC5 = null;
7356            var onclickPev2 = null;
7357            var actionKey = null;
7358
7359            // Modal context can be passed as parameter or via event.data
7360            var effectiveModalContext =
7361                modalContext || (event.data && event.data.modalContext);
7362
7363            // Determine the action key based on event type and data
7364            if (eventType === "GENERAL_LAYOUT_INTERACTION_CLICK") {
7365                // Read CTA element title from DOM data attribute
7366                // Note: browser converts data-asc-element-title to dataset.ascElementTitle
7367                actionKey =
7368                    event.data.currentTarget &&
7369                    event.data.currentTarget.dataset.ascElementTitle;
7370            } else if (eventType === "ABOUT_EVENT") {
7371                actionKey = event.data.eventName;
7372            } else if (eventType === "INVENTORY_LAYOUT_TRACK_SEARCH_EVENT") {
7373                // VSP/VDP pages use eventName like ABOUT_EVENT
7374                actionKey = event.data.eventName;
7375            } else {
7376                actionKey = eventType;
7377            }
7378
7379            // FeaturedPhotoGallery is shared by VDP and the commercial catalog. Catalog clicks
7380            // have their own PV 77 events; suppress the VDP content-action event there.
7381            if (
7382                getPageContextKeyFromLastLoad() === "CommercialCatalog" &&
7383                VDP_CONTENT_ACTION_EVENT_KEYS[actionKey]
7384            ) {
7385                return;
7386            }
7387
7388            // Crestline quote forms use their own linknames rather than the generic modal shape
7389            var quoteModalOnclick =
7390                effectiveModalContext &&
7391                QUOTE_MODAL_ONCLICKS[effectiveModalContext];
7392
7393            // First, try to build a dynamic (page-context-aware) onclick link name
7394            onclickC5 = quoteModalOnclick
7395                ? quoteModalOnclick.c5
7396                : buildDynamicOnclickLinkName(actionKey, effectiveModalContext);
7397
7398            // If not a dynamic action, fall back to static mapping
7399            if (!onclickC5) {
7400                onclickC5 = ONCLICK_LINK_NAME_MAP[actionKey];
7401            }
7402
7403            if (!onclickC5) {
7404                // No mapping found - this is expected for clicks that are not
7405                // Milestone 2 events (e.g., Support link opens a modal = Milestone 4).
7406                // Silently skip to avoid confusing debug output.
7407                return;
7408            }
7409
7410            if (quoteModalOnclick) {
7411                onclickPev2 =
7412                    quoteModalOnclick.pev2 + getLastLoadNameplateSuffix();
7413            } else if (effectiveModalContext) {
7414                onclickPev2 = buildModalCallPev2(effectiveModalContext);
7415            } else if (CATALOG_CONTENT_ACTION_EVENT_KEYS[actionKey]) {
7416                onclickPev2 =
7417                    "dc:wts:commercial catalog:content:action" +
7418                    getLastLoadNameplateSuffix();
7419            } else if (
7420                actionKey === "CALL_US_NOW_IM_INTERESTED" &&
7421                getPageContextKeyFromLastLoad() === "Home"
7422            ) {
7423                onclickPev2 = "dc:wts:home:content:action";
7424            } else if (
7425                actionKey === "CALL_US_NOW_IM_INTERESTED" &&
7426                getPageContextKeyFromLastLoad() === "VDP"
7427            ) {
7428                onclickPev2 = buildVdpContentActionPev2();
7429            } else if (VDP_CONTENT_ACTION_EVENT_KEYS[actionKey]) {
7430                onclickPev2 = buildVdpContentActionPev2();
7431            } else {
7432                onclickPev2 = ONCLICK_PEV2_MAP[actionKey] || null;
7433            }
7434
7435            var digitaldata = buildOnclickDigitalData(
7436                onclickC5,
7437                onclickPev2,
7438                actionKey
7439            );
7440            window.digitaldata = digitaldata;
7441
7442            if (enableDebug) {
7443                logTrackEventDebug(
7444                    "global-onclick",
7445                    actionKey,
7446                    window.digitaldata
7447                );
7448                logEVarSourceMap(window.digitaldata);
7449            }
7450
7451            // Fire the Adobe DTM event
7452            if (typeof _satellite !== "undefined" && _satellite.track) {
7453                if (enableDebug) {
7454                    console.log(
7455                        "[Ford Direct Analytics] Calling _satellite.track('global-onclick')"
7456                    );
7457                }
7458                _satellite.track("global-onclick");
7459            } else {
7460                if (enableDebug) {
7461                    console.warn(
7462                        "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7463                    );
7464                }
7465            }
7466        },
7467
7468        /**
7469         * Tracks a global-overlay (2c) event when a Milestone 4 overlay opens, and records it
7470         * so later onclicks inside the overlay can pick up its eVar11 / hierarchy.
7471         *
7472         * @param {Object} options
7473         * @param {string} options.overlayKey - Key into OVERLAY_CATALOG
7474         * @param {string} [options.inventoryID] - Listing the overlay describes
7475         */
7476        trackOverlay: function (options) {
7477            if (!_isEnabled) {
7478                return;
7479            }
7480
7481            var overlayKey = options && options.overlayKey;
7482            var definition = resolveOverlayDefinition(overlayKey);
7483            if (!definition) {
7484                return;
7485            }
7486
7487            // Wizard steps replace their sibling rather than nesting on top of it.
7488            for (var i = activeOverlayStack.length - 1; i >= 0; i--) {
7489                if (activeOverlayStack[i].family === definition.family) {
7490                    activeOverlayStack.splice(i, 1);
7491                }
7492            }
7493
7494            var digitaldata = buildOverlayDigitalData(
7495                definition,
7496                options && options.inventoryID
7497            );
7498
7499            activeOverlayStack.push({
7500                overlayKey: overlayKey,
7501                family: definition.family,
7502                iaiFamily: definition.iaiFamily,
7503                pageName: digitaldata.page.pageName,
7504                hierarchy: digitaldata.page.hierarchy,
7505                pageNameNoVehicle: digitaldata.page.pageNameNoVehicle,
7506            });
7507
7508            window.digitaldata = digitaldata;
7509
7510            if (enableDebug) {
7511                logTrackEventDebug(
7512                    "global-overlay",
7513                    overlayKey,
7514                    window.digitaldata
7515                );
7516                logEVarSourceMap(window.digitaldata);
7517            }
7518
7519            if (typeof _satellite !== "undefined" && _satellite.track) {
7520                _satellite.track("global-overlay");
7521            } else if (enableDebug) {
7522                console.warn(
7523                    "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7524                );
7525            }
7526        },
7527
7528        /**
7529         * Tracks a window-sticker (4c) or global-download (7c) event.
7530         *
7531         * @param {string} toolEventKey - Key into TOOL_EVENTS
7532         * @param {Object} [options]
7533         * @param {string} [options.inventoryID]
7534         */
7535        trackToolEvent: function (toolEventKey, options) {
7536            if (!_isEnabled) {
7537                return;
7538            }
7539
7540            var definition = TOOL_EVENTS[toolEventKey];
7541            if (!definition) {
7542                if (enableDebug) {
7543                    console.warn(
7544                        "[Ford Direct Analytics] Unknown tool event:",
7545                        toolEventKey
7546                    );
7547                }
7548                return;
7549            }
7550
7551            window.digitaldata = buildToolEventDigitalData(
7552                definition,
7553                options && options.inventoryID
7554            );
7555
7556            if (enableDebug) {
7557                logTrackEventDebug(
7558                    definition.trackName,
7559                    definition.toolDescriptor,
7560                    window.digitaldata
7561                );
7562                logEVarSourceMap(window.digitaldata);
7563            }
7564
7565            if (typeof _satellite !== "undefined" && _satellite.track) {
7566                _satellite.track(definition.trackName);
7567            } else if (enableDebug) {
7568                console.warn(
7569                    "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7570                );
7571            }
7572        },
7573
7574        /**
7575         * Tracks a referral-exit (6c) event for a named buyer-facing outbound CTA.
7576         * @param {string} href
7577         */
7578        trackReferralExit: function (href) {
7579            if (!_isEnabled) {
7580                return;
7581            }
7582            if (isSkippableReferralHref(href)) {
7583                if (enableDebug) {
7584                    console.log(
7585                        "[Ford Direct Analytics] Skipping referral-exit for href:",
7586                        href
7587                    );
7588                }
7589                return;
7590            }
7591
7592            window.digitaldata = buildReferralExitDigitalData();
7593
7594            if (enableDebug) {
7595                logTrackEventDebug(
7596                    "referral-exit",
7597                    window.digitaldata.onclick.referredTo,
7598                    window.digitaldata
7599                );
7600                logEVarSourceMap(window.digitaldata);
7601            }
7602
7603            if (typeof _satellite !== "undefined" && _satellite.track) {
7604                _satellite.track("referral-exit");
7605            } else if (enableDebug) {
7606                console.warn(
7607                    "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7608                );
7609            }
7610        },
7611
7612        /**
7613         * Removes an overlay from the stack when its modal closes. Keyed on purpose: many
7614         * Reveal modals on this site never open an overlay, and a blind pop would discard a
7615         * still-open overlay underneath them.
7616         *
7617         * @param {string} overlayKey
7618         */
7619        closeOverlay: function (overlayKey) {
7620            for (var i = activeOverlayStack.length - 1; i >= 0; i--) {
7621                if (activeOverlayStack[i].overlayKey === overlayKey) {
7622                    activeOverlayStack.splice(i, 1);
7623                    if (enableDebug) {
7624                        console.log(
7625                            "[Ford Direct Analytics] closeOverlay:",
7626                            overlayKey,
7627                            "remaining:",
7628                            activeOverlayStack.length
7629                        );
7630                    }
7631                    return;
7632                }
7633            }
7634        },
7635
7636        /**
7637         * Tracks a form-submit (9c) event for Ford Direct Analytics.
7638         *
7639         * @param {Object} event - Visor event with formContext in event.data
7640         */
7641        trackFormSubmit: function (event) {
7642            if (!_isEnabled) {
7643                if (enableDebug) {
7644                    console.log(
7645                        "[Ford Direct Analytics] Disabled - skipping form-submit"
7646                    );
7647                }
7648                return;
7649            }
7650
7651            var formContext =
7652                event && event.data && event.data.formContext;
7653            if (!formContext) {
7654                if (enableDebug) {
7655                    console.log(
7656                        "[Ford Direct Analytics] Skipping form-submit - no formContext"
7657                    );
7658                }
7659                return;
7660            }
7661
7662            var digitaldata = buildFormSubmitDigitalData(
7663                formContext,
7664                event
7665            );
7666            if (!digitaldata) {
7667                if (enableDebug) {
7668                    console.log(
7669                        "[Ford Direct Analytics] Skipping form-submit - could not build digitaldata"
7670                    );
7671                }
7672                return;
7673            }
7674
7675            window.digitaldata = digitaldata;
7676
7677            if (enableDebug) {
7678                logTrackEventDebug("form-submit", formContext, window.digitaldata);
7679                logEVarSourceMap(window.digitaldata);
7680            }
7681
7682            if (typeof _satellite !== "undefined" && _satellite.track) {
7683                if (enableDebug) {
7684                    console.log(
7685                        "[Ford Direct Analytics] Calling _satellite.track('form-submit')"
7686                    );
7687                }
7688                _satellite.track("form-submit");
7689            } else if (enableDebug) {
7690                console.warn(
7691                    "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7692                );
7693            }
7694        },
7695
7696        /**
7697         * Tracks a click-to-call (5c) event for Ford Direct Analytics.
7698         *
7699         * @param {Object} event - The visor CALL_US_NOW_IM_INTERESTED event
7700         */
7701        trackClickToCall: function (event) {
7702            if (!_isEnabled) {
7703                if (enableDebug) {
7704                    console.log(
7705                        "[Ford Direct Analytics] Disabled - skipping click-to-call"
7706                    );
7707                }
7708                return;
7709            }
7710
7711            // Homepage phone is a content action (1c), not click-to-call (5c) - Page Variables
7712            // row 16. That row leaves every event column blank, so the click-to-call event and
7713            // its eVar48/49 intentionally do not fire here.
7714            if (getPageContextKeyFromLastLoad() === "Home") {
7715                FordDirectAnalytics.trackClick(
7716                    event,
7717                    "CALL_US_NOW_IM_INTERESTED"
7718                );
7719                return;
7720            }
7721
7722            var digitaldata = buildClickToCallDigitalData(event);
7723            window.digitaldata = digitaldata;
7724
7725            if (enableDebug) {
7726                var toolDescriptor =
7727                    digitaldata &&
7728                    digitaldata.page &&
7729                    digitaldata.page.toolDescriptor;
7730                logTrackEventDebug("click-to-call", toolDescriptor, window.digitaldata);
7731                logEVarSourceMap(window.digitaldata);
7732            }
7733
7734            if (typeof _satellite !== "undefined" && _satellite.track) {
7735                if (enableDebug) {
7736                    console.log(
7737                        "[Ford Direct Analytics] Calling _satellite.track('click-to-call')"
7738                    );
7739                }
7740                _satellite.track("click-to-call");
7741            } else if (enableDebug) {
7742                console.warn(
7743                    "[Ford Direct Analytics] _satellite not available - DTM script may not have loaded"
7744                );
7745            }
7746        },
7747    };
7748
7749    document.addEventListener("click", function (e) {
7750        var target = e.target && e.target.closest && e.target.closest(REFERRAL_EXIT_SELECTOR);
7751        if (!target) {
7752            return;
7753        }
7754        FordDirectAnalytics.trackReferralExit(target.href);
7755    });
7756
7757    // Export to modules
7758    modules.FordDirectAnalytics = FordDirectAnalytics;
7759})(window.wts, window.wts.modules);
7760;
7761(function (wts, modules) {
7762    /**
7763    * The location of our visor event listeners
7764    * Marketing libraries should hook into visor from here
7765    *
7766    * @example
7767    * wts.visor.on(UI_EVENTS.PAGE_VIEW, function(e){
7768    *     //...
7769    * });
7770    *
7771    */
7772
7773    var UI_EVENTS = wts.visor.Events;
7774    var AscTracking = modules.AscTracking;
7775    var AdobeAnalytics = modules.AdobeAnalytics;
7776    var FordDirectAnalytics = modules.FordDirectAnalytics;
7777    var getClientData = modules.getClientData;
7778
7779    // this script is rendered before the @data section, so we must defer
7780    var pageData = getClientData("#page-context-data");
7781
7782    // TODO: this is used by setAnalyticsCTAEvent
7783    //var analyticsData = getClientData("#ga-data");
7784    var vehicleCategory = pageData.isVast ? "VAST" : "Vehicle";
7785
7786    const trackAdobeEvent = window.TrackAdobeEvent || console.log;
7787
7788    wts.visor.on(UI_EVENTS.PAGE_VIEW, function (event) {
7789        var MarketingEvents = modules.MarketingEvents;
7790        var VehicleViewData = modules.VehicleViewData;
7791        var data = getClientData("#marketing-events-data");
7792
7793        if (event.data.context === "VDP") {
7794
7795            if (data.stockType !== "Used") {
7796                data.stockType = "New";
7797            }
7798
7799            var vehicleViewData = new VehicleViewData(
7800                data.inventoryID,
7801                data.dealerInventoryNumericID,
7802                data.zipCode,
7803                data.make,
7804                data.model,
7805                data.year,
7806                data.stockType,
7807                data.color,
7808                data.transmissionType,
7809                data.bodyStyle,
7810                data.fuelType,
7811                data.driveTrain,
7812                data.price,
7813                data.trim,
7814                data.mileage,
7815                data.vin,
7816                data.fuelType
7817            );
7818
7819            MarketingEvents.vehicleView(vehicleViewData);
7820            AscTracking.trackPageViewEvent(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_ITEM_PAGEVIEW));
7821        } else if (event.data.context === "VAST" || event.data.context === "VSP") {
7822            AscTracking.trackPageViewEvent(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_ITEMLIST_PAGEVIEW));
7823        }
7824
7825        const ascEventData = {
7826            pageType: event.data.context
7827        };
7828
7829        AscTracking.trackPageViewEvent(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_PAGEVIEW, { detail: ascEventData }));
7830    });
7831
7832    wts.visor.on(UI_EVENTS.PAGE_LOAD, function (event) {
7833        // Check if Ford Direct Analytics is enabled - if so, use it exclusively (mutual exclusivity)
7834        var fordDirectSetup = getClientData("#ford-direct-analytics-setup");
7835        var isFordDirectEnabled = fordDirectSetup && fordDirectSetup.isEnabled;
7836
7837        if (isFordDirectEnabled) {
7838            // Ford Direct Analytics - exclusive path when enabled
7839            if (FordDirectAnalytics) {
7840                FordDirectAnalytics.trackPageLoad(event);
7841            }
7842        } else {
7843            // Existing Adobe Analytics (Stellantis) - only runs when Ford Direct is disabled
7844            var adobePageData = AdobeAnalytics.getAdobePageLoadEventData(event);
7845            if (adobePageData) {
7846                trackAdobeEvent(new CustomEvent("page-load", { detail: adobePageData }));
7847            }
7848        }
7849    });
7850
7851    wts.visor.on(UI_EVENTS.INTERACTION_CLICK, function (event) {
7852        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(event);
7853        if (adobePageData) {
7854            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
7855        }
7856
7857        const ascEventData = {
7858            elementText: event.data.currentTarget.innerText.trim(),
7859            elementTitle: event.data.currentTarget.dataset.ascElementTitle || "",
7860            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.ACCESSABILITY_TOOL,
7861            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.CTA_BUTTON,
7862            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
7863            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.SEARCH
7864        };
7865        AscTracking.trackInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: ascEventData }));
7866    });
7867
7868    wts.visor.on(UI_EVENTS.SEE_MORE_INTERACTION_CLICK, function (event) {
7869        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(event);
7870        if (adobePageData) {
7871            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
7872        }
7873    });
7874
7875    wts.visor.on(UI_EVENTS.GENERAL_LAYOUT_INTERACTION_CLICK, function (event) {
7876        var adobeEventData = AdobeAnalytics.getAdobeEventDataFromGeneralLayoutTarget(event.data.currentTarget);
7877
7878        if (adobeEventData) {
7879            var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(adobeEventData);
7880            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
7881        }
7882
7883        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: event.data.currentTarget }));
7884
7885        // Ford Direct Analytics - parallel tracking path
7886        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
7887            FordDirectAnalytics.trackClick(event, "GENERAL_LAYOUT_INTERACTION_CLICK");
7888        }
7889    });
7890
7891    wts.visor.on(UI_EVENTS.VOCATIONAL_CAROUSEL_PAGE_CHANGE, function (event) {
7892        var adobeEventData = AdobeAnalytics.getAdobeEventDataFromVocationalPageChange(event.data.pageChangeEventData);
7893
7894        if (adobeEventData) {
7895            var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(adobeEventData);
7896            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
7897        }
7898
7899        AscTracking.trackVocationalCarouselPageChange(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: event.data.pageChangeEventData }));
7900    });
7901
7902    wts.visor.on(UI_EVENTS.PAGINATION_CONTROL_CHANGE, function (event) {
7903        var adobeEventData = AdobeAnalytics.getAdobeEventDataFromPaginationControls(event.data.currentTarget);
7904
7905        if (adobeEventData) {
7906            var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(adobeEventData);
7907            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
7908        }
7909
7910        AscTracking.trackPaginationControlChange(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_ELEMENT_CONFIGURATION, { detail: event.data.currentTarget }));
7911    });
7912
7913    wts.visor.on(UI_EVENTS.VSP_SEARCH_COMPLETE, function (e) {
7914        var viewModel = e.data.viewModel;
7915
7916        // Ford Direct Analytics - fire PAGE_LOAD for search results update (same pattern as PAGE_LOAD event)
7917        var fordDirectSetup = getClientData("#ford-direct-analytics-setup");
7918        var isFordDirectEnabled = fordDirectSetup && fordDirectSetup.isEnabled;
7919
7920        if (isFordDirectEnabled && FordDirectAnalytics && pageData && pageData.isVSP) {
7921            var pathname = window.location.pathname;
7922            var isRootHome = pathname === "/" || pathname === "";
7923
7924            if (!isRootHome) {
7925                // Pass totalResults for searchDepth field per Ford spec
7926                // TotalResults is nested under viewModel.State (see truck-search.js)
7927                var totalResults =
7928                    viewModel &&
7929                    viewModel.State &&
7930                    viewModel.State.TotalResults
7931                        ? viewModel.State.TotalResults
7932                        : 0;
7933                FordDirectAnalytics.trackPageLoad({
7934                    data: {
7935                        pageContext: "SearchResults",
7936                        totalResults: totalResults,
7937                        searchedChassisModel:
7938                            (viewModel && viewModel.ResultsSearchedChassisModel) ||
7939                            "",
7940                        searchedBodyType:
7941                            (viewModel && viewModel.ResultsSearchedBodyType) || "",
7942                    },
7943                });
7944            }
7945        }
7946
7947        // Fire both the itemlist and the generic pageview events, mirroring the initial page load behavior
7948        const ascEventData = {
7949            pageType: viewModel.Layout.FeatureArea
7950        };
7951
7952        AscTracking.trackPageViewEvent(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_ITEMLIST_PAGEVIEW, { detail: ascEventData }));
7953        AscTracking.trackPageViewEvent(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_PAGEVIEW, { detail: ascEventData }));
7954    });
7955
7956    wts.visor.on(UI_EVENTS.FORM_START, function (event) {
7957        var adobeEventData = AdobeAnalytics.getAdobeEventDataFromFormStart(event.data.currentTarget);
7958
7959        if (adobeEventData) {
7960            var adobePageData = AdobeAnalytics.getAdobeFormStartClickEventData(adobeEventData);
7961            trackAdobeEvent(new CustomEvent("form-start", { detail: adobePageData }));
7962        }
7963
7964        AscTracking.initializeFormStart(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT, { detail: { currentTarget: event.data.currentTarget } }));
7965    });
7966
7967    wts.visor.on(UI_EVENTS.FORM_ENGAGE, function (event) {
7968        AscTracking.trackFormEngage(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT, { detail: { currentTarget: event.data.currentTarget } }));
7969    });
7970
7971    wts.visor.on(UI_EVENTS.CALL_US_NOW_IM_INTERESTED, function (event) {
7972
7973        if (!wts.user.isTruckProOrAbove) {
7974
7975            // CASE: on different pages this has different categories
7976            var category = "Vehicle";
7977            var inventoryID = event.data.inventoryID;
7978            var gaPhoneLabel =
7979                event.data &&
7980                event.data.gaPhoneLabel &&
7981                String(event.data.gaPhoneLabel).trim();
7982            var commPhoneOverride =
7983                event.data &&
7984                event.data.commPhoneNumber &&
7985                String(event.data.commPhoneNumber).trim();
7986
7987            if (typeof window.analyticsDimensions !== "undefined") {
7988                category = "Search";
7989            }
7990
7991            getLabelAndDimensions(inventoryID, function (label, dimensions) {
7992                var eventLabel = gaPhoneLabel || commPhoneOverride || label;
7993                window.TrackEvent(category, "Phone Call", eventLabel, dimensions);
7994            });
7995        }
7996
7997        // Ford Direct Analytics - parallel tracking path
7998        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
7999            var modalContext = event.data && event.data.modalContext;
8000            if (modalContext) {
8001                FordDirectAnalytics.trackClick(
8002                    event,
8003                    "CALL_US_NOW_IM_INTERESTED",
8004                    modalContext
8005                );
8006            } else {
8007                FordDirectAnalytics.trackClickToCall(event);
8008            }
8009        }
8010    });
8011
8012    wts.visor.on(UI_EVENTS.CALLBACK_REQUEST_CLICK, function (event) {
8013        AscTracking.initializeFormStart(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT, {
8014            detail: {
8015                currentTarget: event.data.currentTarget
8016            }
8017        }));
8018
8019        // Fire the start engagement event
8020        AscTracking.trackFormEngage(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT, {
8021            detail: {
8022                currentTarget: event.data.currentTarget,
8023                formName: 'Callback Form',
8024                eventName: 'Request a Callback',
8025                commStatus: 'start'
8026            }
8027        }));
8028    });
8029
8030    wts.visor.on(UI_EVENTS.CLICK_SUPPORT_LINK, function () {
8031        TrackEvent("Support", "Support Link Clicked", "");
8032        window.lastEvent = "Support Link Clicked";
8033    });
8034
8035    // Ford Direct Analytics - support form submission tracking
8036    wts.visor.on(UI_EVENTS.SUBMIT_SUPPORT_FORM, function (event) {
8037        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8038            FordDirectAnalytics.trackClick(event, "SUBMIT_SUPPORT_FORM");
8039        }
8040    });
8041
8042    // Ford Direct Analytics - Milestone 4 global-overlay open/close
8043    wts.visor.on(UI_EVENTS.FORD_OVERLAY_OPEN, function (event) {
8044        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8045            FordDirectAnalytics.trackOverlay(event.data);
8046        }
8047    });
8048
8049    wts.visor.on(UI_EVENTS.FORD_OVERLAY_CLOSE, function (event) {
8050        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8051            FordDirectAnalytics.closeOverlay(event.data && event.data.overlayKey);
8052        }
8053    });
8054
8055    wts.visor.on(UI_EVENTS.SUBMIT_VEHICLE_INQUIRY_FORM, function (e) {
8056        var inventoryID = e.data.InventoryID;
8057        var pathType = e.data.PathType;
8058        var messageType = e.data.MessageType;
8059
8060        modules.MarketingEvents.vehicleLead(new modules.VehicleLeadData(inventoryID));
8061
8062        var formName = { form_name: messageType };
8063
8064        if (inventoryID) {
8065            getLabelAndDimensions(inventoryID, function (label, dimensions) {
8066                dimensions = Object.assign({
8067                    'path_type': pathType,
8068                    'form_name': messageType
8069                }, dimensions);
8070
8071                window.TrackEvent(vehicleCategory, "New Lead", label, dimensions);
8072            });
8073        }
8074        else if (window.isGarage) {
8075            window.TrackEvent("Garage", "New Lead", null, formName);
8076        }
8077        else if (window.isAbout) {
8078            window.TrackEvent("About", "New Lead", null, formName);
8079        }
8080        else if (window.isCustomPage) {
8081            window.TrackEvent(window.customPageName, "New Lead", null, formName);
8082        }
8083        else if (pageData.isVSP) {
8084            // a VSP without an inventoryID must be No Results... right?
8085            window.TrackEvent("Search", "New Lead", null, formName);
8086        }
8087
8088        return modules.MarketingEvents.wait();
8089    });
8090
8091    wts.visor.on(UI_EVENTS.SUBMIT_VEHICLE_INQUIRY_FORM_ERROR, function (event) {
8092        trackAdobeEvent(new CustomEvent("error-display", {
8093            detail: {
8094                error: {
8095                    message: event.data.message,
8096                    type: event.data.type
8097                }
8098            }
8099        }));
8100    });
8101
8102    wts.visor.on(UI_EVENTS.SUBMIT_LOAD_GARAGE_FORM, function () {
8103        TrackEvent("Sign Up", "New Lead");
8104    });
8105
8106    wts.visor.on(UI_EVENTS.WAIT_FOR_FORM_CONFIRMATION, function (e) {
8107        var data = e.data;
8108        var inventoryID = data.inventoryID;
8109        var messageType = data.messageType;
8110        var dealerMessageID = data.dealerMessageID;
8111
8112        var adobePageData = AdobeAnalytics.getAdobeFormSubmitData(e);
8113        if (adobePageData) {
8114            trackAdobeEvent(new CustomEvent("form-submit", { detail: adobePageData }));
8115        }
8116        
8117        AscTracking.trackFormSubmission(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_SUBMISSION, { detail: { uniqueID: data.uniqueID, inventoryID: data.inventoryID, messageType: messageType } }));
8118
8119        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8120            if (!data.isSupportRequest && !data.isNoResultsMessage) {
8121                FordDirectAnalytics.trackFormSubmit(e);
8122            }
8123        }
8124
8125        if (data.isSupportRequest) {
8126            TrackEvent("Support", "Support Link Clicked Confirmed");
8127            return;
8128        }
8129
8130        var action = (function () {
8131
8132            if (data.isCallbackMessage) {
8133                return "Phone Dealer Confirmed";
8134            }
8135            if (data.isNoResultsMessage) {
8136                return "No Results Confirmed";
8137            }
8138
8139            if (window.lastEvent === "Tell Me More" ||
8140                window.lastEvent === "I'm Interested" ||
8141                window.lastEvent === "Email Dealer" ||
8142                window.lastEvent === "Bare Chassis Request") {
8143                return window.lastEvent + " Confirmed";
8144            }
8145
8146            return "";
8147        })();
8148
8149        var formName = { form_name: messageType };
8150
8151        if (window.lastEvent === "Support Link Clicked" || window.lastEvent === "Support Link Clicked Confirmed") {
8152            TrackEvent("Support", action, "", formName);
8153        }
8154        else if (inventoryID) {
8155            getLabelAndDimensions(inventoryID, function (label, dimensions) {
8156                dimensions = Object.assign({
8157                    'form_name': messageType,
8158                    'dealer_message_id': dealerMessageID, // TODO: need to decide the dimension name
8159                }, dimensions);
8160                window.TrackEvent(vehicleCategory, action, label, dimensions);
8161            });
8162        }
8163        else if (typeof window.isGarage !== "undefined") {
8164            // trackGarageEvent(action);
8165            TrackEvent("Garage", action, null, formName);
8166        }
8167        else if (typeof window.isAbout !== "undefined") {
8168            // trackAboutEvent(action);
8169            TrackEvent("About", action, null, formName);
8170        }
8171        else if (typeof window.isEZOrder !== "undefined") {
8172            // trackEZOrderEvent(action)
8173            TrackEvent("EZOrder", action, null, formName);
8174        }
8175        else if (typeof window.isCustomOrder !== "undefined") {
8176            // trackCustomOrderEvent(action)
8177            TrackEvent("CustomOrder", action, null, formName);
8178        }
8179        else if (window.isCustomPage) {
8180            TrackEvent(window.customPageName, action, null, formName);
8181        }
8182        else if (typeof trackSearchEvent === "function") {
8183            // there are 2 trackSearchEvents — one for searchv1 and one for v2 (in _InventoryLayout)
8184            // trackSearchEvent(action);
8185            TrackEvent("Search", action, null, formName);
8186        }
8187
8188    });
8189
8190    wts.visor.on(UI_EVENTS.OPEN_VEHICLE_INQUIRY_FORM, function (e) {
8191        // TIPS FOR WIZARDS:
8192        // When interacting with the CTA on VSP we trigger "Tell Me More"
8193        //                           and on VDP we trigger "I'm Interested"
8194        //
8195        var inventoryID = e.data.inventoryID;
8196
8197        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8198            if (pageData.isVSP) {
8199                window.TrackEvent(vehicleCategory, "Tell Me More", label, dimensions);
8200            }
8201            else if (pageData.isVDP) {
8202                window.TrackEvent(vehicleCategory, "I'm Interested", label, dimensions);
8203            }
8204        });
8205    });
8206
8207    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_INTERNAL_LINK_CLICK, function (e) {
8208        var inventoryID = e.data.inventoryID;
8209        var attributeName = e.data.attributeName;
8210
8211        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8212            window.TrackEvent(vehicleCategory, "VDP Internal Link Click", `${attributeName} - ${label}`, dimensions);
8213        });
8214    });
8215
8216    wts.visor.on(UI_EVENTS.CLICK_FEATURED_PHOTO_THUMB, function (e) {
8217        TrackEvent(vehicleCategory, "Click Thumbnail [C]", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8218
8219        const ascEventData = {
8220            elementText: e.data.description,
8221            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8222            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8223            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.OPEN,
8224            inventoryID: e.data.inventoryID,
8225            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.IMAGE,
8226            linkUrl: null,
8227            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8228        };
8229        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8230
8231        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8232            FordDirectAnalytics.trackClick(e, "PHOTO_GALLERY_CLICK_THUMBNAIL");
8233        }
8234    });
8235
8236    wts.visor.on(UI_EVENTS.SUBMIT_CUSTOM_FORM, function (e) {
8237        var action = "Submit Form";
8238        var category = "Custom Form";
8239        var label = `${e.data.dealerName} - ${e.data.formName}`;
8240        TrackEvent(category, action, label, { dealer_message_id: e.data.dealerMessageID });
8241
8242        AscTracking.trackFormSubmission(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_SUBMISSION, {
8243            detail: {
8244                uniqueID: e.data.dealerMessageID,
8245                messageType: e.data.formName,
8246                formElement: e.data.formElement
8247            }
8248        }));
8249    });
8250
8251    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_OPEN, function (e) {
8252        var index = e.data.current;
8253        var isVDP = e.data.isVDP;
8254        var inventoryID = e.data.inventoryID;
8255
8256        var isBodySide = index === 1;
8257
8258        var side = isVDP ? "[D]" : "[C]";
8259        if (isBodySide) {
8260            side = isVDP ? "[DB]" : "[B]";
8261        }
8262
8263        var action = ["View Photos ", side].join("");
8264
8265        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8266            window.TrackEvent(vehicleCategory, action, label, dimensions);
8267        });
8268
8269        const ascEventData = {
8270            elementText: e.data.description,
8271            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8272            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8273            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.OPEN,
8274            inventoryID: e.data.inventoryID,
8275            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.IMAGE,
8276            linkUrl: null,
8277            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8278        };
8279        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8280    });
8281
8282    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_CLOSE, function (e) {
8283
8284        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8285        if (adobePageData) {
8286            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8287        }
8288
8289        const ascEventData = {
8290            elementText: e.data.description,
8291            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8292            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8293            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.CLOSE,
8294            inventoryID: e.data.inventoryID,
8295            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8296            linkUrl: null,
8297            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8298        };
8299        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8300    });
8301
8302    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_NEXT_PHOTO, function (e) {
8303        var inventoryID = e.data.inventoryID;
8304
8305        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8306            window.TrackEvent(vehicleCategory, "View Next Photo", label, dimensions);
8307        });
8308
8309        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8310        if (adobePageData) {
8311            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8312        }
8313
8314        const ascEventData = {
8315            elementText: e.data.description,
8316            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8317            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8318            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.INCREASE,
8319            inventoryID: e.data.inventoryID,
8320            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8321            linkUrl: null,
8322            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8323        };
8324        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8325
8326        // Ford Direct Analytics - parallel tracking path
8327        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8328            FordDirectAnalytics.trackClick(e, "PHOTO_GALLERY_NEXT_PHOTO");
8329        }
8330    });
8331
8332    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_PREVIOUS_PHOTO, function (e) {
8333        var inventoryID = e.data.inventoryID;
8334
8335        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8336            window.TrackEvent(vehicleCategory, "View Previous Photo", label, dimensions);
8337        });
8338
8339        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8340        if (adobePageData) {
8341            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8342        }
8343
8344        const ascEventData = {
8345            elementText: e.data.description,
8346            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8347            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8348            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.DECREASE,
8349            inventoryID: e.data.inventoryID,
8350            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8351            linkUrl: null,
8352            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8353        };
8354        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8355
8356        // Ford Direct Analytics - parallel tracking path
8357        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8358            FordDirectAnalytics.trackClick(e, "PHOTO_GALLERY_PREVIOUS_PHOTO");
8359        }
8360    });
8361
8362    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_CLICK_THUMBNAIL, function (e) {
8363        var inventoryID = e.data.inventoryID;
8364
8365        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8366            window.TrackEvent(vehicleCategory, "View Photo via Thumbnail", label, dimensions);
8367        });
8368
8369        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8370        if (adobePageData) {
8371            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8372        }
8373
8374        const ascEventData = {
8375            elementText: e.data.description,
8376            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8377            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8378            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.OPEN,
8379            inventoryID: e.data.inventoryID,
8380            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.IMAGE,
8381            linkUrl: null,
8382            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8383        };
8384        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8385
8386        // Ford Direct Analytics - parallel tracking path
8387        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8388            FordDirectAnalytics.trackClick(e, "PHOTO_GALLERY_CLICK_THUMBNAIL");
8389        }
8390    });
8391
8392    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_NEXT_PAGE, function (e) {
8393        var inventoryID = e.data.inventoryID;
8394
8395        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8396            window.TrackEvent(vehicleCategory, "Page Thumbnails Next", label, dimensions);
8397        });
8398
8399        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8400        if (adobePageData) {
8401            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8402        }
8403
8404        const ascEventData = {
8405            elementText: e.data.description,
8406            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8407            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8408            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.INCREASE,
8409            inventoryID: e.data.inventoryID,
8410            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8411            linkUrl: null,
8412            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8413        };
8414        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8415    });
8416
8417    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_PREVIOUS_PAGE, function (e) {
8418        var inventoryID = e.data.inventoryID;
8419
8420        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8421            window.TrackEvent(vehicleCategory, "Page Thumbnails Previous", label, dimensions);
8422        });
8423
8424        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8425        if (adobePageData) {
8426            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8427        }
8428
8429        const ascEventData = {
8430            elementText: e.data.description,
8431            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8432            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8433            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.DECREASE,
8434            inventoryID: e.data.inventoryID,
8435            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8436            linkUrl: null,
8437            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8438        };
8439        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8440    });
8441
8442    wts.visor.on(UI_EVENTS.FEATURED_PHOTO_GALLERY_NEXT_PHOTO, function (e) {
8443        var inventoryID = e.data.inventoryID;
8444
8445        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8446            window.TrackEvent(vehicleCategory, "View Next Photo", label, dimensions);
8447        });
8448
8449        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8450        if (adobePageData) {
8451            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8452        }
8453
8454        const ascEventData = {
8455            elementText: e.data.description,
8456            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8457            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8458            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.INCREASE,
8459            inventoryID: e.data.inventoryID,
8460            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8461            linkUrl: null,
8462            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8463        };
8464        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8465
8466        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8467            FordDirectAnalytics.trackClick(e, "PHOTO_GALLERY_NEXT_PHOTO");
8468        }
8469    });
8470
8471    wts.visor.on(UI_EVENTS.FEATURED_PHOTO_GALLERY_PREVIOUS_PHOTO, function (e) {
8472        var inventoryID = e.data.inventoryID;
8473
8474        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8475            window.TrackEvent(vehicleCategory, "View Previous Photo", label, dimensions);
8476        });
8477
8478        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8479        if (adobePageData) {
8480            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8481        }
8482
8483        const ascEventData = {
8484            elementText: e.data.description,
8485            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8486            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8487            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.DECREASE,
8488            inventoryID: e.data.inventoryID,
8489            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8490            linkUrl: null,
8491            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8492        };
8493        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8494
8495        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8496            FordDirectAnalytics.trackClick(e, "PHOTO_GALLERY_PREVIOUS_PHOTO");
8497        }
8498    });
8499
8500    wts.visor.on(UI_EVENTS.FEATURED_PHOTO_GALLERY_VIEW_PHOTO, function (e) {
8501        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8502            FordDirectAnalytics.trackClick(e, "FEATURED_PHOTO_GALLERY_VIEW_PHOTO");
8503        }
8504    });
8505
8506    wts.visor.on(UI_EVENTS.FEATURED_PHOTO_GALLERY_NEXT_PAGE, function (e) {
8507        var inventoryID = e.data.inventoryID;
8508
8509        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8510            window.TrackEvent(vehicleCategory, "Page Thumbnails Next", label, dimensions);
8511        });
8512
8513        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8514        if (adobePageData) {
8515            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8516        }
8517
8518        const ascEventData = {
8519            elementText: e.data.description,
8520            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8521            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8522            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.INCREASE,
8523            inventoryID: e.data.inventoryID,
8524            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8525            linkUrl: null,
8526            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8527        };
8528        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8529    });
8530
8531    wts.visor.on(UI_EVENTS.FEATURED_PHOTO_GALLERY_PREVIOUS_PAGE, function (e) {
8532        var inventoryID = e.data.inventoryID;
8533
8534        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8535            window.TrackEvent(vehicleCategory, "Page Thumbnails Previous", label, dimensions);
8536        });
8537
8538        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8539        if (adobePageData) {
8540            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8541        }
8542
8543        const ascEventData = {
8544            elementText: e.data.description,
8545            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8546            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8547            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.DECREASE,
8548            inventoryID: e.data.inventoryID,
8549            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8550            linkUrl: null,
8551            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8552        };
8553        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8554    });
8555
8556    wts.visor.on(UI_EVENTS.PHOTO_GALLERY_VIEW_PHOTO_NEW_TAB, function (e) {
8557        getLabelAndDimensions(e.data.inventoryID, function (label, dimensions) {
8558            window.TrackEvent(vehicleCategory, "Open in New Tab", label, dimensions);
8559        });
8560
8561        const ascEventData = {
8562            elementText: e.data.description,
8563            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.GALLERY,
8564            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8565            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.OPEN,
8566            inventoryID: e.data.inventoryID,
8567            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.HYPERLINK,
8568            linkUrl: null,
8569            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.GALLERY,
8570        };
8571        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8572    });
8573
8574    wts.visor.on(UI_EVENTS.CLICK_CONDITION_LINK, function (e) {
8575        TrackEvent(vehicleCategory, "View Condition", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8576    });
8577
8578    wts.visor.on(UI_EVENTS.CLICK_OUTBOUND_LINK, function (e) {
8579        var linkURL = e.data;
8580        TrackEvent(vehicleCategory, "View Outbound Link", linkURL, window.myAnalyticsDimensions);
8581
8582        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8583            FordDirectAnalytics.trackReferralExit(linkURL);
8584        }
8585    });
8586
8587    wts.visor.on(UI_EVENTS.CLICK_WINDOWSTICKER_LINK, function (e) {
8588        TrackEvent(vehicleCategory, "Window Sticker", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8589
8590        // Ford Direct Analytics - parallel tracking path
8591        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8592            FordDirectAnalytics.trackToolEvent("window-sticker");
8593        }
8594    });
8595
8596    wts.visor.on(UI_EVENTS.CLICK_BODY_DATASHEET_LINK, function (e) {
8597        var category = pageData.isVast ? "VAST" : "Branding";
8598        TrackEvent(category, "Click Datasheet", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8599
8600        // Ford Direct Analytics - parallel tracking path
8601        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8602            FordDirectAnalytics.trackToolEvent("download-vehicle-details");
8603        }
8604    });
8605
8606    wts.visor.on(UI_EVENTS.CLICK_CHASSIS_DATASHEET_LINK, function (e) {
8607        var category = pageData.isVast ? "VAST" : "Branding";
8608        var resourceURL = e.data;
8609        TrackEvent(category, "Click Datasheet", resourceURL, window.myAnalyticsDimensions);
8610
8611        // Ford Direct Analytics - parallel tracking path
8612        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8613            FordDirectAnalytics.trackToolEvent("download-vehicle-details");
8614        }
8615    });
8616
8617    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_TRACK_DEALER_VIDEO_EVENT, function (e) {
8618        var category = pageData.isVast ? "VAST" : "Vehicle";
8619        TrackEvent(category, "View Dealer Video", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8620
8621        // Ford Direct Analytics - parallel tracking path
8622        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8623            FordDirectAnalytics.trackClick(e, "VEHICLE_DETAILS_TRACK_DEALER_VIDEO_EVENT");
8624        }
8625    });
8626
8627    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_TRACK_BODY_DETAILS_VIDEO_EVENT, function (e) {
8628        var category = pageData.isVast ? "VAST" : "Vehicle";
8629        TrackEvent(category, "View Body Details Video", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8630    });
8631
8632    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_TRACK_BODY_MANUFACTURER_VIDEO_EVENT, function (e) {
8633        var category = pageData.isVast ? "VAST" : "Vehicle";
8634        TrackEvent(category, "View Body Manufacturer Video", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8635    });
8636
8637    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_CLOSE_VIDEO_EVENT, function (e) {
8638        var category = pageData.isVast ? "VAST" : "Vehicle";
8639        TrackEvent(category, "Close Video", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8640
8641        const ascEventData = {
8642            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.VIDEO,
8643            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8644            eventActionResult: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.EXIT,
8645            inventoryID: e.data.inventoryID,
8646            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.NAV_ICON,
8647            linkUrl: null,
8648            mediaType: AscTracking.ASC_MAPPINGS.MEDIA_TYPE.VIDEO,
8649        };
8650        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: ascEventData }));
8651    });
8652
8653    wts.visor.on(UI_EVENTS.VSP_CHANGE_ITEMS_PER_PAGE, function (e) {
8654        var vehicleCategory = pageData.isVast ? "VAST" : "Search";
8655        TrackEvent(vehicleCategory, "Items Per Page", e.data.data);
8656    });
8657
8658    wts.visor.on(UI_EVENTS.VSP_CHANGE_SORT, function (e) {
8659        var vehicleCategory = pageData.isVast ? "VAST" : "Search";
8660        TrackEvent(vehicleCategory, "Sort By", e.data.data);
8661    });
8662
8663    wts.visor.on(UI_EVENTS.CLICK_AUCTION_LINK, function (e) {
8664        TrackEvent(vehicleCategory, "View Auction", window.myAnalyticsLabel, window.myAnalyticsDimensions);
8665    });
8666
8667    wts.visor.on(UI_EVENTS.CLICK_VEHICLE_LISTING_TITLE, function (e) {
8668        var inventoryID = e.data;
8669
8670        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8671            if (pageData.isVast) {
8672                window.TrackEvent(vehicleCategory, "View VAST VDP - Title", label, dimensions);
8673            }
8674            else {
8675                window.TrackEvent(vehicleCategory, "More Info - Title", label, dimensions);
8676            }
8677        });
8678    });
8679
8680    wts.visor.on(UI_EVENTS.CLICK_VEHICLE_LISTING_PHOTO, function (e) {
8681        var isChassisSide = e.data.isChassisSide;
8682        var inventoryID = e.data.inventoryID;
8683        // when clicking a link, the chassis side has [C] and the body side has [B]
8684        var side = isChassisSide ? "[C]" : "[B]";
8685
8686        var action = "{action} - Photo {side}".replace("{side}", side);
8687
8688        getLabelAndDimensions(inventoryID, function (labels, dimensions) {
8689            if (pageData.isVast) {
8690                action = action.replace("{action}", "View VAST VDP")
8691                window.TrackEvent(vehicleCategory, action, labels, dimensions);
8692            }
8693            else {
8694                action = action.replace("{action}", "More Info")
8695                window.TrackEvent(vehicleCategory, action, labels, dimensions);
8696            }
8697        });
8698    });
8699
8700    wts.visor.on(UI_EVENTS.CLICK_VEHICLE_LISTING_SEE_DETAILS, function (e) {
8701        var inventoryID = e.data;
8702        getLabelAndDimensions(inventoryID, function (label, dimensions) {
8703            if (pageData.isVast) {
8704                window.TrackEvent(vehicleCategory, "View VAST VDP - See More Details", label, dimensions);
8705            }
8706            else {
8707                window.TrackEvent(vehicleCategory, "More Info - See More Details", label, dimensions);
8708            }
8709        });
8710    });
8711
8712    wts.visor.on(UI_EVENTS.CLICK_REQUEST_UPFIT, function (e) {
8713        var selectedPackage = e.data;
8714        var label = selectedPackage === null ? "" : selectedPackage;
8715
8716        TrackEvent("RequestAnUpfit", "Request for Upfit With Truck", label);
8717        window.lastEvent = "Request for Upfit With Truck";
8718    });
8719
8720    wts.visor.on(UI_EVENTS.CLICK_REQUEST_UPFIT_INFO, function (e) {
8721        var selectedPackage = e.data;
8722        var label = selectedPackage === null ? "" : selectedPackage;
8723        TrackEvent("LookingForAnUpfit", "View More Info", label);
8724    });
8725
8726    wts.visor.on(UI_EVENTS.CLICK_UPFIT_SPECIFICATION_SHEET, function (e) {
8727        var selectedPackage = e.data;
8728        TrackEvent("LookingForAnUpfit", "Clicked Spec Sheet", selectedPackage);
8729
8730        // Ford Direct Analytics - parallel tracking path
8731        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8732            FordDirectAnalytics.trackToolEvent("download-build-and-get-quote");
8733        }
8734    });
8735
8736    wts.visor.on(UI_EVENTS.CLICK_VDP_COMVOY_WEBSITE_LINK, function (e) {
8737        var linkURL = e.data;
8738        TrackEvent("Vehicle", "Click Dealer Website Link", linkURL);
8739    });
8740
8741    wts.visor.on(UI_EVENTS.SELECT_UPFIT_VOCATION, function (e) {
8742        var vocation = e.data;
8743        TrackEvent("LookingForAnUpfit", "Chose a Vocation", vocation);
8744    });
8745
8746    wts.visor.on(UI_EVENTS.CLICK_REMOVE_SELECTED_PACKAGE, function (e) {
8747        TrackEvent("LookingForAnUpfit", "Browse Other Upfits");
8748    });
8749
8750    wts.visor.on(UI_EVENTS.SELECT_UPFIT_PACKAGE, function (e) {
8751        var selectedPackage = e.data;
8752        TrackEvent("LookingForAnUpfit", "Select Package", selectedPackage);
8753    });
8754
8755    wts.visor.on(UI_EVENTS.SUBMIT_REQUEST_UPFIT_FORM, function (e) {
8756        var selectedPackage = e.data;
8757        window.TrackEvent("RequestAnUpfit", undefined, selectedPackage);
8758    });
8759
8760    wts.visor.on(UI_EVENTS.SELECT_VAN_BUILDER_VOCATION, function (e) {
8761        var vocation = e.data;
8762        window.TrackEvent("VanBuilder", "Chose a Vocation", vocation);
8763    });
8764
8765    wts.visor.on(UI_EVENTS.ABOUT_EVENT, function (e) {
8766        var eventName = e.data.eventName;
8767        var value = e.data.value;
8768
8769        window.TrackEvent("About", eventName, value);
8770        var currentTarget = e.data.value.currentTarget;
8771
8772        if (currentTarget) {
8773            AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: currentTarget }));
8774        }
8775
8776        // Ford Direct Analytics - parallel tracking path
8777        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8778            FordDirectAnalytics.trackClick(e, "ABOUT_EVENT");
8779        }
8780    });
8781
8782    wts.visor.on(UI_EVENTS.EZORDER_EVENT, function (e) {
8783        var category = e.data.category;
8784        var action = e.data.action;
8785        var label = e.data.label;
8786
8787        window.TrackEvent(category, action, label);
8788
8789        ascEventData = {
8790            commType: AscTracking.ASC_MAPPINGS.COMM_TYPE.FORM,
8791            commStatus: action.startsWith("Open") ? AscTracking.ASC_MAPPINGS.COMM_STATUS.START : action.startsWith("Close") ? AscTracking.ASC_MAPPINGS.COMM_STATUS.CLOSE : AscTracking.ASC_MAPPINGS.COMM_STATUS_ENGAGE,
8792            formName: "EZ Order",
8793            currentTarget: e.data.currentTarget
8794        };
8795
8796        AscTracking.trackFormEngage(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT, { detail: ascEventData }));
8797    });
8798
8799    wts.visor.on(UI_EVENTS.CUSTOM_ORDER_EVENT, function (e) {
8800        var category = e.data.category;
8801        var action = e.data.action;
8802        var label = e.data.label;
8803
8804        window.TrackEvent(category, action, label);
8805    });
8806
8807    wts.visor.on(UI_EVENTS.TAYCOR_EVENT, function (e) {
8808        var category = e.data.category;
8809        var action = e.data.action;
8810        var label = e.data.label;
8811
8812        window.TrackEvent(category, action, label);
8813    });
8814
8815    wts.visor.on(UI_EVENTS.CRESTLINE_EVENT, function (e) {
8816        var category = e.data.category;
8817        var action = e.data.action;
8818        var label = e.data.label;
8819
8820        window.TrackEvent(category, action, label);
8821
8822        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
8823    });
8824
8825    wts.visor.on(UI_EVENTS.WEBBUY_EVENT, function (e) {
8826        var category = pageData.isVSP ? "Search" : "Vehicle";
8827        var action = null;
8828        var label = null;
8829
8830        if (e.data) {
8831            var el = e.data;
8832
8833            let title = el.getAttribute("vehicleTitle");
8834            let vin = el.getAttribute("vin");
8835
8836            label = title && vin ? title + " - " + vin : label;
8837
8838            switch (el.innerHTML.toLowerCase()) {
8839                case "buy now":
8840                    action = "Click WebBuy Buy Now CTA";
8841                    break;
8842                case "instant trade offer":
8843                    action = "Click WebBuy Instant Trade Offer CTA";
8844                    break;
8845                case "get financed online":
8846                    action = "Click WebBuy Get Financed Online CTA";
8847                    break;
8848                default:
8849                    // no-op
8850                    break;
8851            }
8852
8853            if (action && label) {
8854                window.TrackEvent(category, action, label);
8855            }
8856
8857            AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data }));
8858        }
8859
8860        
8861    });
8862
8863    wts.visor.on(UI_EVENTS.CVSHOWROOM_EVENT, function (e) {
8864        var category = e.data.category;
8865        var action = e.data.action;
8866        var label = e.data.label;
8867
8868        window.TrackEvent(category, action, label);
8869    });
8870
8871    wts.visor.on(UI_EVENTS.CLICK_CUSTOM_PAGE_EMAIL_DEAILER, function (e) {
8872        var customPageName = e.data.customPageName;
8873        TrackEvent(customPageName, "Email Dealer");
8874
8875        if (!window.asc_datalayer) {
8876            return;
8877        }
8878
8879        var pageType = window.asc_datalayer.page_type;
8880        if (modules.AscDelegatedTracking) {
8881            pageType = modules.AscDelegatedTracking.resolvePageType(document.body) || pageType;
8882        }
8883
8884        AscTracking.fireAscEvent({
8885            event: AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION,
8886            event_owner: window.asc_datalayer.affiliation,
8887            page_type: pageType,
8888            element_text: "Email Dealer",
8889            element_title: "Email Dealer",
8890            element_type: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.FORM,
8891            element_subtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.CTA_BUTTON,
8892            event_action: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8893            event_action_result: AscTracking.ASC_MAPPINGS.EVENT_ACTION_RESULT.POPUP,
8894            form_name: customPageName
8895        });
8896    });
8897
8898    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_TRACK_VEHICLE_EVENT, function (e) {
8899        var eventName = e.data.eventName;
8900        var label = e.data.label;
8901        var dimensions = e.data.dimensions;
8902        TrackEvent("Vehicle", eventName, label, dimensions);
8903
8904        e.data.type = "cta";
8905        e.data.page = "shop-vehicle-detais";
8906        e.data.location = "vehicle-container";
8907
8908        if (eventName === "Print Confirmed") {
8909            e.data.description = "gallery-print";
8910        } else if (eventName.indexOf("Share - ") > -1) {
8911            e.data.description = "social-share";
8912        } else {
8913            // if eventName isn't explicitly handled then dont track the adobe event
8914            return;
8915        }
8916
8917        const adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
8918        if (adobePageData) {
8919            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
8920        }
8921
8922        const ascEventData = {
8923            elementText: e.data.eventName,
8924            elementTitle: e.data.elementTitle,
8925            elementSubtype: AscTracking.ASC_MAPPINGS.ELEMENT_SUBTYPE.HYPERLINK,
8926            elementType: AscTracking.ASC_MAPPINGS.ELEMENT_TYPE.MENU,
8927            eventAction: AscTracking.ASC_MAPPINGS.EVENT_ACTION.CLICK,
8928        };
8929        AscTracking.trackInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MENU_INTERACTION, { detail: ascEventData }));
8930    });
8931
8932    wts.visor.on(UI_EVENTS.TRACK_CLOSE_FORM_EVENT, function (e) {
8933        let inventoryID = e.data.inventoryID;
8934        let eventName = e.data.eventName;
8935
8936        if (inventoryID) {
8937            getLabelAndDimensions(inventoryID, function (label, dimensions) {
8938                window.TrackEvent("Vehicle", eventName, label, dimensions);
8939            });
8940        }
8941        else {
8942            window.TrackEvent("Vehicle", eventName);
8943        }
8944        
8945        AscTracking.trackFormClose(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_FORM_ENGAGEMENT, { detail: e.data }));
8946    });
8947
8948    wts.visor.on(UI_EVENTS.VEHICLE_DETAILS_TRACK_BRANDING_EVENT, function (e) {
8949        var eventName = e.data.eventName;
8950        var label = e.data.label;
8951        var dimensions = e.data.dimensions;
8952        TrackEvent("Branding", eventName, label, dimensions);
8953    });
8954
8955    wts.visor.on(UI_EVENTS.MY_GARAGE_GARAGE_EVENT, function (e) {
8956        var eventName = e.data.eventName;
8957        var value = e.data.value;
8958        TrackEvent("Garage", eventName, value);
8959    });
8960
8961    wts.visor.on(UI_EVENTS.MY_GARAGE_VEHICLE_EVENT, function (e) {
8962        var eventName = e.data.eventName;
8963        var label = e.data.label;
8964        var dimensions = e.data.dimensions;
8965        TrackEvent("Vehicle", eventName, label, dimensions);
8966    });
8967
8968    wts.visor.on(UI_EVENTS.INVENTORY_LAYOUT_TRACK_SEARCH_EVENT, function (e) {
8969        var eventName = e.data.eventName;
8970        var value = e.data.value;
8971
8972        window.TrackEvent("Search", eventName, value);
8973
8974        var currentTarget = e.data.value.currentTarget;
8975        if (currentTarget) {
8976            AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: currentTarget }));
8977        }
8978
8979        // Ford Direct Analytics - parallel tracking path
8980        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
8981            FordDirectAnalytics.trackClick(e, "INVENTORY_LAYOUT_TRACK_SEARCH_EVENT");
8982        }
8983    });
8984
8985    wts.visor.on(UI_EVENTS.INVENTORY_LAYOUT_TRACK_LOCATOR_SEARCH_EVENT, function (e) {
8986        var eventName = e.data.eventName;
8987        var label = e.data.label;
8988        var dimensions = e.data.dimensions;
8989        TrackEvent("Search", eventName, label, dimensions);
8990    });
8991
8992    wts.visor.on(UI_EVENTS.SELECT_DEALER_LOCATOR_SEARCH, function (e) {
8993        var action = e.data.action;
8994        var label = e.data.label;
8995        var dimensions = e.data.dimensions;
8996        window.TrackEvent("Dealer Locator Search", action, label, dimensions);
8997    });
8998
8999    wts.visor.on(UI_EVENTS.SELECT_DEALER_LOCATOR_SEARCH_PAGE, function (e) {
9000        var searchPage = e.data;
9001        window.TrackEvent("Dealer Locator Search", "Go To Page", searchPage);
9002    });
9003
9004    wts.visor.on(UI_EVENTS.SELECT_DEALER_LOCATOR_SEARCH_CONDITIONAL, function (e) {
9005        var eventName = e.data.eventName;
9006        var value = e.data.value;
9007        window.TrackEvent("DealerLocatorSearch", eventName, value);
9008    });
9009
9010    wts.visor.on(UI_EVENTS.TRUCK_SEARCH_EVENT_CONDITIONAL, function (e) {
9011        var eventName = e.data.eventName;
9012        var value = e.data.value;
9013        var category = pageData.isVast ? "VAST" : "Search";
9014        TrackEvent(category, eventName, value);
9015
9016        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9017        if (adobePageData) {
9018            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9019        }
9020    });
9021
9022    wts.visor.on(UI_EVENTS.TRUCK_SEARCH_EVENT_VIN, function (e) {
9023        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9024        if (adobePageData) {
9025            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9026        }
9027    });
9028
9029   wts.visor.on(UI_EVENTS.MILES_SEARCH_CONDITIONAL, function (e) {
9030        var value = e.data.value + " Miles";
9031        var category = pageData.isVast ? "VAST" : "Search";
9032        TrackEvent(category, "Distance", value);
9033
9034       var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9035       if (adobePageData) {
9036           trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9037       }
9038    });
9039
9040    wts.visor.on(UI_EVENTS.LOCATION_SEARCH_CONDITIONAL, function (e) {
9041        var category = pageData.isVast ? "VAST" : "Search";
9042        TrackEvent(category, "Location Entry", e.data.value);
9043    });
9044
9045    wts.visor.on(UI_EVENTS.TRUCK_SEARCH_ZIPCODE_CHANGE, function (e) {
9046        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9047        if (adobePageData) {
9048            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9049        }
9050    });
9051
9052    wts.visor.on(UI_EVENTS.VOCATION_SEARCH_CONDITIONAL, function (e) {
9053        var value = `Vocation: ${e.data.value}`;
9054        TrackEvent("Search", "Click Value", value);
9055    });
9056
9057    wts.visor.on(UI_EVENTS.TRUCK_SEARCH_EVENT_NO_RESULTS, function (e) {
9058        var noResultsEventLabel = e.data.noResultsEventLabel;
9059        var category = pageData.isVast ? "VAST" : "Search";
9060        TrackEvent(category, "No Results", noResultsEventLabel, {});
9061    });
9062
9063    wts.visor.on(UI_EVENTS.TRUCK_SEARCH_CLEAR_FILTERS, function (e) {
9064        var category = pageData.isVast ? "VAST" : "Search";
9065        TrackEvent(category, "Click Clear", "Clear Search Filters");
9066
9067        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9068        if (adobePageData) {
9069            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9070        }
9071
9072        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9073    });
9074
9075    wts.visor.on(UI_EVENTS.CLICK_VEHICLE_AD, function (e) {
9076        var label = e.data.label
9077        var dimensions = e.data.dimensions;
9078        TrackEvent(vehicleCategory, "Ad Click", label, dimensions);
9079
9080        AscTracking.trackMediaInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_MEDIA_INTERACTION, { detail: e.data }));
9081    });
9082
9083    wts.visor.on(UI_EVENTS.CLICK_RECENTLY_VIEWED, function (e) {
9084        var label = e.data.label;
9085        var dimensions = e.data.dimensions;
9086        window.TrackEvent(vehicleCategory, "Recently Viewed Click", label, dimensions);
9087    });
9088
9089    wts.visor.on(UI_EVENTS.CLICK_TRUCK_SEARCH_FACETS_VAST, function (e) {
9090        var keyValue = e.data.keyValue;
9091        TrackEvent("VAST", "Click Value", keyValue);
9092    });
9093
9094    wts.visor.on(UI_EVENTS.CLICK_SHOW_MY_INVENTORY_ON_VAST, function () {
9095        TrackEvent("VAST", "Click Value", "IncludeInventory: My Inventory");
9096    });
9097
9098    wts.visor.on(UI_EVENTS.CLICK_SHOW_SISTER_INVENTORY_ON_VAST, function () {
9099        TrackEvent("VAST", "Click Value", "IncludeInventory: Sister Inventory");
9100    });
9101
9102    wts.visor.on(UI_EVENTS.CLICK_SHOW_GROUP_INVENTORY_ON_VAST, function () {
9103        TrackEvent("VAST", "Click Value", "IncludeInventory: Group Inventory");
9104    });
9105
9106    wts.visor.on(UI_EVENTS.CLICK_AVAILABLE_FOR_PURCHASE_TAB_ON_VAST, function () {
9107        TrackEvent("VAST", "Click Tab", "Available for Purchase");
9108    });
9109
9110    wts.visor.on(UI_EVENTS.CLICK_WHOLE_WTS_NETWORK_TAB_ON_VAST, function () {
9111        TrackEvent("VAST", "Click Tab", "Whole WTS Network");
9112    });
9113
9114    wts.visor.on(UI_EVENTS.CLICK_BRIGHTDROP_POOL_TAB_ON_VAST, function () {
9115        TrackEvent("VAST", "Click Tab", "Brightdrop Pool");
9116    });
9117
9118    wts.visor.on(UI_EVENTS.OPEN_VEHICLE_ACQUISITION_DETAILS, function (e) {
9119        var inventoryID = e.data;
9120        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9121            TrackEvent("VAST", "View Acquisition Details", label, dimensions);
9122        });
9123    });
9124
9125    wts.visor.on(UI_EVENTS.CLICK_EMAIL_VAST_CONTACT, function (e) {
9126        var inventoryID = e.data;
9127        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9128            TrackEvent("VAST", "Email Acquisition Contact", label, dimensions);
9129        });
9130    });
9131
9132    wts.visor.on(UI_EVENTS.CLICK_CALL_VAST_CONTACT, function (e) {
9133        var inventoryID = e.data;
9134        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9135            TrackEvent("VAST", "Call Acquisition Contact", label, dimensions);
9136        });
9137    });
9138
9139    wts.visor.on(UI_EVENTS.CLICK_CALL_VAST_DEALER, function (e) {
9140        var inventoryID = e.data;
9141        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9142            TrackEvent("VAST", "Call Dealer", label, dimensions);
9143        });
9144    });
9145
9146    wts.visor.on(UI_EVENTS.CLICK_VAST_CONDITION_LINK, function (e) {
9147        var inventoryID = e.data;
9148        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9149            TrackEvent("VAST", "View Condition", label, dimensions);
9150        });
9151    });
9152
9153    wts.visor.on(UI_EVENTS.CLICK_VAST_AUCTION_LINK, function (e) {
9154        var inventoryID = e.data;
9155        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9156            TrackEvent("VAST", "View Auction", label, dimensions);
9157        });
9158    });
9159
9160    wts.visor.on(UI_EVENTS.SUBMIT_VAST_MESSAGE, function (e) {
9161        var inventoryID = e.data;
9162        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9163            TrackEvent("VAST", "Message Acquisition Contact", label, dimensions);
9164        });
9165    });
9166
9167    wts.visor.on(UI_EVENTS.CLICK_VAST_OUTBOUND_LINK, function (e) {
9168        var inventoryID = e.data.inventoryID;
9169        var href = e.data.href;
9170        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9171            window.TrackEvent("VAST", "View Outbound Link", href, dimensions);
9172        });
9173    });
9174
9175    wts.visor.on(UI_EVENTS.CLICK_BACK_TO_SEARCH_RESULTS, function (e) {
9176        var inventoryID = e.data.inventoryID;
9177        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9178            window.TrackEvent(vehicleCategory, "Close Details", label, dimensions);
9179        });
9180
9181        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9182        if (adobePageData) {
9183            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9184        }
9185    });
9186
9187    wts.visor.on(UI_EVENTS.CLICK_BARE_CHASSIS_INQUIRY, function (e) {
9188        var inventoryID = e.data;
9189        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9190            window.TrackEvent(vehicleCategory, "Bare Chassis Request", label, dimensions);
9191        });
9192    });
9193
9194    wts.visor.on(UI_EVENTS.CLICK_BODY_ONLY_INQUIRY, function (e) {
9195        var inventoryID = e.data;
9196        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9197            window.TrackEvent(vehicleCategory, "Body Only Request", label, dimensions);
9198        });
9199    });
9200
9201    wts.visor.on(UI_EVENTS.CLICK_SAVE_VEHICLE_TO_GARAGE, function (e) {
9202        var inventoryID = e.data;
9203        getLabelAndDimensions(inventoryID, function (label, dimensions) {
9204            window.TrackEvent(vehicleCategory, "Save To Garage", label, dimensions);
9205        });
9206    });
9207
9208
9209    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_STATE_OPEN_EVENT, function (e) {
9210        var label = e.data.label;
9211        var category = pageData.isVast ? "VAST" : "Search";
9212        window.TrackEvent(category, "Open Facet Header", label);
9213
9214        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9215        if (adobePageData) {
9216            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9217        }
9218
9219        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9220    });
9221
9222    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_STATE_CLOSE_EVENT, function (e) {
9223        var label = e.data.label;
9224        var category = pageData.isVast ? "VAST" : "Search";
9225        window.TrackEvent(category, "Close Facet Header", label);
9226
9227        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9228        if (adobePageData) {
9229            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9230        }
9231
9232        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9233    });
9234
9235    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_CHASSIS_OPEN_EVENT, function (e) {
9236        var label = e.data.label;
9237        var category = pageData.isVast ? "VAST" : "Search";
9238        window.TrackEvent(category, "Open Facet Header", label);
9239
9240        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9241        if (adobePageData) {
9242            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9243        }
9244
9245        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9246    });
9247
9248    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_CHASSIS_CLOSE_EVENT, function (e) {
9249        var label = e.data.label;
9250        var category = pageData.isVast ? "VAST" : "Search";
9251        window.TrackEvent(category, "Close Facet Header", label);
9252
9253        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9254        if (adobePageData) {
9255            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9256        }
9257
9258        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9259    });
9260
9261    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_BODY_OPEN_EVENT, function (e) {
9262        var label = e.data.label;
9263        var category = pageData.isVast ? "VAST" : "Search";
9264        window.TrackEvent(category, "Open Facet Header", label);
9265
9266        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9267        if (adobePageData) {
9268            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9269        }
9270
9271        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9272    });
9273
9274    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_BODY_CLOSE_EVENT, function (e) {
9275        var label = e.data.label;
9276        var category = pageData.isVast ? "VAST" : "Search";
9277        window.TrackEvent(category, "Close Facet Header", label);
9278
9279        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9280        if (adobePageData) {
9281            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9282        }
9283
9284        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9285    });
9286
9287    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_ADDONS_OPEN_EVENT, function (e) {
9288        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9289        if (adobePageData) {
9290            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9291        }
9292
9293        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9294    });
9295
9296    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_ADDONS_CLOSE_EVENT, function (e) {
9297        var adobePageData = AdobeAnalytics.getAdobeInteractionClickEventData(e);
9298        if (adobePageData) {
9299            trackAdobeEvent(new CustomEvent("interaction-click", { detail: adobePageData }));
9300        }
9301
9302        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9303    });
9304
9305    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_PROGRESSIVE_OPEN_EVENT, function (e) {
9306        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9307    });
9308
9309    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_PROGRESSIVE_CLOSE_EVENT, function (e) {
9310        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9311    });
9312
9313    wts.visor.on(UI_EVENTS.VSP_FACET_OPEN_EVENT, function (e) {
9314        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9315    });
9316
9317    wts.visor.on(UI_EVENTS.VSP_FACET_CLOSE_EVENT, function (e) {
9318        AscTracking.trackGeneralLayoutInteractionClick(new CustomEvent(AscTracking.ASC_MAPPINGS.EVENT.ASC_CTA_INTERACTION, { detail: e.data.currentTarget }));
9319    });
9320
9321    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_TRAILER_OPEN_EVENT, function (e) {
9322        var label = e.data.label;
9323        var category = pageData.isVast ? "VAST" : "Search";
9324        window.TrackEvent(category, "Open Facet Header", label);
9325    });
9326
9327    wts.visor.on(UI_EVENTS.VSP_FACETGROUP_TRAILER_CLOSE_EVENT, function (e) {
9328        var label = e.data.label;
9329        var category = pageData.isVast ? "VAST" : "Search";
9330        window.TrackEvent(category, "Close Facet Header", label);
9331    });
9332
9333    wts.visor.on(UI_EVENTS.VAST_VSP_FACETGROUP_PARTNER_OPEN_EVENT, function (e) {
9334        if (pageData.isVast) {
9335            var label = e.data.label;
9336            window.TrackEvent("VAST", "Open Facet Header", label);
9337        }
9338    });
9339
9340    wts.visor.on(UI_EVENTS.VAST_VSP_FACETGROUP_PARTNER_CLOSE_EVENT, function (e) {
9341        if (pageData.isVast) {
9342            var label = e.data.label;
9343            window.TrackEvent("VAST", "Close Facet Header", label);
9344        }
9345    });
9346
9347    wts.visor.on(UI_EVENTS.NAVIGATION_PAGE_PARTNER_LEARN_MORE, function (e) {
9348        var label = `${getClientData("#dealer-data").dealerDescription}-Click Nav Page Partner Ad Learn More`;
9349        window.TrackEvent("Dealer Tools", "Navigation Page Clicks", label);
9350    });
9351
9352    wts.visor.on(UI_EVENTS.NAVIGATION_PAGE_VIEW_ALL_PARTNERS, function (e) {
9353        var label = `${getClientData("#dealer-data").dealerDescription}-Click Nav Page View All Partners`;
9354        window.TrackEvent("Dealer Tools", "Navigation Page Clicks", label);
9355    });
9356
9357    wts.visor.on(UI_EVENTS.MARKETPLACE_EVENT, function (e) {
9358        var category = e.data.category;
9359        var action = e.data.action;
9360        var label = e.data.label;
9361
9362        window.TrackEvent(category, action, label);
9363    });
9364
9365    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_TOGGLE, function (e) {
9366        window.TrackEvent("Catalog", "Commercial Catalog Toggle", e.data);
9367    });
9368
9369
9370    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_VIEW_BODY_LINE_LITERATURE_PDF, function (e) {
9371        window.TrackEvent("Catalog", "View Body Line Literature PDF", "Click " + e.data.Target, { dimension9: e.data.BodyType } );
9372
9373        // Ford Direct Analytics - parallel tracking path
9374        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
9375            FordDirectAnalytics.trackToolEvent("download-commercial-catalog");
9376        }
9377    });
9378
9379    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_VIEW_BODY_LINE_VIDEO, function (e) {
9380        window.TrackEvent("Catalog", "View Body Line Video", "Click " + e.data.BodyLine + " " + e.data.Source, { dimension9: e.data.BodyType });
9381
9382        // Ford Direct Analytics - parallel tracking path
9383        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
9384            FordDirectAnalytics.trackClick(e, "COMMERCIAL_CATALOG_VIEW_BODY_LINE_VIDEO");
9385        }
9386    });
9387
9388    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_START_REQUEST, function (e) {
9389        window.TrackEvent("Catalog", "Start Request", "Start " + e.data.BodyLine + " " + e.data.BodyType + " Custom Order Request", { dimension9: e.data.BodyType });
9390    });
9391
9392    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_VIEW_PHOTO, function (e) {
9393        window.TrackEvent("Catalog", "View Photo", "Click Photo " + e.data.BodyLine + " " + e.data.Source , { dimension9: e.data.BodyType });
9394
9395        // Ford Direct Analytics - parallel tracking path
9396        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
9397            FordDirectAnalytics.trackClick(e, "COMMERCIAL_CATALOG_VIEW_PHOTO");
9398        }
9399    });
9400
9401    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_NEXT_PHOTO, function (e) {
9402        window.TrackEvent("Catalog", "View Next Photo", "View Next Photo " + e.data.BodyLine + ' - ' + e.data.URL, { dimension9: e.data.BodyType });
9403    });
9404
9405    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_PREV_PHOTO, function (e) {
9406        window.TrackEvent("Catalog", "View Previous Photo", "View Next Photo " + e.data.BodyLine + ' - ' + e.data.URL, { dimension9: e.data.BodyType });
9407    });
9408
9409    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_CHANGE_BODY_TYPE, function (e) {
9410        window.TrackEvent("Catalog", "Change Body Type", "Click " + e.data, { dimension9: e.data});
9411    });
9412
9413    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_CLICK_BODY_LINE, function (e) {
9414        window.TrackEvent("Catalog", "Select Body Line", "Click " + e.data.BodyManufacturer + " " + e.data.BodyLine, { dimension9: e.data.BodyType });
9415    });
9416
9417    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_NEXT, function (e) {
9418        window.TrackEvent("Catalog", "Page Thumbnails Next", "Page Thumbnails Next " + e.data.BodyLine, { dimension9: e.data.BodyType });
9419
9420        // Ford Direct Analytics - parallel tracking path
9421        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
9422            FordDirectAnalytics.trackClick(e, "COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_NEXT");
9423        }
9424    });
9425
9426    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_PREVIOUS, function (e) {
9427        window.TrackEvent("Catalog", "Page Thumbnails Previous", "Page Thumbnails Previous " + e.data.BodyLine, { dimension9: e.data.BodyType });
9428
9429        // Ford Direct Analytics - parallel tracking path
9430        if (FordDirectAnalytics && FordDirectAnalytics.isEnabled()) {
9431            FordDirectAnalytics.trackClick(e, "COMMERCIAL_CATALOG_CLICK_PAGE_THUMBNAILS_PREVIOUS");
9432        }
9433    });
9434
9435    wts.visor.on(UI_EVENTS.COMMERCIAL_CATALOG_CLICK_SHOW_MORE, function (e) {
9436        window.TrackEvent("Catalog", "Show More", "Show More" + " " + e.data.BodyLine, {dimension9: e.data.BodyType});
9437    });
9438
9439    wts.visor.on(UI_EVENTS.CONDITIONAL_CALL_TO_ACTION, function (e) {
9440        if (e.data) {
9441            var el = e.data;
9442            let category = el.getAttribute("category");
9443            let action = el.getAttribute("action");
9444            let label = el.getAttribute("label");
9445
9446            window.TrackEvent(category, action, label);
9447        }
9448    });
9449
9450    wts.visor.on(UI_EVENTS.RESOURCE_RACK_LAUNCH_PAGE, function (e) {
9451        window.TrackEvent("Resource Rack", "Navigation Launch", null, { ...e.data, ActionSubType: 'Navigation Launch Resource Rack' });
9452    });
9453    
9454    wts.visor.on(UI_EVENTS.RESOURCE_RACK_CHANGE_SEARCH_CONTROLS, function (e) {
9455        window.TrackEvent("Resource Rack", "Search", null, e.data );
9456    });
9457   
9458    wts.visor.on(UI_EVENTS.RESOURCE_RACK_PREVIEW_LINK_CLICK, function (e) {
9459        window.TrackEvent("Resource Rack", "Preview Link Click", null, e.data);
9460    });
9461   
9462    wts.visor.on(UI_EVENTS.RESOURCE_RACK_ACTION_MENU_GET_LINK, function (e) {
9463        window.TrackEvent("Resource Rack", "Get Link", null, e.data);
9464    });
9465    
9466    wts.visor.on(UI_EVENTS.RESOURCE_RACK_MULTISELECT_FOOTER_ACTION_COPY_LINKS, function (e) {
9467        window.TrackEvent("Resource Rack", "Copy Links", null, e.data);
9468    });
9469    
9470    wts.visor.on(UI_EVENTS.RESOURCE_RACK_MULTISELECT_FOOTER_ACTION_START_PROSPECTING, function (e) {
9471        window.TrackEvent("Resource Rack", "Start Prospecting", null, e.data);
9472    });
9473
9474
9475    wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_LAUNCH_PAGE, function (e) {
9476        window.TrackEvent("Tracking Links History", "Navigation Launch", null, { ...e.data, Version: 'V1' });
9477    });
9478    //Create and submit have been hidden from page for time being
9479    //wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_CREATE_TRACKING_LINK, function (e) {
9480    //    window.TrackEvent("Tracking Links History", "Launch Create Tracking Link");
9481    //});
9482
9483    //wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_SUBMIT_TRACKING_LINK, function (e) {
9484    //    window.TrackEvent("Tracking Links History", "Create Tracking Link Submit");
9485    //});
9486    
9487    wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_CLICK_DETAILS, function (e) {
9488        window.TrackEvent("Tracking Links History", "View Details", null, {...e.data, Version: 'V1'});
9489    });
9490    
9491    wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_CLICK_EDIT_TRACKING_LINK, function (e) {
9492        window.TrackEvent("Tracking Links History", "Launch Edit Link", null, { ...e.data, Version: 'V1' });
9493    });
9494    
9495    wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_CLICK_SAVE_EDIT, function (e) {
9496        window.TrackEvent("Tracking Links History", "Edit Link Submit", null, { ...e.data, Version: 'V1' });
9497    });
9498    
9499    wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_CLICK_DISABLE_LINK, function (e) {
9500        window.TrackEvent("Tracking Links History", "Disable Link", null, { ...e.data, Version: 'V1' });
9501    });
9502    
9503    wts.visor.on(UI_EVENTS.TRACKING_LINKS_HISTORY_CLICK_COPY_LINK, function (e) {
9504        window.TrackEvent("Tracking Links History", "Copy Link", null, { ...e.data, Version: 'V1' });
9505    });
9506
9507    ////Todo - Investigate AddTrackingInfo already present in Email
9508    //wts.visor.on(UI_EVENTS.TRACKING_LINKS_EMAIL_CLICK_SEE_LINKS, function (e) {
9509    //    window.TrackEvent("Tracking Links Email", "See Tracking Links");
9510    //});
9511
9512    
9513    wts.visor.on(UI_EVENTS.INVOICE_BINDER_LAUNCH_PAGE, function (e) {
9514        window.TrackEvent("Invoice Binder", "Navigation Launch", null, { ...e.data, Version: 'V1' });
9515    });
9516    
9517    wts.visor.on(UI_EVENTS.INVOICE_BINDER_SEARCH, function (e) {
9518        window.TrackEvent("Invoice Binder", "Search", null, { ...e.data, Version: 'V1' });
9519    });
9520
9521    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_ON_COUNT, function (e) {
9522        window.TrackEvent("Invoice Binder", "Click Missing Invoices", null, { ...e.data, Version: 'V1' });
9523    });
9524    
9525    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_VIEW_LISTING_DETAILS, function (e) {
9526        window.TrackEvent("Invoice Binder", "View Listing", null, { ...e.data, Version: 'V1' });
9527    });
9528    
9529    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_MARK_IF_NOT_UPFIT, function (e) {
9530        window.TrackEvent("Invoice Binder", "Mark it Not Upfit", null, { ...e.data, Version: 'V1' });
9531    });
9532    
9533    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_WINDOW_STICKER, function (e) {
9534        window.TrackEvent("Invoice Binder", "View Window Sticker", null, { ...e.data, Version: 'V1' });
9535    });
9536    
9537    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_INVOICE_LINK, function (e) {
9538        window.TrackEvent("Invoice Binder", "View Invoice", null, { ...e.data, Version: 'V1' });
9539    });
9540    
9541    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_UPLOAD_INVOICES, function (e) {
9542        window.TrackEvent("Invoice Binder", "Upload Invoice", null, { ...e.data, Version: 'V1' });
9543    });
9544    
9545    wts.visor.on(UI_EVENTS.INVOICE_BINDER_CLICK_DELETE_INVOICE, function (e) {
9546        window.TrackEvent("Invoice Binder", "Delete Invoice", null, { ...e.data, Version: 'V1' });
9547    });
9548
9549    wts.visor.on(UI_EVENTS.PRINTING_ACTION_PRINT_COMPLETE_LISTING, function (e) {
9550        window.TrackEvent("Print Listing", "Print Complete Listing", null, { ...e.data, Version: 'V1' });
9551    });
9552
9553    wts.visor.on(UI_EVENTS.PRINTING_ACTION_PRINT_UPFIT_WINDOW_STICKER, function (e) {
9554        window.TrackEvent("Print Window Sticker", "Print Upfit Window Sticker", null, { ...e.data, Version: 'V1' });
9555    });
9556
9557    /**
9558     * Inventory Management
9559     */
9560    // General
9561    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_GROUP_SELECT_MEMBER_DEALER, function (e) {
9562        window.TrackEvent("Inventory Management", "General", null, { ...e.data, Version: 'V1' });
9563    });
9564
9565    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_EXPORT_LISTINGS, function (e) {
9566        window.TrackEvent("Inventory Management", "General", null, { ...e.data, Version: 'V1' });
9567    });
9568
9569    // Create Listing
9570    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CREATE_BODY_ONLY_LISTING, function (e) {
9571        window.TrackEvent("Inventory Management", "Create Listing", null, { ...e.data, Version: 'V1' });
9572    });
9573
9574    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CREATE_TRAILER_LISTING, function (e) {
9575        window.TrackEvent("Inventory Management", "Create Listing", null, { ...e.data, Version: 'V1' });
9576    });
9577
9578    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CREATE_VEHICLE_LISTING, function (e) {
9579        window.TrackEvent("Inventory Management", "Create Listing", null, { ...e.data, Version: 'V1' });
9580    });
9581
9582    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_TOTAL_LISTINGS, function (e) {
9583        window.TrackEvent("Inventory Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9584    });
9585
9586    // Inventory Summary
9587    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_ON_SALE_LISTINGS, function (e) {
9588        window.TrackEvent("Inventory Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9589    });
9590
9591    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_FEATURED_LISTINGS, function (e) {
9592        window.TrackEvent("Inventory Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9593    });
9594
9595    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_HIDDEN_LISTINGS, function (e) {
9596        window.TrackEvent("Inventory Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9597    });
9598
9599    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_PRIVATE_LISTINGS, function (e) {
9600        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9601    });
9602
9603    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_VAST_ONLY_LISTINGS, function (e) {
9604        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9605    });
9606
9607    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_UNKNOWN_IF_UPFIT, function (e) {
9608        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9609    });
9610
9611    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_MISSING_PHOTOS, function (e) {
9612        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9613    });
9614
9615    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_INVALID_PHOTOS, function (e) {
9616        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9617    });
9618
9619    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_NO_INVOICES, function (e) {
9620        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9621    });
9622
9623    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_MANUAL_LISTINGS, function (e) {
9624        window.TrackEvent("Inventory_Management", "Inventory Summary", null, { ...e.data, Version: 'V1' });
9625    });
9626
9627    // Inventory Search
9628    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_RUN_LAST_QUERY, function (e) {
9629        window.TrackEvent("Inventory Management", "Inventory Search", null, { ...e.data, Version: 'V1' });
9630    });
9631   wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_TOGGLE_ADVANCED_SEARCH, function (e) {
9632        window.TrackEvent("Inventory Management", "Inventory Search", null, { ...e.data, Version: 'V1' });
9633    });
9634
9635    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_TEXT_SEARCH, function (e) {
9636        window.TrackEvent("Inventory Management", "Inventory Search", null, { ...e.data, Version: 'V1' });
9637    });
9638
9639    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_FILTER_INVENTORY_SELECT, function (e) {
9640        window.TrackEvent("Inventory Management", "Inventory Search", null, { ...e.data, Version: 'V1' });
9641    });
9642
9643    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_FILTER_INVENTORY_CHECKBOX, function (e) {
9644        window.TrackEvent("Inventory Management", "Inventory Search", null, { ...e.data, Version: 'V1' });
9645    });
9646
9647    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_RESET_FILTERS, function (e) {
9648        window.TrackEvent("Inventory Management", "Inventory Search", null, { ...e.data, Version: 'V1' });
9649    });
9650
9651    // Action Menu
9652    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_VIEW_INVOICES, function (e) {
9653        window.TrackEvent("Inventory Management", "Action Menu", null, { ...e.data, Version: 'V1' });
9654    });
9655
9656    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_EDIT_PRICING, function (e) {
9657        window.TrackEvent("Inventory Management", "Action Menu", null, { ...e.data, Version: 'V1' });
9658    });
9659
9660    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_EDIT_TRADE_DETAILS, function (e) {
9661        window.TrackEvent("Inventory Management", "Action Menu", null, { ...e.data, Version: 'V1' });
9662    });
9663
9664    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_EDIT_LISTING, function (e) {
9665        window.TrackEvent('Inventory Management', 'Action Menu', null, { ...e.data, Version: 'V1' });
9666    });
9667
9668    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_MARK_AS_MOVED, function (e) {
9669        window.TrackEvent('Inventory Management', 'Action Menu', null, { ...e.data, Version: 'V1' });
9670    });
9671
9672    wts.visor.on(UI_EVENTS.INVENTORY_MANAGEMENT_CLICK_DELETE_LISTING, function (e) {
9673        window.TrackEvent('Inventory Management', 'Action Menu', null, { ...e.data, Version: 'V1' });
9674    });
9675
9676    wts.visor.on(UI_EVENTS.VIDEO_PLAYER_START, function (e) {
9677        window.TrackEvent('Youtube Video Player Start', 'VDP', e.data.label, { ...e.data });
9678    });
9679
9680    wts.visor.on(UI_EVENTS.VIDEO_PLAYER_RESUME, function (e) {
9681        window.TrackEvent('Youtube Video Player Resume', 'VDP', e.data.label, { ...e.data });
9682    });
9683
9684    wts.visor.on(UI_EVENTS.VIDEO_PLAYER_PAUSE, function (e) {
9685        window.TrackEvent('Youtube Video Player Pause', 'VDP', e.data.label, { ...e.data });
9686    });
9687
9688    wts.visor.on(UI_EVENTS.VIDEO_PLAYER_THRESHOLD, function (e) {
9689        window.TrackEvent('Youtube Video Player Threshold', 'VDP', e.data.label, { ...e.data });
9690    });
9691
9692    wts.visor.on(UI_EVENTS.READ_MORE_VEHICLE_NOTES_CLICK, function (e) {
9693        getLabelAndDimensions(e.data, function (label, dimensions) {
9694            window.TrackEvent("Vehicle", "Read More Vehicle Notes", label, dimensions);
9695        });
9696    });
9697
9698    wts.visor.on(UI_EVENTS.READ_LESS_VEHICLE_NOTES_CLICK, function (e) {
9699        getLabelAndDimensions(e.data, function (label, dimensions) {
9700            window.TrackEvent("Vehicle", "Read Less Vehicle Notes", label, dimensions);
9701        });
9702    });
9703
9704    wts.visor.on(UI_EVENTS.VIEW_ALL_VEHICLE_OPTIONS_CLICK, function (e) {
9705        getLabelAndDimensions(e.data, function (label, dimensions) {
9706            window.TrackEvent("Vehicle", "View All Vehicle Options", label, dimensions);
9707        });
9708    });
9709
9710    wts.visor.on(UI_EVENTS.SEE_DEALERSHIPS_OTHER_LISTINGS_CLICK, function (e) {
9711        getLabelAndDimensions(e.data, function (label, dimensions) {
9712            window.TrackEvent("Vehicle", "See Dealership's Other Listings", label, dimensions);
9713        });
9714    });
9715
9716    //Todo - Hook these up to PDF Viewer without wts context
9717    //wts.visor.on(UI_EVENTS.PDF_VIEWER_INTERNAL_LOADING, function (e) {
9718    //    window.TrackEvent("PDF Viewer", "Internal PDF Preview", null, { ...e.data, Version: 'V1' });
9719    //});
9720    
9721    //wts.visor.on(UI_EVENTS.PDF_VIEWER_CLICK_GET_SHAREABLE_LINK, function (e) {
9722    //    window.TrackEvent("PDF Viewer", "Get Shareable Link", null, { ...e.data, Version: 'V1' });
9723    //});
9724    
9725    //wts.visor.on(UI_EVENTS.PDF_VIEWER_CLICK_GET_TRACKING_LINK, function (e) {
9726    //    window.TrackEvent("PDF Viewer", "Get Tracking Link", null, { ...e.data, Version: 'V1' });
9727    //});
9728
9729
9730    /**
9731     * Hides away the details of fetching the label and dimensions
9732     * 
9733     * @description The VDP and VSP fetch the labels and dimensions from different window-attached objects.
9734     *  - VDP retrieves it directly via myAnalyticsLabel/myAnalyticsDimensions
9735     *  - VSP uses a lookup table via analyticsLabels/analyticsDimensions
9736     * @param {string} inventoryID an inventoryID
9737     * @param {Function} callback A function that will have two parameters: label, dimensions
9738     * @returns {undefined}
9739     */
9740    function getLabelAndDimensions(inventoryID, callback) {
9741
9742        if (!inventoryID) {
9743            return;
9744        }
9745
9746        var label = "";
9747        var vin = null;
9748        var customDimensions = null;
9749
9750        // vsp
9751        if (window.analyticsLabels && inventoryID in window.analyticsLabels) {
9752            label += window.analyticsLabels[inventoryID];
9753        }
9754
9755        // vdp
9756        if (window.myAnalyticsLabel) {
9757            label += window.myAnalyticsLabel;
9758        }
9759
9760        if (window.myAnalyticsDimensions) {
9761            customDimensions = window.myAnalyticsDimensions;
9762            vin = customDimensions["dimension6"] || null;
9763        }
9764
9765        // Order matter a lot here. window.myAnalyticsDimensions is always going to be defined so we will check for the VSP specific version
9766        // to override the previous if statement
9767        if (window.analyticsDimensions && inventoryID in window.analyticsDimensions) {
9768            customDimensions = window.analyticsDimensions[inventoryID];
9769            vin = customDimensions["dimension6"] || null;
9770        }
9771
9772        if (vin !== null) {
9773            label += " - " + vin;
9774        }
9775
9776        callback(label, customDimensions);
9777    }
9778})(window.wts, window.wts.modules);
9779;
9780wts.registerBundle("marketing");
9781(function (wts, modules) {   
9782
9783    /**
9784     * @module MarketingEvents
9785     * */
9786
9787    /**
9788     * @const {object} */
9789    var events = {
9790        vehicleLead: "Vehicle:Lead",
9791        vehicleView: "Vehicle:View"
9792    };
9793
9794    /**
9795     * Exposes an api to attach event handlers for marketing related events.
9796     * Exposes an api to trigger marketing related events.
9797     * @class MarketingEvents
9798     * @param {EventEmitter} eventEmitter An instance of `EventEmitter` @see {module:EventEmitter}
9799     * @returns {MarketingEvents} An instance of `MarketingEvents`
9800     * @example
9801     * modules.MarketingEvents.onVehicleLead(function(eventArgs) {//Do important things with eventArgs.data});
9802     * @example
9803     * var inventoryId = 12345;
9804     * modules.MarketingEvents.vehicleLead(new modules.VehicleLeadData(inventoryId));
9805     * */
9806    function MarketingEvents(eventEmitter) {
9807        if (!(eventEmitter instanceof modules.EventEmitter)) {
9808            throw "eventEmitter must be an `EventEmitter`";
9809        }
9810
9811        this.eventEmitter = eventEmitter;
9812        
9813        return this;
9814    }
9815
9816    /** 
9817     *  Handles the vehicle lead event
9818     *  @callback VehicleLeadCallback
9819     *  @param {Object} eventArgs where eventArgs.data is `VehicleLeadData`
9820     * */
9821
9822    /**
9823     * Handles the vehicle viewed event
9824     * @callback VehicleViewCallback
9825     * @param {Object} eventArgs where eventArgs.data is `VehicleViewData`
9826     * */
9827
9828
9829
9830    MarketingEvents.prototype = {
9831        /**
9832         * @description the collection of active marketing event related promises
9833         * @type {Array<Jquery.Deferred>}
9834         */
9835        activePromises: [],
9836
9837        /**
9838         * @description the collection of inventory details request promises, keyed to inventory id.
9839         * @type {Array<JQuery.Deferred<VehicleViewData>>}
9840         */
9841        inventoryPromises: [],
9842
9843        /**
9844         * @description returns a Deferred (@see Jquery.Deferred)  that resolves when all activePromises (from marketing events) have resolved.
9845         * @returns {JQuery.Deferred} the deferred
9846         */
9847        wait: function() {
9848            return $.when.apply($, this.activePromises);
9849        },
9850
9851        /**
9852         * Adds an event handler callback for the `VehicleLead` event
9853         * @param {VehicleLeadCallback} callback - the event handler
9854         * @param {any} context the context to apply the event handler (aka 'this' in the handler scope)
9855         * @returns {void}
9856         * */
9857        onVehicleLead: function (callback, context) {
9858            this.eventEmitter.on(events.vehicleLead, callback, context);
9859        },
9860
9861        /**
9862         * Adds an event handler for the `VehicleView` event
9863         * @param {VehicleViewCallback} callback - the event handler
9864         * @param {any} context the context to apply the event handler (aka 'this' in the handler scope)
9865         * @returns {void}
9866         */
9867        onVehicleView: function (callback, context) {
9868            this.eventEmitter.on(events.vehicleView, callback, context);
9869        },
9870
9871        /**
9872         * Triggers the `VehicleLead` event
9873         * @param {VehicleLeadData} vehicleLeadData the `VehicleLeadData` event object
9874         * @throws {Error} if parameter not an instance of VehicleLeadData
9875         * @returns {void}
9876         */
9877        vehicleLead: function (vehicleLeadData) {
9878            if (vehicleLeadData instanceof VehicleLeadData) {
9879                this.eventEmitter.trigger(events.vehicleLead, vehicleLeadData);
9880            }
9881            else {
9882                throw "parameter vehicleLeadData is not an instance of `VehicleLeadData`";
9883            }
9884        },
9885
9886        /**
9887         * Triggers the `VehicleView` event
9888         * @param {VehicleViewData} vehicleViewData the `VehicleViewData` event object
9889         * @throws {Error} if parameter not an instance of VehicleViewData
9890         * @returns {void}
9891         */
9892        vehicleView: function (vehicleViewData) {
9893            if (vehicleViewData instanceof VehicleViewData) {
9894                this.eventEmitter.trigger(events.vehicleView, vehicleViewData);
9895            }
9896            else {
9897                throw "parameter vehicleViewData is not an instance of `VehicleViewData`";
9898            }
9899        },
9900
9901        /**
9902         * retrieves the `VehicleViewData` from an api endpoint and caches it as a promise resolving to the data
9903         * @param {string} inventoryId the inventory id for the vehicle
9904         * @param {Function} callback the function to call after the vehicle data is retrieved
9905         * @returns {Promise} the vehicle instance `VehicleViewData`
9906         */
9907        getVehicleData: function (inventoryId, callback) {
9908            var that = this;
9909
9910            // Create and cache the request promise
9911            if (!this.inventoryPromises[inventoryId]) {
9912                this.inventoryPromises[inventoryId] = $.get("/api/marketing/vehicledata/" + inventoryId)
9913                    .fail(function (resp) {
9914                        // Do we have a standardized way of handling these things??
9915                    });
9916            }
9917
9918            // capture the promise that resolves after the callback into activePromises and remove it after the callback
9919            var def = this.inventoryPromises[inventoryId].then(function(data) {
9920                callback(data);
9921                that.activePromises = that.activePromises.filter(function (value) {
9922                    return value !== def;
9923                });
9924            });
9925
9926            this.activePromises.push(def);
9927        }
9928    };
9929
9930    /**
9931     * VehicleLeadData holds information relevant to the vehicle lead event
9932     * @class VehicleLeadData
9933     * @param {string} inventoryId - the vehicle inventory id
9934     * @returns {VehicleLeadData} an instance of `VehicleLeadData`
9935     */
9936    function VehicleLeadData(inventoryId) {
9937        this.inventoryId = inventoryId;
9938
9939        return this;
9940    }
9941
9942    /**
9943     * @class VehicleViewData
9944     * @param {string} inventoryId the vehicle's inventory id
9945     * @param {number} dealerNumericId the vehicle's dealer numeric id
9946     * @param {string} zipCode the dealer's zipcode
9947     * @param {string} make the vehicle's make, e.g. Ford
9948     * @param {string} model the vehicle's model, e.g. F-150
9949     * @param {number} year the vehicle's model year, e.g. 2020
9950     * @param {string} stockType 'New' or 'Used'
9951     * @param {string} color the vehicle's exterior color
9952     * @param {string} transmission the vehicle's transmission type, e.g. Automatic
9953     * @param {string} bodyStyle the vehicle's body style, 'Van' or 'Truck'
9954     * @param {string} fuelType the vehicle's fuel type, 'Gas' or 'Diesel'
9955     * @param {string} drivetrain the vehicle's drivetrain, e.g. 4x2
9956     * @param {number} price the vehicle's 'final price' after discounts at the time of view
9957     * @param {string} trim the vehicle's trim package
9958     * @param {number} mileage the vehicle's mileage
9959     * @param {string} vin the vehicle's vin number
9960     * @param {string} fuelType the vehicle's fuel type
9961     * @return {VehicleViewData} an instance of `VehicleViewData`
9962     */
9963    function VehicleViewData(
9964        inventoryId,
9965        dealerNumericId,
9966        zipCode,
9967        make,
9968        model,
9969        year,
9970        stockType,
9971        color,
9972        transmission,
9973        bodyStyle,
9974        fuelType,
9975        drivetrain,
9976        price,
9977        trim,
9978        mileage,
9979        vin,
9980        fuelType
9981        )    {        
9982            this.inventoryId = inventoryId;
9983            this.dealerNumericId = dealerNumericId;
9984            this.zipCode = zipCode;
9985            this.make = make;
9986            this.model = model;
9987            this.year = year;
9988            this.stockType = stockType;
9989            this.color = color;
9990            this.transmission = transmission;
9991            this.bodyStyle = bodyStyle;
9992            this.fuelType = fuelType;
9993            this.drivetrain = drivetrain;
9994            this.price = price;
9995            this.trim = trim;
9996            this.mileage = mileage;
9997            this.vin = vin;
9998            this.fuelType = fuelType;
9999    }
10000
10001    // Singleton marketing events object
10002    modules.MarketingEvents = new MarketingEvents(new modules.EventEmitter());
10003
10004    // constructors for tracking data objects
10005    modules.VehicleLeadData = VehicleLeadData;
10006    modules.VehicleViewData = VehicleViewData;
10007
10008})(window.wts,window.wts.modules);;
10009(function (wts, modules) {
10010
10011    
10012    /** 
10013      * @class FacebookMarketingEvents
10014      * @param {MarketingEvents} marketingEvents a `MarketingEvents` object
10015      * @returns {void}
10016      */    
10017    function FacebookPixel(marketingEvents) {
10018        if (typeof marketingEvents === "undefined") {
10019            throw "MarketingEvents dependency not found";
10020        }
10021
10022        this.marketingEvents = marketingEvents;
10023
10024        return this;
10025    }
10026
10027    FacebookPixel.prototype = {
10028
10029        /**
10030         * Initializes facebook pixel with pixelId
10031         * @param {string} pixelId The dealer's facebook pixel id
10032         * @returns {void}
10033         */
10034        init: function (pixelId) {
10035            /**
10036             * Facebook Pixel injection script from: https://
10037             * */
10038            (function (f, b, e, v, n, t, s) {
10039                if (f.fbq) {
10040                    return;
10041                } n = f.fbq = function () {
10042                    n.callMethod ?
10043                        n.callMethod.apply(n, arguments) : n.queue.push(arguments);
10044                };
10045                if (!f._fbq) {
10046                    f._fbq = n;
10047                } n.push = n; n.loaded = !0; n.version = "2.0";
10048                n.queue = []; t = b.createElement(e); t.async = !0;
10049                t.src = v; s = b.getElementsByTagName(e)[0];
10050                s.parentNode.insertBefore(t, s);
10051            })(window, document, "script",
10052                "https://connect.facebook.net/en_US/fbevents.js");
10053
10054            this.pixel = window.fbq;
10055
10056            this.marketingEvents.onVehicleView(this.vehicleViewHandler, this);
10057            this.marketingEvents.onVehicleLead(this.vehicleLeadHandler, this);
10058
10059            this.pixel("init", pixelId);
10060            this.pixel("track", "PageView");
10061        },
10062
10063        /**
10064         * Sends a pixel 'lead' event to facebook 
10065         * @param {Object} eventArgs the event arguments, @see EventEmitter.dispatchEvent
10066         * @returns {void}
10067         * @throws {string} If `eventArgs.data` is not a `VehicleLeadData`
10068         */
10069        vehicleLeadHandler: function (eventArgs) {
10070            if (!(eventArgs.data instanceof modules.VehicleLeadData)) {
10071                throw "eventArgs.data must be a `VehicleLeadData`, see MarketingEvents module";
10072            }
10073
10074            var vehicleLeadData = eventArgs.data;
10075            var that = this;
10076
10077            this.marketingEvents.getVehicleData(vehicleLeadData.inventoryId, function (vehicleViewData) {
10078                var facebookPixelVehicleData = that.mapVehicleData(vehicleViewData);
10079
10080                // Track the lead, yea!
10081                that.pixel("track", "Lead", facebookPixelVehicleData);
10082            });            
10083        },
10084
10085        /**
10086         * Composes the event object for the viewed vehicle and calls the Facebook Pixel 'ViewContent' event
10087         * @param {Object} eventArgs the event arguments, @see EventEmitter.dispatchEvent
10088         * @return {void}
10089         * @throws {string} If `eventArgs.data` is not a `VehicleLeadData`
10090         */
10091        vehicleViewHandler: function (eventArgs) {
10092            if (!(eventArgs.data instanceof modules.VehicleViewData)) {
10093                throw "eventArgs.data must be a `VehicleViewData`, see MarketingEvents module";
10094            }
10095
10096            var facebookPixelVehicleData = this.mapVehicleData(eventArgs.data);
10097                        
10098            this.pixel("track", "ViewContent", facebookPixelVehicleData);
10099        },
10100
10101        /**
10102         * Maps vehicle data from the event to facebook pixel's schema
10103         * @param {VehicleViewData} vehicleViewData the `VehicleViewData` to map
10104         * @returns {Object} the mapped FacebookPixelVehicleData
10105         */
10106        mapVehicleData: function (vehicleViewData) {
10107            var drivetrain = "";
10108
10109            switch (vehicleViewData.drivetrain) {
10110                case "4x2": drivetrain = "RWD"; break;
10111                case "4x4": drivetrain = "FOUR_WD"; break;
10112                default: drivetrain = "TWO_WD"; break;
10113            }
10114
10115            // See: https://developers.facebook.com/docs/marketing-api/dynamic-ads-auto/auto-event/
10116            var facebookPixelVehicleData = {
10117                content_type: "vehicle", // REQUIRED: DO NOT change this, must be set to 'vehicle'
10118                content_ids: [vehicleViewData.dealerNumericId], // REQUIRED: array of vehicle IDs
10119                postal_code: vehicleViewData.zipCode, // RECOMMENDED
10120                country: "United States", // RECOMMENDED  don't use abbreviations
10121                make: vehicleViewData.make, // RECOMMENDED
10122                model: vehicleViewData.model, // RECOMMENDED
10123                year: vehicleViewData.year, // RECOMMENDED
10124                state_of_vehicle: vehicleViewData.stockType, // RECOMMENDED
10125                exterior_color: vehicleViewData.color, // RECOMMENDED
10126                transmission: vehicleViewData.transmission, // RECOMMENDED
10127                body_style: vehicleViewData.bodyStyle, // RECOMMENDED
10128                fuel_type: vehicleViewData.fuelType, // RECOMMENDED
10129                drivetrain: drivetrain, // RECOMMDENDED
10130                price: vehicleViewData.price, // RECOMMENDED, up to 2 decimals optional
10131                currency: "USD", // REQUIRED if price and preferred_price_range is used 
10132                // preferred_price_range: '[10000,20000]' // Optional up to two decimals, min,max
10133                trim: vehicleViewData.trim,
10134                "mileage.value": vehicleViewData.mileage,
10135                "mileage.unit": "MI",
10136                vin: vehicleViewData.vin,
10137                value: 50
10138            };
10139
10140            return facebookPixelVehicleData;
10141        }
10142    };
10143
10144    modules.FacebookPixel = new FacebookPixel(modules.MarketingEvents);
10145
10146})(window.wts, window.wts.modules);;
10147(function (wts, modules) {
10148    
10149    /** 
10150      * @class GoogleAdwordsEvents
10151      * @param {MarketingEvents} marketingEvents a `MarketingEvents` object
10152      * @returns {void}
10153      */    
10154    function GoogleAdwords(marketingEvents) {
10155        if (typeof marketingEvents === "undefined") {
10156            throw "MarketingEvents dependency not found";
10157        }
10158
10159        this.marketingEvents = marketingEvents;
10160
10161        return this;
10162    }
10163
10164    GoogleAdwords.prototype = {
10165
10166        /**
10167         * Init initializes google adwords with the adwords id
10168         * @param {string} awId The dealer's adwords id
10169         * @returns {void}
10170         */
10171        init: function (awId, awConversionCode) {
10172
10173            this.marketingEvents.onVehicleView(this.vehicleViewHandler, this);
10174            this.marketingEvents.onVehicleLead(this.vehicleLeadHandler, this);
10175
10176            // Google defined pieces
10177            // the processing on this is 
10178            window.dataLayer = window.dataLayer || [];
10179            this.gtag = function () {
10180                window.dataLayer.push(arguments);
10181            };
10182
10183            this.adWordsId = awId;
10184            this.gtag("js", new Date());
10185            this.gtag("config", awId);
10186            this.awConversionCode = awConversionCode
10187        },
10188
10189        /**
10190         * Sends a 'purchase' event to google remarketing when a lead is generated
10191         * Also sends a 'conversion' event for google adwords conversion tracking
10192         * @param {Object} eventArgs the event arguments, @see EventEmitter.dispatchEvent
10193         * @returns {void}
10194         * @throws {string} If `eventArgs.data` is not a `VehicleLeadData`
10195         */
10196        vehicleLeadHandler: function (eventArgs) {
10197            if (!(eventArgs.data instanceof modules.VehicleLeadData)) {
10198                throw "eventArgs.data must be a `VehicleLeadData`, see MarketingEvents module";
10199            }
10200
10201            var vehicleLeadData = eventArgs.data;
10202            var that = this;
10203
10204            this.marketingEvents.getVehicleData(vehicleLeadData.inventoryId, function (vehicleViewData) {
10205                // Track the lead, yea!
10206                var adwordsData = that.mapVehicleData(vehicleViewData);
10207
10208                // Google adwords remarketing event
10209                that.gtag("event", "purchase", adwordsData);
10210
10211                // Google adwords conversion tracking event
10212                that.gtag("event", "conversion", { "send_to": that.adWordsId + "/" + that.awConversionCode, "event_callback": function () { } });
10213            });      
10214            // Comvoy Conversion Code = "/ElITCLvnhroBEJjDjOQC"
10215        },
10216
10217        /**
10218         * Composes the event object for the viewed vehicle and calls the Google AdWords remarketing event event
10219         * @param {Object} eventArgs the event arguments, @see EventEmitter.dispatchEvent
10220         * @return {void}
10221         * @throws {string} If `eventArgs.data` is not a `VehicleLeadData`
10222         */
10223        vehicleViewHandler: function (eventArgs) {
10224            if (!(eventArgs.data instanceof modules.VehicleViewData)) {
10225                throw "eventArgs.data must be a `VehicleViewData`, see MarketingEvents module";
10226            }
10227
10228            var adwordsData = this.mapVehicleData(eventArgs.data);
10229                        
10230            this.gtag("event", "view_item", adwordsData);
10231        },
10232
10233        /**
10234         * Maps from `VehicleViewData` to google's remarketing object 
10235         * @param {VehicleViewData} vehicleViewData the `VehicleViewData` to map
10236         * @returns {any} the google adwords remarketing object
10237         */
10238        mapVehicleData: function (vehicleViewData) {
10239            // See: https://support.google.com/google-ads/answer/7305793
10240            var adwordsData = {
10241                value: vehicleViewData.price,
10242                items: [{
10243                    id: vehicleViewData.dealerNumericId,
10244                    // location_id: TBD - could use the dealer ID here
10245                    google_business_vertical: "custom"
10246                }]
10247            };
10248
10249            return adwordsData;
10250        }
10251    };
10252
10253    modules.GoogleAdwords = new GoogleAdwords(modules.MarketingEvents);
10254
10255})(window.wts, window.wts.modules);;
10256(function (wts, modules) {
10257   
10258    /** 
10259      * @class BingUET
10260      * @param {MarketingEvents} marketingEvents a `MarketingEvents` object
10261      * @returns {void}
10262      */    
10263    function BingUET(marketingEvents) {
10264        if (typeof marketingEvents === "undefined") {
10265            throw "MarketingEvents dependency not found";
10266        }
10267
10268        this.marketingEvents = marketingEvents;
10269
10270        return this;
10271    }
10272
10273    BingUET.prototype = {
10274
10275        /**
10276         * Init initializes bing uet
10277         * @param {string} uetTagId The Universal Event Tracking tag id
10278         * @returns {void}
10279         */
10280        init: function (uetTagId) {
10281
10282            var that = this;
10283
10284            var bing = function (w, d, t, r, u, id) {
10285                var f, n, i; that[u] = w[u] = w[u] || [],
10286                    f = function () {
10287                        var o = { ti: id };
10288                        o.q = w[u],
10289                            w[u] = new UET(o),
10290                        w[u].push("pageLoad"),
10291                        that[u] = w[u];
10292                    },
10293                    n = d.createElement(t),
10294                    n.src = r,
10295                    n.async = 1,
10296                    n.onload = n.onreadystatechange = function () {
10297                        var s = this.readyState;
10298                        s && s !== "loaded" && s !== "complete" || (f(), n.onload = n.onreadystatechange = null);
10299                    },
10300                    i = d.getElementsByTagName(t)[0],
10301                    i.parentNode.insertBefore(n, i);
10302            };
10303
10304            bing(window, document, "script", "//bat.bing.com/bat.js", "uetq", uetTagId);
10305
10306            // Connect marketing events
10307            this.marketingEvents.onVehicleView(this.vehicleViewHandler, this);
10308            this.marketingEvents.onVehicleLead(this.vehicleLeadHandler, this);
10309        },
10310
10311        /**
10312         * Sends a Bing Custom Event       
10313         * @param {Object} eventArgs the event arguments, @see EventEmitter.dispatchEvent
10314         * @returns {void}
10315         * @throws {string} If `eventArgs.data` is not a `VehicleLeadData`
10316         */
10317        vehicleLeadHandler: function (eventArgs) {
10318            if (!(eventArgs.data instanceof modules.VehicleLeadData)) {
10319                throw "eventArgs.data must be a `VehicleLeadData`, see MarketingEvents module";
10320            }
10321
10322            var vehicleLeadData = eventArgs.data;
10323            var that = this;
10324
10325            this.marketingEvents.getVehicleData(vehicleLeadData.inventoryId, function (vehicleViewData) {
10326
10327                var bingData = that.mapVehicleData(vehicleViewData);
10328
10329                bingData.ecomm_pagetype = "purchase";
10330
10331                var vehicleData = Object.assign({ "event_category": "Remarketing"}, bingData);
10332
10333                that.uetq.push("event", "generated_lead", vehicleData);
10334            });            
10335        },
10336
10337        /**
10338         * Composes the event object for the viewed vehicle and calls the Bing UET Custom Event
10339         * @param {Object} eventArgs the event arguments, @see EventEmitter.dispatchEvent
10340         * @return {void}
10341         * @throws {string} If `eventArgs.data` is not a `VehicleLeadData`
10342         */
10343        vehicleViewHandler: function (eventArgs) {
10344            if (!(eventArgs.data instanceof modules.VehicleViewData)) {
10345                throw "eventArgs.data must be a `VehicleViewData`, see MarketingEvents module";
10346            }
10347
10348            var bingData = this.mapVehicleData(eventArgs.data);
10349
10350            bingData.ecomm_pagetype = "product";
10351
10352            var vehicleData = Object.assign({ "event_category": "Remarketing"}, bingData);
10353                        
10354            this.uetq.push("event", "view_item", vehicleData);
10355        },
10356
10357        /**
10358         * Maps from `VehicleViewData` to google's remarketing object 
10359         * @param {VehicleViewData} vehicleViewData the `VehicleViewData` to map
10360         * @returns {any} the bing remarketing object
10361         */
10362        mapVehicleData: function (vehicleViewData) {
10363            // See: https://support.google.com/google-ads/answer/7305793
10364            var bingData = {
10365                "content_type": "vehicle",
10366                "ecomm_prodid": [vehicleViewData.dealerNumericId]
10367            };
10368
10369            return bingData;
10370        }
10371    };
10372
10373    modules.BingUET = new BingUET(modules.MarketingEvents);
10374
10375})(window.wts, window.wts.modules);;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.