You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4108 lines
107 KiB

6 days ago
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head th:replace="~{fragments/user-head :: head}"></head>
  4. <header th:replace="~{user/razor-pay :: header}"></header>
  5. <style>
  6. :root {
  7. --accent: #12363b;
  8. --muted: #6b6b6b;
  9. --card-bg: #ffffff;
  10. --slot-radius: 10px;
  11. --green-start: #16a34a;
  12. --green-end: #09905a;
  13. }
  14. * {
  15. box-sizing: border-box;
  16. margin: 0;
  17. padding: 0
  18. }
  19. html,
  20. body {
  21. height: 100%
  22. }
  23. body {
  24. font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
  25. background:
  26. linear-gradient(rgba(255, 255, 255, 0.70), rgba(255, 255, 255, 0.82)),
  27. url('/victoriapublichall/images/vph-background_v1.jpg') top center / cover no-repeat,
  28. url('/victoriapublichall/images/vph-background_v1.jpg') bottom center / cover no-repeat;
  29. min-height: 100vh;
  30. background-attachment: fixed;
  31. /* keeps both images static */
  32. background-repeat: no-repeat;
  33. background-size: cover;
  34. -webkit-font-smoothing: antialiased;
  35. min-height: 100vh;
  36. display: flex;
  37. flex-direction: column;
  38. gap: 12px;
  39. padding: 18px 24px;
  40. }
  41. /* container */
  42. .container {
  43. width: 100%;
  44. max-width: 1200px;
  45. margin: 0 auto;
  46. }
  47. /******************************
  48. NAVBAR (desktop + mobile)
  49. ******************************/
  50. .topbar {
  51. display: flex;
  52. align-items: center;
  53. justify-content: space-between;
  54. gap: 16px;
  55. padding: 10px 0;
  56. position: relative;
  57. z-index: 1300;
  58. }
  59. .leftbar {
  60. display: flex;
  61. align-items: center;
  62. gap: 20px;
  63. }
  64. .nav-left {
  65. display: flex;
  66. align-items: center;
  67. gap: 12px;
  68. }
  69. .logo {
  70. width: 72px;
  71. height: 72px;
  72. object-fit: contain;
  73. border-radius: 50%;
  74. }
  75. /* default desktop nav */
  76. nav.primary {
  77. display: flex;
  78. gap: 18px;
  79. align-items: center;
  80. }
  81. /* Desktop Navigation Links (Figma Accurate) */
  82. nav.primary a {
  83. font-family: "Playfair Display", serif !important;
  84. font-weight: 400 !important;
  85. /* Regular */
  86. font-style: normal !important;
  87. font-size: 15px !important;
  88. /* Figma size */
  89. line-height: 100% !important;
  90. letter-spacing: 0 !important;
  91. color: #3C1E12;
  92. /* matches Book a Visit color */
  93. text-decoration: none;
  94. padding: 6px 8px;
  95. /* keep your layout spacing */
  96. border-radius: 6px;
  97. transition: background 0.2s ease;
  98. }
  99. /* Hover state (optional subtle effect like Figma preview) */
  100. nav.primary a:hover {
  101. background: rgba(0, 0, 0, 0.06);
  102. color: #3C1E12;
  103. /* no change in color */
  104. }
  105. /* Mobile menu links */
  106. .mobile-nav a {
  107. font-family: "Playfair Display", serif !important;
  108. font-weight: 400 !important;
  109. font-size: 15px !important;
  110. letter-spacing: 0 !important;
  111. color: #3C1E12;
  112. }
  113. .rightbar {
  114. display: flex;
  115. align-items: center;
  116. gap: 12px;
  117. }
  118. #desktopBookBtn {
  119. width: 174px;
  120. /* Figma button width */
  121. height: 57px;
  122. /* Figma button height */
  123. display: inline-flex;
  124. align-items: center;
  125. justify-content: center;
  126. background: transparent;
  127. border-radius: 8px;
  128. /* Figma corner radius */
  129. /* Border from Figma (thin outline) */
  130. border: 1px solid rgba(60, 30, 18, 0.40);
  131. /* darker visible border */
  132. cursor: pointer;
  133. /* Figma Typography */
  134. font-family: "Playfair Display", serif !important;
  135. font-weight: 700 !important;
  136. /* Bold */
  137. font-style: italic !important;
  138. font-size: 24px !important;
  139. line-height: 100% !important;
  140. /* Letter spacing -6% = -0.06em */
  141. letter-spacing: -0.06em !important;
  142. color: #3C1E12 !important;
  143. }
  144. /* Hover effect (Figma-like subtle shadow) */
  145. #desktopBookBtn:hover {
  146. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.10);
  147. }
  148. /* hamburger (hidden on desktop) */
  149. .hamburger {
  150. display: none;
  151. align-items: center;
  152. justify-content: center;
  153. width: 44px;
  154. height: 44px;
  155. border-radius: 8px;
  156. border: 1px solid rgba(0, 0, 0, 0.06);
  157. background: transparent;
  158. cursor: pointer;
  159. padding: 8px;
  160. }
  161. .hamburger .bar {
  162. display: block;
  163. width: 18px;
  164. height: 2px;
  165. background: var(--dark);
  166. margin: 3px 0;
  167. transition: transform .2s, opacity .2s;
  168. }
  169. /* mobile floating menu + overlay (initial hidden) */
  170. .mobile-overlay {
  171. position: fixed;
  172. inset: 0;
  173. background: rgba(0, 0, 0, 0.22);
  174. z-index: 1350;
  175. opacity: 0;
  176. pointer-events: none;
  177. transition: opacity .22s ease;
  178. }
  179. .mobile-overlay.show {
  180. opacity: 1;
  181. pointer-events: auto;
  182. }
  183. .mobile-nav {
  184. position: fixed;
  185. left: 12px;
  186. right: 12px;
  187. top: 68px;
  188. max-width: 420px;
  189. margin: 0 auto;
  190. background: #fff;
  191. border-radius: 12px;
  192. padding: 14px;
  193. box-shadow: 0 22px 60px rgba(0, 0, 0, 0.16);
  194. border: 1px solid rgba(0, 0, 0, 0.06);
  195. z-index: 1400;
  196. transform: translateY(-8px) scale(.995);
  197. opacity: 0;
  198. pointer-events: none;
  199. transition: opacity .22s ease, transform .22s ease;
  200. }
  201. .mobile-nav.open {
  202. opacity: 1;
  203. transform: translateY(0) scale(1);
  204. pointer-events: auto;
  205. }
  206. .mobile-nav a {
  207. display: block;
  208. padding: 10px 12px;
  209. color: var(--dark);
  210. text-decoration: none;
  211. border-radius: 8px;
  212. font-weight: 600;
  213. }
  214. .mobile-nav a+a {
  215. margin-top: 8px;
  216. }
  217. .mobile-nav .book-cta {
  218. background: #f7f9f9;
  219. text-align: center;
  220. padding: 12px;
  221. color: var(--accent);
  222. font-weight: 700;
  223. }
  224. /******************************
  225. PAGE / HERO / PANEL
  226. ******************************/
  227. .page {
  228. width: 100%;
  229. display: flex;
  230. flex-direction: column;
  231. align-items: center;
  232. justify-content: flex-start;
  233. margin-top: 20px;
  234. }
  235. .panel-wrap {
  236. width: 100%;
  237. display: flex;
  238. justify-content: center;
  239. align-items: center;
  240. margin-top: 18px;
  241. }
  242. .hero {
  243. width: 100%;
  244. max-width: 920px;
  245. text-align: center;
  246. margin-bottom: 12px;
  247. }
  248. /* Desktop style */
  249. .page h1 {
  250. font-family: "Inter", sans-serif;
  251. font-weight: 700;
  252. font-style: normal;
  253. font-size: 48px;
  254. line-height: 100%;
  255. letter-spacing: 0;
  256. text-align: center;
  257. width: 100%;
  258. max-width: 444px;
  259. /* Figma width */
  260. margin: 0 auto 10px;
  261. /* Center + bottom spacing */
  262. color: #3B1E14 !important;
  263. }
  264. .hero .lead {
  265. font-family: "Inter", sans-serif;
  266. font-weight: 400;
  267. font-style: normal;
  268. font-size: 20px;
  269. line-height: 100%;
  270. letter-spacing: 0;
  271. text-align: center;
  272. width: 100%;
  273. max-width: 417px;
  274. /* From Figma */
  275. margin: 0 auto 20px;
  276. /* Center + spacing */
  277. color: #000000;
  278. /* BLACK COLOR as requested */
  279. }
  280. .muted-label {
  281. font-family: "Inter", sans-serif;
  282. font-weight: 700;
  283. font-style: normal;
  284. font-size: 16px;
  285. line-height: 100%;
  286. letter-spacing: 0;
  287. color: #3B1E14;
  288. width: 100%;
  289. max-width: 100%;
  290. margin-bottom: 8px;
  291. text-align: left;
  292. display: block;
  293. margin-left: 0px !important;
  294. /* ⬅ FIXES CENTER ISSUE */
  295. }
  296. .input {
  297. width: 100%;
  298. padding: 14px 16px;
  299. border-radius: 12px;
  300. border: 1px solid rgba(0, 0, 0, 0.06);
  301. background: #fff;
  302. font-size: 16px;
  303. }
  304. .btn {
  305. display: inline-block;
  306. padding: 14px 18px;
  307. border-radius: 12px;
  308. background: var(--accent);
  309. color: #fff;
  310. font-weight: 700;
  311. border: none;
  312. cursor: pointer;
  313. box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
  314. }
  315. /* CSS - responsive behavior */
  316. /* default field behaviour */
  317. .field {
  318. width: 100%;
  319. max-width: 760px;
  320. /* original maximum */
  321. margin: 0 auto 18px;
  322. /* center and spacing */
  323. margin-top: 0 auto 18px;
  324. }
  325. /* compact variant to match your 536x48 measurement */
  326. .field .input.compact {
  327. box-sizing: border-box;
  328. width: 100%;
  329. height: 48px;
  330. /* your requested height */
  331. padding: 10px 14px;
  332. /* inner spacing */
  333. border-radius: 10px;
  334. border: 1px solid rgba(0, 0, 0, 0.06);
  335. font-size: 15px;
  336. background-color: white;
  337. }
  338. #contactDisplay {
  339. color: #666;
  340. font-weight: 700;
  341. padding: 10px 14px;
  342. border-radius: 10px;
  343. min-width: 180px;
  344. }
  345. #changeContact {
  346. color: #0478D0;
  347. text-decoration: underline;
  348. cursor: pointer;
  349. /*margin-left: 40px;*/
  350. font-size: medium;
  351. }
  352. .muted-label {
  353. display: block;
  354. margin-bottom: 8px;
  355. color: #000;
  356. font-weight: 700;
  357. }
  358. /* Desktop */
  359. .people-row {
  360. display: flex;
  361. align-items: center;
  362. gap: 16px;
  363. /*white-space: nowrap;*/
  364. }
  365. .muted-label-inline {
  366. font-weight: 600;
  367. color: black;
  368. /*margin-right: 320px;*/
  369. font-size: 16px;
  370. }
  371. .small {
  372. height: 40px;
  373. width: 40px;
  374. display: flex;
  375. align-items: center;
  376. justify-content: center;
  377. border-radius: 8px;
  378. border: 1px solid #ddd;
  379. background: #fff;
  380. cursor: pointer;
  381. }
  382. .counter {
  383. min-width: 40px;
  384. height: 40px;
  385. display: flex;
  386. align-items: center;
  387. justify-content: center;
  388. font-weight: 700;
  389. border: 1px solid #ddd;
  390. background: #fff;
  391. border-radius: 8px;
  392. }
  393. .people-support {
  394. margin-top: 10px;
  395. color: #000000 !important;
  396. font-size: 13px;
  397. }
  398. .people-support .link {
  399. color: #0478D0;
  400. text-decoration: none;
  401. }
  402. /* a little spacing when controls are inline with other content */
  403. .spacer {
  404. width: 12px;
  405. }
  406. .date-wrap {
  407. display: flex;
  408. align-items: center;
  409. gap: 10px;
  410. position: relative;
  411. }
  412. /* main date input visual */
  413. .date-wrap .input[type="date"] {
  414. font-family: "Inter", sans-serif !important;
  415. font-size: 16px;
  416. color: #0000 !important;
  417. padding: 12px 44px 12px 12px;
  418. /* reserve space on right for icon */
  419. width: 100%;
  420. background: #fff;
  421. border: 1px solid #ccc;
  422. border-radius: 12px;
  423. box-sizing: border-box;
  424. cursor: pointer;
  425. background-color: white;
  426. /* keep native appearance so browsers show their indicator */
  427. -webkit-appearance: textfield;
  428. appearance: textfield;
  429. }
  430. /* Force inner pieces to be black in WebKit browsers */
  431. .date-wrap .input[type="date"]::-webkit-datetime-edit,
  432. .date-wrap .input[type="date"]::-webkit-datetime-edit-text,
  433. .date-wrap .input[type="date"]::-webkit-datetime-edit-month-field,
  434. .date-wrap .input[type="date"]::-webkit-datetime-edit-day-field,
  435. .date-wrap .input[type="date"]::-webkit-datetime-edit-year-field {
  436. color: #000 !important;
  437. }
  438. .date-wrap .input[type="date"]::-webkit-calendar-picker-indicator {
  439. position: absolute;
  440. /* allow us to place it over the input area */
  441. right: 12px;
  442. /* matches padding-right */
  443. top: 50%;
  444. transform: translateY(-50%);
  445. width: 22px;
  446. height: 22px;
  447. opacity: 1;
  448. /* ensure visible */
  449. cursor: pointer;
  450. filter: none;
  451. /* remove inversion if any */
  452. -webkit-appearance: auto;
  453. /* let browser draw its icon */
  454. }
  455. .date-wrap .input[type="date"]::-moz-focus-inner {
  456. border: 0;
  457. }
  458. /* fallback: custom inline SVG icon placed absolutely (only shows if native icon not visible) */
  459. .date-wrap::after {
  460. content: "";
  461. position: absolute;
  462. right: 12px;
  463. top: 50%;
  464. transform: translateY(-50%);
  465. width: 22px;
  466. height: 22px;
  467. pointer-events: none;
  468. /* indicator should not intercept clicks; native indicator will */
  469. background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='%23000'><path d='M7 10h5v5H7zm-2 9h14V8H5zm14-13h-2V4h-2v2H9V4H7v2H5v2h14z'/></svg>");
  470. background-repeat: no-repeat;
  471. background-position: center;
  472. background-size: 20px 20px;
  473. opacity: 0.9;
  474. }
  475. .date-icon {
  476. width: 40px;
  477. height: 40px;
  478. display: flex;
  479. align-items: center;
  480. justify-content: center;
  481. border-radius: 10px;
  482. background: rgba(0, 0, 0, 0.04);
  483. }
  484. .counter {
  485. width: 46px;
  486. text-align: center;
  487. font-weight: 700;
  488. }
  489. /* time range pills */
  490. /* pills */
  491. .time-ranges {
  492. display: flex;
  493. gap: 10px;
  494. flex-wrap: wrap;
  495. margin: 14px 0 8px;
  496. }
  497. .pill {
  498. padding: 10px 12px;
  499. border-radius: 10px;
  500. background: rgba(255, 255, 255, 0.95);
  501. border: 1px solid rgba(0, 0, 0, 0.06);
  502. min-width: 110px;
  503. text-align: center;
  504. cursor: pointer;
  505. user-select: none;
  506. }
  507. .pill small {
  508. display: block;
  509. color: var(--muted);
  510. margin-top: 6px;
  511. font-weight: 600;
  512. font-size: 12px;
  513. }
  514. .pill.active {
  515. background: linear-gradient(180deg, var(--green-start), var(--green-end));
  516. color: #fff;
  517. border-color: transparent;
  518. box-shadow: 0 8px 26px rgba(0, 0, 0, 0.08);
  519. }
  520. .pill.active small {
  521. color: #fff !important;
  522. }
  523. /* info row: left multi-line, right inline summary */
  524. .info-row {
  525. display: flex;
  526. justify-content: space-between;
  527. align-items: flex-start;
  528. gap: 18px;
  529. margin-top: 6px;
  530. }
  531. .preview-note-box {
  532. width: 320px;
  533. font-size: 13px;
  534. color: var(--muted);
  535. line-height: 1.45;
  536. }
  537. .summary-inline {
  538. display: flex;
  539. gap: 18px;
  540. align-items: center;
  541. /*white-space: nowrap;*/
  542. flex-shrink: 1;
  543. flex-wrap: wrap;
  544. }
  545. .summary-item {
  546. font-size: 13px;
  547. color: var(--muted);
  548. }
  549. .summary-item strong {
  550. margin-left: 6px;
  551. font-weight: 700;
  552. }
  553. /* once a slot is chosen and locked, make it look non-clickable */
  554. .slot.locked {
  555. cursor: not-allowed;
  556. }
  557. /* slots grid and slot styles */
  558. .slots-grid {
  559. display: grid;
  560. grid-template-columns: 1fr 1fr;
  561. gap: 12px;
  562. margin-top: 14px;
  563. }
  564. .slot {
  565. display: flex;
  566. align-items: center;
  567. justify-content: space-between;
  568. padding: 10px 12px;
  569. border-radius: 10px;
  570. background: var(--card-bg);
  571. border: 1px solid rgba(0, 0, 0, 0.06);
  572. cursor: pointer;
  573. }
  574. .slot.disabled {
  575. background: #f1f1f1;
  576. color: #8b8b8b;
  577. cursor: not-allowed;
  578. }
  579. .left {
  580. display: flex;
  581. flex-direction: column;
  582. gap: 6px;
  583. }
  584. .time {
  585. font-weight: 700;
  586. }
  587. .meta {
  588. font-size: 13px;
  589. color: var(--muted);
  590. }
  591. .choose {
  592. width: 36px;
  593. height: 36px;
  594. border-radius: 8px;
  595. border: 1px solid rgba(0, 0, 0, 0.08);
  596. display: flex;
  597. align-items: center;
  598. justify-content: center;
  599. font-weight: 700;
  600. font-size: 16px;
  601. }
  602. .slot .time {
  603. font-weight: 700;
  604. color: #000 !important;
  605. /* always black */
  606. }
  607. .slot.chosen {
  608. background: #ffffff !important;
  609. /* keep slot white */
  610. color: #fff;
  611. border-color: transparent;
  612. border-color: rgba(0, 0, 0, 0.06) !important;
  613. /* optional subtle glow around the entire slot (comment out if not wanted) */
  614. box-shadow: 0 6px 18px rgba(9, 144, 90, 0.04);
  615. }
  616. /* .slot.chosen .meta{ color: rgba(255,255,255,0.9); } */
  617. .slot.chosen .choose {
  618. background: linear-gradient(180deg, var(--green-start), var(--green-end)) !important;
  619. color: #fff;
  620. border-color: transparent !important;
  621. box-shadow: 0 6px 16px rgba(9, 144, 90, 0.12) !important;
  622. }
  623. .info-row {
  624. display: flex !important;
  625. flex-direction: column !important;
  626. /* always stacked */
  627. align-items: center !important;
  628. /* center items horizontally */
  629. justify-content: flex-start;
  630. gap: 14px;
  631. width: 100%;
  632. max-width: 820px;
  633. margin: 0 auto;
  634. box-sizing: border-box;
  635. }
  636. /* preview note becomes a centered block (fluid width) */
  637. .preview-note-box {
  638. width: 100%;
  639. max-width: 640px;
  640. flex: 0 0 auto;
  641. font-size: 13px;
  642. color: #000000;
  643. line-height: 1.45;
  644. text-align: left !important;
  645. /* ← LEFT aligned */
  646. padding: 6px 8px;
  647. }
  648. .summary-inline {
  649. width: 100% !important;
  650. /* use the full container width */
  651. max-width: 100% !important;
  652. /* don't constrain to 420px here */
  653. display: flex !important;
  654. align-items: center !important;
  655. justify-content: flex-end !important;
  656. /* left / right extremes */
  657. gap: 24px !important;
  658. padding: 6px 8px !important;
  659. box-sizing: border-box !important;
  660. margin: 0 auto !important;
  661. white-space: nowrap !important;
  662. }
  663. /* Make each item expand to fill half the line */
  664. .summary-item {
  665. display: flex !important;
  666. justify-content: flex-end !important;
  667. /* both right-aligned */
  668. /* align-items: center !important; */
  669. gap: 8px !important;
  670. line-height: 1 !important;
  671. flex: 0 0 auto !important;
  672. /* allow items to grow and take space */
  673. min-width: 0 !important;
  674. /* prevent overflow due to long text */
  675. text-align: right !important;
  676. }
  677. /* Left block: align contents to left */
  678. .summary-item.unallocated {
  679. justify-content: flex-end !important;
  680. text-align: right !important;
  681. /* color: #E63946 !important; */
  682. }
  683. /* Right block: align contents to right */
  684. .summary-item.allocated {
  685. justify-content: flex-end !important;
  686. text-align: right !important;
  687. /* color: #16A34A !important; */
  688. }
  689. /* Keep numeric counter compact and centered inside its area */
  690. .summary-item strong {
  691. min-width: 28px !important;
  692. display: inline-block !important;
  693. text-align: center !important;
  694. }
  695. /* DEFAULT color = black */
  696. .summary-item span,
  697. .summary-item strong {
  698. color: #000 !important;
  699. }
  700. /* Red */
  701. .text-red span,
  702. .text-red strong {
  703. color: #E63946 !important;
  704. }
  705. /* Green */
  706. .text-green span,
  707. .text-green strong {
  708. color: #16A34A !important;
  709. }
  710. .submit-note {
  711. font-family: "Inter", sans-serif;
  712. font-size: 13px;
  713. color: #000000;
  714. text-align: left;
  715. /* left aligned */
  716. margin: 3px 0 10px 0;
  717. /* top:3px, bottom:10px */
  718. margin-left: 3px;
  719. }
  720. .submit-wrap {
  721. margin-top: 16px;
  722. }
  723. /* Book Visit — visible by default */
  724. .book-vist {
  725. opacity: 1;
  726. pointer-events: auto;
  727. }
  728. /* Cancel Book — HIDDEN by default */
  729. .cancel-book {
  730. display: none;
  731. /* ⬅ start hidden */
  732. }
  733. /* Utility classes that JS toggles */
  734. .section-hidden {
  735. display: none !important;
  736. }
  737. .section-enabled {
  738. display: block !important;
  739. opacity: 1 !important;
  740. pointer-events: auto !important;
  741. }
  742. /* When Cancel is visible, style the button normally */
  743. .cancel-book.section-enabled .btn {
  744. background: #043B55;
  745. color: #ffffff;
  746. border-radius: 10px;
  747. border: none;
  748. cursor: pointer;
  749. font-weight: 700;
  750. box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
  751. }
  752. /* summary card inside cancel section (you already had this) */
  753. .summary-card {
  754. max-width: 100%;
  755. background: #fff;
  756. border-radius: 10px;
  757. padding: 18px 20px;
  758. border: 2px solid rgba(0, 0, 0, 0.12);
  759. box-sizing: border-box;
  760. font-family: Inter, sans-serif;
  761. color: #111;
  762. margin: 0 auto;
  763. }
  764. /* Top row */
  765. .summary-row {
  766. display: flex;
  767. justify-content: space-between;
  768. align-items: flex-start;
  769. margin-bottom: 12px;
  770. }
  771. .summary-left .label {
  772. font-size: 13px;
  773. font-weight: 700;
  774. margin-bottom: 10px;
  775. color: black;
  776. }
  777. .summary-right div {
  778. font-size: 13px;
  779. font-weight: 400;
  780. margin-bottom: 10px;
  781. text-align: right;
  782. color: black;
  783. }
  784. /* AV */
  785. .av-title {
  786. font-size: 13px;
  787. font-weight: 700;
  788. color: black;
  789. margin: 6px 0 10px;
  790. }
  791. .av-list {
  792. display: flex;
  793. flex-direction: column;
  794. gap: 8px;
  795. }
  796. .av-row {
  797. display: flex;
  798. justify-content: space-between;
  799. font-size: 13px;
  800. }
  801. .av-time,
  802. .av-seats {
  803. font-size: 13px;
  804. font-weight: 400;
  805. margin-bottom: 10px;
  806. }
  807. /* main buttons (book visit / preview etc.) */
  808. .submit-wrap {
  809. margin-top: 16px;
  810. }
  811. .btn {
  812. width: 100%;
  813. padding: 12px 26px;
  814. background: #043B55;
  815. color: #ffffff;
  816. border-radius: 10px;
  817. border: none;
  818. cursor: pointer;
  819. font-weight: 700;
  820. box-sizing: border-box;
  821. margin-bottom: 15px;
  822. }
  823. /* small note text under cards */
  824. .submit-note {
  825. font-family: "Inter", sans-serif;
  826. font-size: 13px;
  827. color: #000000;
  828. text-align: left;
  829. margin: 10px 0;
  830. margin-left: 3px;
  831. font-weight: 700;
  832. }
  833. /* make the button INSIDE cancel-book clearly disabled */
  834. .cancel-book.section-enabled .btn {
  835. background: #fff;
  836. /* SAME background */
  837. color: #000;
  838. border-radius: 10px;
  839. border: 3px solid #000;
  840. /* SAME border */
  841. cursor: pointer;
  842. font-weight: 700;
  843. box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
  844. }
  845. /* ---------- Mobile polish (drop this at the end of your CSS) ---------- */
  846. @media (max-width: 520px) {
  847. /* page / body */
  848. body {
  849. padding: 12px 10px;
  850. background-position: center top;
  851. background-size: cover;
  852. -webkit-font-smoothing: antialiased;
  853. }
  854. /* container & panels */
  855. .container,
  856. .panel-wrap,
  857. .page {
  858. width: 100%;
  859. max-width: 420px;
  860. margin: 0 auto;
  861. padding: 0 8px;
  862. box-sizing: border-box;
  863. }
  864. .panel-wrap {
  865. padding: 6px 0 18px;
  866. }
  867. /* header / nav */
  868. .logo {
  869. width: 61px;
  870. height: 61px;
  871. }
  872. nav.primary {
  873. display: none !important;
  874. }
  875. #desktopBookBtn {
  876. display: none !important;
  877. }
  878. .hamburger {
  879. display: flex !important;
  880. }
  881. /* hero */
  882. .page h1 {
  883. font-size: 32px !important;
  884. width: 100%;
  885. max-width: 360px;
  886. margin: 6px auto 8px;
  887. }
  888. .hero .lead {
  889. font-size: 14px !important;
  890. max-width: 320px;
  891. margin-bottom: 12px;
  892. }
  893. /* fields */
  894. .field {
  895. width: calc(100% - 20px);
  896. margin-left: 10px;
  897. margin-right: 10px;
  898. max-width: none;
  899. }
  900. .field .input,
  901. .field .input.compact {
  902. width: 100%;
  903. font-size: 15px;
  904. padding: 12px 14px;
  905. height: 46px;
  906. border-radius: 10px;
  907. }
  908. /* contact row */
  909. #contactLine {
  910. display: flex;
  911. align-items: center;
  912. justify-content: space-between;
  913. gap: 8px;
  914. }
  915. #contactDisplay {
  916. min-width: 0;
  917. flex: 1 1 auto;
  918. padding: 10px 12px;
  919. font-weight: 700;
  920. border-radius: 10px;
  921. overflow: hidden;
  922. text-overflow: ellipsis;
  923. white-space: nowrap;
  924. margin-left: -14px
  925. }
  926. #changeContact {
  927. margin-left: 10px;
  928. }
  929. /* label and inline people row */
  930. .muted-label {
  931. font-size: 16px;
  932. margin-left: 0 !important;
  933. display: block;
  934. }
  935. /* whole row */
  936. .people-row {
  937. display: flex;
  938. align-items: center;
  939. width: 100%;
  940. gap: 8px;
  941. /* IMPORTANT: allow wrapping so row doesn't overflow */
  942. white-space: normal;
  943. }
  944. /* label on the left */
  945. .muted-label-inline {
  946. flex: 1 1 auto;
  947. /* take left side space */
  948. margin-right: 0;
  949. font-size: 15px;
  950. font-weight: 600;
  951. color: black;
  952. text-align: left;
  953. }
  954. /* Hover effect for Select Date input */
  955. .date-wrap .input[type="date"]:hover {
  956. border-color: #043B55;
  957. /* brand color */
  958. box-shadow: 0 0 0 2px rgba(4, 59, 85, 0.12);
  959. cursor: pointer;
  960. }
  961. /* Focus (keyboard / click) */
  962. .date-wrap .input[type="date"]:focus {
  963. outline: none;
  964. border-color: #043B55;
  965. box-shadow: 0 0 0 3px rgba(4, 59, 85, 0.18);
  966. }
  967. /* push controls to the right */
  968. #minusPeople {
  969. margin-left: auto;
  970. /* this pushes the group to the right */
  971. }
  972. #peopleCount,
  973. #plusPeople {
  974. margin-left: 6px;
  975. /* spacing between controls */
  976. }
  977. /* touch-friendly sizes */
  978. .small {
  979. width: 40px;
  980. height: 40px;
  981. border-radius: 10px;
  982. font-size: 18px;
  983. }
  984. .counter {
  985. min-width: 52px;
  986. height: 40px;
  987. border-radius: 10px;
  988. font-size: 16px;
  989. }
  990. /* pills / time ranges */
  991. .time-ranges {
  992. gap: 8px;
  993. margin: 12px 0;
  994. justify-content: flex-start;
  995. overflow-x: auto;
  996. -webkit-overflow-scrolling: touch;
  997. padding-bottom: 6px;
  998. }
  999. .time-ranges::-webkit-scrollbar {
  1000. display: none;
  1001. }
  1002. .pill {
  1003. min-width: 96px;
  1004. padding: 5px;
  1005. border-radius: 10px;
  1006. flex: 0 0 auto;
  1007. }
  1008. /* slots grid -> single column */
  1009. .slots-grid {
  1010. grid-template-columns: 1fr;
  1011. gap: 10px;
  1012. margin-top: 12px;
  1013. }
  1014. .slot {
  1015. padding: 10px 12px;
  1016. border-radius: 10px;
  1017. }
  1018. /* info row -> stack */
  1019. .info-row {
  1020. flex-direction: column;
  1021. align-items: stretch;
  1022. gap: 10px;
  1023. margin-top: 10px;
  1024. }
  1025. .preview-note-box {
  1026. width: 100%;
  1027. max-width: none;
  1028. font-size: 13px;
  1029. }
  1030. .summary-inline {
  1031. justify-content: flex-end !important;
  1032. gap: 12px !important;
  1033. flex-wrap: wrap !important;
  1034. white-space: normal !important;
  1035. }
  1036. .summary-item {
  1037. flex: 0 0 48% !important;
  1038. /* two-column layout on small screens */
  1039. box-sizing: border-box !important;
  1040. }
  1041. .summary-item.unallocated {
  1042. justify-content: flex-start !important;
  1043. text-align: left !important;
  1044. }
  1045. .summary-item.allocated {
  1046. justify-content: flex-end !important;
  1047. text-align: right !important;
  1048. }
  1049. /* submit area */
  1050. .submit-wrap {
  1051. margin-top: 18px;
  1052. padding-bottom: 18px;
  1053. }
  1054. .btn {
  1055. width: 100%;
  1056. padding: 12px 14px;
  1057. font-size: 16px;
  1058. border-radius: 12px;
  1059. }
  1060. /* small text adjustments */
  1061. .muted,
  1062. .people-support,
  1063. .summary-item {
  1064. font-size: 13px;
  1065. line-height: 1.3;
  1066. }
  1067. /* avoid long fixed widths on mobile that break layout */
  1068. .page h1,
  1069. .hero .lead,
  1070. .preview-note-box,
  1071. .summary-inline,
  1072. .field {
  1073. max-width: 100%;
  1074. }
  1075. /* small helper: ensure form controls never overflow */
  1076. input[type="text"],
  1077. input[type="date"],
  1078. textarea,
  1079. select {
  1080. max-width: 100%;
  1081. box-sizing: border-box;
  1082. }
  1083. /* interaction: slightly larger tap areas on interactive elements */
  1084. .pill,
  1085. .slot,
  1086. .small,
  1087. .btn {
  1088. touch-action: manipulation;
  1089. -webkit-tap-highlight-color: rgba(0, 0, 0, 0.06);
  1090. }
  1091. }
  1092. /* small helpers */
  1093. .link {
  1094. color: #0b76c6;
  1095. text-decoration: underline;
  1096. cursor: pointer;
  1097. font-size: 14px;
  1098. }
  1099. .vph-modal-overlay {
  1100. position: fixed;
  1101. inset: 0;
  1102. background: rgba(0, 0, 0, 0.56);
  1103. display: none;
  1104. align-items: center;
  1105. justify-content: center;
  1106. z-index: 9999;
  1107. padding: 28px;
  1108. -webkit-font-smoothing: antialiased;
  1109. -moz-osx-font-smoothing: grayscale;
  1110. }
  1111. .vph-modal-overlay.show {
  1112. display: flex !important;
  1113. }
  1114. /* modal card */
  1115. .vph-modal {
  1116. width: 500px;
  1117. max-width: 100%;
  1118. background: #fff;
  1119. border-radius: 12px;
  1120. box-shadow: 0 30px 80px rgba(7, 25, 30, 0.45);
  1121. padding: 20px 22px;
  1122. position: relative;
  1123. outline: none;
  1124. /* keep on-screen */
  1125. overflow: hidden;
  1126. /* inner area handles overflow */
  1127. display: flex;
  1128. flex-direction: column;
  1129. gap: 12px;
  1130. transition: transform .18s ease, opacity .18s ease;
  1131. transform: translateY(0);
  1132. }
  1133. /* when overlay appears the modal can fade in (optional) */
  1134. .vph-modal-overlay.show .vph-modal {
  1135. transform: translateY(0);
  1136. opacity: 1;
  1137. }
  1138. /* header */
  1139. .vph-modal-header {
  1140. text-align: center;
  1141. margin-top: 2px;
  1142. }
  1143. .vph-modal-header h3 {
  1144. margin: 0;
  1145. font-weight: 700;
  1146. color: #3B1E14;
  1147. font-size: 18px;
  1148. letter-spacing: 0;
  1149. font-family: "Inter", sans-serif !important;
  1150. /* Add Inter font */
  1151. }
  1152. /* content area (scrollable) */
  1153. .vph-modal .vph-modal-content {
  1154. flex: 1 1 auto;
  1155. overflow-y: auto;
  1156. padding-right: 6px;
  1157. }
  1158. /* summary box (top) */
  1159. .vph-summary-box {
  1160. min-height: auto;
  1161. /* ✅ allow growth */
  1162. height: auto;
  1163. /* ✅ dynamic */
  1164. background: #f3f4f2;
  1165. /* subtle off-white */
  1166. border-radius: 8px;
  1167. padding: 12px;
  1168. margin: 0;
  1169. border: 1px solid rgba(0, 0, 0, 0.02);
  1170. }
  1171. /* two-column summary row */
  1172. .vph-summary-row {
  1173. display: flex;
  1174. gap: 12px;
  1175. align-items: flex-start;
  1176. justify-content: space-between;
  1177. }
  1178. .vph-summary-left,
  1179. .vph-summary-right {
  1180. width: 48%;
  1181. display: flex;
  1182. flex-direction: column;
  1183. gap: 10px;
  1184. }
  1185. .vph-summary-left .vph-label {
  1186. color: #000 !important;
  1187. /* Black text */
  1188. font-size: 13px;
  1189. font-weight: 700 !important;
  1190. /* Normal, not bold */
  1191. font-family: "Inter", sans-serif !important;
  1192. /* Add Inter font */
  1193. }
  1194. .vph-summary-right {
  1195. text-align: right;
  1196. color: #0b0b0b;
  1197. font-size: 14px;
  1198. font-weight: 400;
  1199. }
  1200. /* AV Experience box */
  1201. .vph-av-box {
  1202. /* width:562px; */
  1203. /* height:114px; */
  1204. max-height: 30vh;
  1205. overflow-y: auto;
  1206. background: #f3f4f2;
  1207. border-radius: 8px;
  1208. padding: 10px 12px;
  1209. border: 1px solid rgba(0, 0, 0, 0.02);
  1210. }
  1211. .vph-av-box::-webkit-scrollbar {
  1212. width: 4px;
  1213. }
  1214. .vph-av-box::-webkit-scrollbar-thumb {
  1215. background: #b5b5b5;
  1216. border-radius: 5px;
  1217. }
  1218. .vph-av-row {
  1219. display: flex;
  1220. justify-content: space-between;
  1221. align-items: center;
  1222. padding: 8px 10px;
  1223. border-radius: 6px;
  1224. background: transparent;
  1225. }
  1226. .vph-av-time {
  1227. font-weight: 400;
  1228. color: #0b0b0b;
  1229. font-size: 14px;
  1230. }
  1231. .vph-av-meta {
  1232. font-weight: 400;
  1233. color: #0b0b0b;
  1234. font-size: 14px;
  1235. }
  1236. /* footer area — controls */
  1237. .vph-modal-footer {
  1238. display: flex;
  1239. gap: 12px;
  1240. align-items: center;
  1241. margin-top: auto;
  1242. flex-shrink: 0;
  1243. }
  1244. /* left icon button (circle) */
  1245. .vph-modal-icon-btn {
  1246. Width: 66px;
  1247. Height: 66px;
  1248. border-radius: 10px;
  1249. border: 1px solid #043B55;
  1250. background: #fff;
  1251. display: inline-flex;
  1252. align-items: center;
  1253. justify-content: center;
  1254. cursor: pointer;
  1255. box-shadow: 0 6px 18px rgba(7, 25, 30, 0.04);
  1256. }
  1257. .vph-modal-submit {
  1258. width: 100%;
  1259. /* responsive */
  1260. max-width: 482px;
  1261. /* desktop size */
  1262. height: 66px;
  1263. padding: 12px 36px;
  1264. border-radius: 10px;
  1265. background: #043B55;
  1266. color: #fff;
  1267. border: none;
  1268. font-weight: 700;
  1269. cursor: pointer;
  1270. box-shadow: 0 10px 30px rgba(11, 75, 72, 0.14);
  1271. }
  1272. /* close X */
  1273. .vph-modal-close {
  1274. position: absolute;
  1275. right: 12px;
  1276. top: 12px;
  1277. width: 36px;
  1278. height: 36px;
  1279. border-radius: 8px;
  1280. border: none;
  1281. background: transparent;
  1282. font-size: 20px;
  1283. cursor: pointer;
  1284. color: #6b6b6b;
  1285. }
  1286. /* ===== Cancel Booking modal helpers (re-use existing .vph-modal styles) ===== */
  1287. /* two-step views */
  1288. .vph-view {
  1289. display: none;
  1290. }
  1291. .vph-view.active {
  1292. display: block;
  1293. }
  1294. /* scrollable content area if you ever need body text */
  1295. /* footer: normal row + column variant for success view */
  1296. .vph-modal-footer {
  1297. display: flex;
  1298. gap: 12px;
  1299. align-items: center;
  1300. justify-content: space-between;
  1301. margin-top: 25px;
  1302. }
  1303. .vph-modal-footer.column {
  1304. flex-direction: column;
  1305. align-items: stretch;
  1306. gap: 12px;
  1307. margin-top: 14px;
  1308. }
  1309. /* icon button on left */
  1310. .vph-modal-icon-btn {
  1311. width: 66px;
  1312. height: 50px;
  1313. border-radius: 10px;
  1314. border: 1px solid #043B55;
  1315. background: #fff;
  1316. display: inline-flex;
  1317. align-items: center;
  1318. justify-content: center;
  1319. cursor: pointer;
  1320. box-shadow: 0 6px 18px rgba(7, 25, 30, 0.04);
  1321. }
  1322. /* primary + ghost buttons for cancel modal */
  1323. .vph-modal-submit.primary {
  1324. height: 50px;
  1325. width: 100%;
  1326. padding: 14px 18px;
  1327. border-radius: 10px;
  1328. background: #043B55;
  1329. color: #fff;
  1330. border: none;
  1331. font-weight: 700;
  1332. cursor: pointer;
  1333. box-shadow: 0 10px 30px rgba(11, 75, 72, 0.14);
  1334. }
  1335. .vph-modal-submit.ghost {
  1336. height: 50px;
  1337. width: 100%;
  1338. padding: 12px 16px;
  1339. border-radius: 10px;
  1340. background: #fff;
  1341. color: #0b0b0b;
  1342. border: 2px solid rgba(0, 0, 0, 0.08);
  1343. font-weight: 700;
  1344. cursor: pointer;
  1345. }
  1346. /* small compact info box (if needed later) */
  1347. .vph-summary-box.small {
  1348. background: #f7f7f6;
  1349. border-radius: 8px;
  1350. padding: 10px;
  1351. border: 1px solid rgba(0, 0, 0, 0.04);
  1352. margin-bottom: 6px;
  1353. text-align: center;
  1354. }
  1355. /* small screens: stack and make scrollable */
  1356. @media (max-width: 520px) {
  1357. .vph-modal {
  1358. width: 94%;
  1359. padding: 14px;
  1360. border-radius: 12px;
  1361. max-height: 92vh;
  1362. }
  1363. .vph-summary-row {
  1364. gap: 10px;
  1365. flex-direction: row;
  1366. }
  1367. .vph-summary-left,
  1368. .vph-summary-right {
  1369. width: 48%;
  1370. }
  1371. /*.vph-av-list {*/
  1372. /* max-height: 30vh;*/
  1373. /*}*/
  1374. .vph-modal-footer {
  1375. gap: 10px;
  1376. align-items: center;
  1377. }
  1378. .vph-modal-submit {
  1379. width: 56%;
  1380. padding: 12px;
  1381. width: 256px;
  1382. }
  1383. }
  1384. /* optional: thin custom scrollbar inside modal for better UX */
  1385. .vph-modal ::-webkit-scrollbar {
  1386. height: 8px;
  1387. width: 8px;
  1388. }
  1389. .vph-modal ::-webkit-scrollbar-thumb {
  1390. background: rgba(0, 0, 0, 0.08);
  1391. border-radius: 6px;
  1392. }
  1393. /* make sure text wrapping and truncation are tidy */
  1394. .vph-summary-right div,
  1395. .vph-summary-left div,
  1396. .vph-av-row {
  1397. word-break: break-word;
  1398. }
  1399. .panel {
  1400. background: transparent;
  1401. }
  1402. .date-wrap .form-control[readonly] {
  1403. background-color: #fff !important;
  1404. color: #000 !important;
  1405. }
  1406. .disabled-pill {
  1407. background-color: #eee !important;
  1408. border-color: #ddd !important;
  1409. }
  1410. .no-slots-message {
  1411. margin-top: 10px;
  1412. color: #c62828;
  1413. font-size: 14px;
  1414. font-weight: 600;
  1415. }
  1416. .no-slots-message-box {
  1417. padding: 12px;
  1418. margin-top: 10px;
  1419. text-align: center;
  1420. color: #d9534f;
  1421. /* red */
  1422. font-weight: 600;
  1423. font-size: 14px;
  1424. /* background: #ffeaea; */
  1425. /* soft red background */
  1426. border-radius: 6px;
  1427. }
  1428. /* make sure summary left labels are visible and take up space */
  1429. .summary-row {
  1430. display: flex;
  1431. align-items: flex-start;
  1432. gap: 12px;
  1433. }
  1434. .summary-left,
  1435. .summary-right {
  1436. width: 50%;
  1437. box-sizing: border-box;
  1438. }
  1439. .summary-left .label {
  1440. display: block;
  1441. color: #111 !important;
  1442. /* force readable color */
  1443. font-size: 13px;
  1444. font-weight: 700;
  1445. margin-bottom: 10px;
  1446. }
  1447. .summary-right>div {
  1448. display: block;
  1449. color: #111 !important;
  1450. font-weight: 400;
  1451. text-align: right;
  1452. margin-bottom: 10px;
  1453. }
  1454. /* =========================================
  1455. CALENDAR HOVER — FINAL GUARANTEED FIX
  1456. ========================================= */
  1457. /* Target the REAL visible flatpickr input */
  1458. .date-wrap input.input.form-control {
  1459. cursor: pointer;
  1460. background-color: #fff !important;
  1461. transition: border-color 0.15s ease,
  1462. box-shadow 0.15s ease,
  1463. background-color 0.15s ease;
  1464. }
  1465. /* Hover */
  1466. .date-wrap input.input.form-control:hover {
  1467. border-color: #043B55 !important;
  1468. box-shadow: 0 0 0 2px rgba(4, 59, 85, 0.18) !important;
  1469. background-color: #fafafa !important;
  1470. }
  1471. /* Focus */
  1472. .date-wrap input.input.form-control:focus {
  1473. outline: none !important;
  1474. border-color: #043B55 !important;
  1475. box-shadow: 0 0 0 3px rgba(4, 59, 85, 0.28) !important;
  1476. }
  1477. /* Optional: hover anywhere in wrapper */
  1478. .date-wrap:hover input.input.form-control {
  1479. border-color: #043B55 !important;
  1480. }
  1481. /* FIX: Cancel Booking button should NOT be full width */
  1482. #cancelBookSection .submit-wrap {
  1483. display: flex;
  1484. gap:12px;
  1485. justify-content: center;
  1486. flex-wrap: wrap; /* allow stacking */
  1487. }
  1488. #cancelBookSection .submit-wrap .btn {
  1489. width: auto !important;
  1490. max-width: none !important;
  1491. min-width: 240px;
  1492. padding: 12px 32px;
  1493. margin-right: 4px
  1494. }
  1495. /*
  1496. bugs follows
  1497. */
  1498. /* Interchange font sizes */
  1499. /* Make preview note text bigger */
  1500. .preview-note-box {
  1501. font-size: 16px !important;
  1502. font-weight: 500;
  1503. line-height: 1.6;
  1504. }
  1505. /* Make the label slightly smaller */
  1506. .field>.muted-label {
  1507. font-size: 14px !important;
  1508. font-weight: 600;
  1509. }
  1510. @keyframes shake {
  1511. 0% {
  1512. transform: translateX(0);
  1513. }
  1514. 20% {
  1515. transform: translateX(-5px);
  1516. }
  1517. 40% {
  1518. transform: translateX(5px);
  1519. }
  1520. 60% {
  1521. transform: translateX(-5px);
  1522. }
  1523. 80% {
  1524. transform: translateX(5px);
  1525. }
  1526. 100% {
  1527. transform: translateX(0);
  1528. }
  1529. }
  1530. .slot.shake {
  1531. animation: shake 0.35s ease;
  1532. border-color: #dc2626 !important;
  1533. }
  1534. .border-field {
  1535. border: 1px solid rgba(0, 0, 0, 0.15);
  1536. /* visible but subtle */
  1537. border-radius: 12px;
  1538. /* matches your UI */
  1539. padding: 16px 18px;
  1540. /* space inside box */
  1541. background: #ffffff;
  1542. /* white card look */
  1543. }
  1544. /* age group details Container */
  1545. /* Container */
  1546. .people-type-list {
  1547. margin-top: 12px;
  1548. display: grid;
  1549. /*grid-template-columns: repeat(2, 1fr); !* 2 by 2 *!*/
  1550. column-gap: 24px;
  1551. row-gap: 16px;
  1552. }
  1553. /* Each card */
  1554. .people-type-row {
  1555. display: flex;
  1556. justify-content: space-between;
  1557. align-items: center;
  1558. }
  1559. /* Label */
  1560. .people-type-label {
  1561. font-weight: 600;
  1562. }
  1563. /* Controls */
  1564. .people-type-controls {
  1565. display: flex;
  1566. align-items: center;
  1567. gap: 10px;
  1568. }
  1569. .people-total-row {
  1570. display: flex;
  1571. justify-content: flex-end;
  1572. /* ✅ pushes content to right */
  1573. align-items: center;
  1574. margin-top: 20px;
  1575. font-weight: 700;
  1576. font-size: 20px;
  1577. }
  1578. .people-total-label {
  1579. margin-right: 15px;
  1580. }
  1581. .people-total-value {
  1582. color: #043B55;
  1583. }
  1584. /* aadhar & passport */
  1585. .id-row {
  1586. display: grid;
  1587. grid-template-columns: 1fr 1fr;
  1588. gap: 12px;
  1589. margin-top: 6px;
  1590. padding-top: 10px;
  1591. padding-bottom: 10px;
  1592. }
  1593. /* ================================
  1594. PREVIEW MODAL – EXTRA DETAILS
  1595. ================================ */
  1596. /* Shared box (re-uses your visual language) */
  1597. .breakdown-box {
  1598. background: #f3f4f2;
  1599. border-radius: 8px;
  1600. padding: 14px 16px;
  1601. margin-top: 14px;
  1602. border: 1px solid rgba(0, 0, 0, 0.05);
  1603. }
  1604. /* Header row */
  1605. .breakdown-header {
  1606. display: flex;
  1607. justify-content: space-between;
  1608. align-items: center;
  1609. font-family: "Inter", sans-serif;
  1610. font-weight: 700;
  1611. font-size: 14px;
  1612. color: #111;
  1613. margin-bottom: 10px;
  1614. }
  1615. /* Rows container */
  1616. .breakdown-body {
  1617. display: flex;
  1618. flex-direction: column;
  1619. gap: 8px;
  1620. }
  1621. /* Each row */
  1622. .breakdown-row {
  1623. display: grid;
  1624. grid-template-columns: 1fr auto;
  1625. align-items: center;
  1626. font-family: "Inter", sans-serif;
  1627. font-size: 14px;
  1628. }
  1629. /* Labels */
  1630. .breakdown-label {
  1631. color: #111;
  1632. font-weight: 500;
  1633. }
  1634. /* Amount values */
  1635. .breakdown-value {
  1636. font-weight: 600;
  1637. text-align: right;
  1638. min-width: 70px;
  1639. }
  1640. /* Divider line */
  1641. .breakdown-divider {
  1642. margin: 10px 0;
  1643. border-top: 1px solid #d1d5db;
  1644. }
  1645. /* Total row */
  1646. .breakdown-row.total {
  1647. font-size: 16px;
  1648. font-weight: 700;
  1649. }
  1650. .breakdown-row.total .breakdown-value {
  1651. color: #043B55;
  1652. }
  1653. /* ================================
  1654. ID PROOF SECTION
  1655. ================================ */
  1656. .breakdown-idproof {
  1657. margin-top: 8px;
  1658. font-family: "Inter", sans-serif;
  1659. font-size: 14px;
  1660. color: #111;
  1661. display: flex;
  1662. gap: 8px;
  1663. flex-wrap: wrap;
  1664. }
  1665. .breakdown-idproof strong {
  1666. font-weight: 700;
  1667. }
  1668. @media (min-width: 768px) {
  1669. .muted-label-inline {
  1670. margin-right: 320px;
  1671. }
  1672. .people-type-list {
  1673. grid-template-columns: repeat(2, 1fr);
  1674. }
  1675. }
  1676. @media(min-width: 768px) {
  1677. #contactLine {
  1678. display: flex;
  1679. gap: 360px;
  1680. margin-left: -14px;
  1681. }
  1682. }
  1683. @media (max-width: 520px) {
  1684. .people-type-list {
  1685. grid-template-columns: 1fr;
  1686. }
  1687. }
  1688. @media (min-width: 521px) {
  1689. #cancelBookSection .submit-wrap .btn {
  1690. min-width: 200px;
  1691. width: auto !important;
  1692. }
  1693. }
  1694. /* Mobile */
  1695. @media (max-width: 520px) {
  1696. #cancelBookSection .submit-wrap {
  1697. flex-direction: column;
  1698. align-items: stretch;
  1699. }
  1700. #cancelBookSection .submit-wrap .btn {
  1701. width: 100% !important;
  1702. min-width: unset !important;
  1703. margin-right: 0 !important;
  1704. }
  1705. }
  1706. /* Cancel button – Red */
  1707. #cancelBtn {
  1708. background: #dc2626 !important; /* red-600 */
  1709. color: #fff !important;
  1710. border: none;
  1711. }
  1712. #cancelBtn:hover {
  1713. background: #b91c1c !important; /* darker red */
  1714. }
  1715. /* QR button – Green */
  1716. #qrBtn {
  1717. background: #16a34a !important; /* green-600 */
  1718. color: #fff !important;
  1719. border: none;
  1720. }
  1721. #qrBtn:hover {
  1722. background: #15803d !important; /* darker green */
  1723. }
  1724. </style>
  1725. <!-- </head> -->
  1726. <body>
  1727. <div class="container">
  1728. <header class="topbar" role="banner">
  1729. <div class="leftbar">
  1730. <div class="nav-left">
  1731. <!-- logo image — use same img src you already have -->
  1732. <img th:src="@{images/VPH-logo_main-EN 1.png}" alt="VPH logo" class="logo">
  1733. </div>
  1734. <!-- desktop nav (hidden on mobile) -->
  1735. <nav class="primary" aria-label="Primary navigation">
  1736. <a href="/victoriapublichall/home">Home</a>
  1737. <!-- <a href="#">About VPH</a>-->
  1738. <a href="/victoriapublichall/visit">Visit</a>
  1739. <!--<a href="#">Contact Us</a>-->
  1740. </nav>
  1741. </div>
  1742. <!-- right side: hamburger (mobile) + desktop CTA -->
  1743. <div class="rightbar">
  1744. <button id="hambtn" class="hamburger" aria-label="Open menu" aria-expanded="false" aria-controls="mobileNav">
  1745. <span class="bar bar1" aria-hidden="true"></span>
  1746. <span class="bar bar2" aria-hidden="true"></span>
  1747. <span class="bar bar3" aria-hidden="true"></span>
  1748. </button>
  1749. </div>
  1750. </header>
  1751. <!-- overlay (click to close) -->
  1752. <div id="mobileOverlay" class="mobile-overlay" aria-hidden="true"></div>
  1753. <!-- mobile nav -->
  1754. <nav id="mobileNav" class="mobile-nav" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Mobile menu">
  1755. <a href="/victoriapublichall/home">Home</a>
  1756. <a href="#">About VPH</a>
  1757. <a href="/victoriapublichall/visit">Visit</a>
  1758. <a href="#">Contact Us</a>
  1759. </nav>
  1760. <main class="page" role="main" aria-labelledby="hero-heading">
  1761. <div class="hero" aria-hidden="false">
  1762. <h1 id="hero-heading">Victoria Public Hall</h1>
  1763. <div class="lead">Experience Chennai's First Town Hall</div>
  1764. </div>
  1765. <div class="panel-wrap">
  1766. <div class="panel" role="region" aria-label="Booking panel">
  1767. <div class="people-support">
  1768. For booking more than 10 people, please click here:
  1769. <!-- <a class="link" href="/victoriapublichall/bulkbooking" onclick="storeMobileForBulk()"> </a>-->
  1770. <a class="link" href="javascript:void(0)" onclick="handleBulkBookingClick()">
  1771. Bulk Booking Request
  1772. </a>
  1773. </div>
  1774. <!-- phone/email & OTP -->
  1775. <div class="field mt-4">
  1776. <label class="muted-label">Phone / Email</label>
  1777. <div id="contactLine">
  1778. <div id="contactDisplay">
  1779. </div>
  1780. <a id="changeContact" href="/victoriapublichall/register">
  1781. Change
  1782. </a>
  1783. </div>
  1784. </div>
  1785. <div id="bookVisitSection" class="book-vist">
  1786. <!-- select date -->
  1787. <div class="field">
  1788. <label class="muted-label">Select Date</label>
  1789. <div class="date-wrap">
  1790. <input id="dateInput" class="input" type="date" aria-label="Select date">
  1791. </div>
  1792. </div>
  1793. <div class="field">
  1794. <div class="date-note">
  1795. <strong>Note:</strong> Victoria Public Hall remains closed every Tuesday for maintenance.
  1796. </div>
  1797. </div>
  1798. <div class="field">
  1799. <label class="muted-label">Identity Proof</label>
  1800. <div class="id-row">
  1801. <!-- DROPDOWN -->
  1802. <select id="idType" class="input compact">
  1803. <option value="">Select ID Proof</option>
  1804. </select>
  1805. <!-- NUMBER INPUT -->
  1806. <input type="text" id="idNumber" class="input compact" placeholder="Enter ID Number" disabled />
  1807. <small id="idError" style="color:#dc2626; display:none; font-weight:600;"></small>
  1808. </div>
  1809. </div>
  1810. <div class="field" style="margin-bottom:30px;">
  1811. <!-- SINGLE LABEL -->
  1812. <label class="muted-label-inline">Number of people</label>
  1813. <!-- DYNAMIC LIST -->
  1814. <div id="peopleTypeList" class="people-type-list"></div>
  1815. <div class="field">
  1816. <div class="people-total-row">
  1817. <span class="people-total-label">Total Amount</span>
  1818. <span id="totalAmount" class="people-total-value">₹0</span>
  1819. </div>
  1820. </div>
  1821. </div>
  1822. <hr>
  1823. <br>
  1824. <!-- Pick your time ranges -->
  1825. <!-- time-range pills -->
  1826. <div class="field border-field">
  1827. <label class="muted-label">Pick your time</label>
  1828. <div class="time-ranges" id="timeRanges" role="tablist" aria-label="Time ranges">
  1829. </div>
  1830. <div id="noAvInfoMsg" class="no-slots-message-box" style="display:none;">
  1831. </div>
  1832. <!-- <div id="noAvInfoMsg" class="no-slots-message-box" style="display:none;">
  1833. During the above time slots, the public will be permitted only within the premises of Victoria Public Hall.
  1834. Entry into the building will not be allowed.
  1835. </div>-->
  1836. <!--<div class="info-row">
  1837. <div class="preview-note-box">
  1838. <Strong>Your visit includes a audio visual experience, please select as many shows as required
  1839. to
  1840. accommodate all visitors.</Strong>
  1841. </div>
  1842. <div class="summary-inline" aria-hidden="false">
  1843. <div class="summary-item unallocated" aria-label="Guest unallocated">
  1844. <span>Guest unallocated</span>
  1845. <strong>03</strong>
  1846. </div>
  1847. <div class="summary-item allocated" aria-label="Guest allocated">
  1848. <span>Guest allocated</span>
  1849. <strong>03</strong>
  1850. </div>
  1851. </div>
  1852. </div>-->
  1853. </div>
  1854. <!-- slots grid -->
  1855. <div class="field border-field">
  1856. <div class="info-row">
  1857. <div class="preview-note-box">
  1858. <Strong>Your visit includes a audio visual experience, please select as many shows as required
  1859. to
  1860. accommodate all visitors.</Strong>
  1861. </div>
  1862. <div class="summary-inline" aria-hidden="false">
  1863. <div class="summary-item unallocated" aria-label="Guest unallocated">
  1864. <span>Guest unallocated</span>
  1865. <strong>03</strong>
  1866. </div>
  1867. <div class="summary-item allocated" aria-label="Guest allocated">
  1868. <span>Guest allocated</span>
  1869. <strong>03</strong>
  1870. </div>
  1871. </div>
  1872. </div>
  1873. <label style="margin:8px 0 6px 0" class="muted-label">Pick one or more shows for all visitors</label>
  1874. <div id="slotsGrid" class="slots-grid" aria-live="polite"></div>
  1875. </div>
  1876. <div class="submit-wrap">
  1877. <p class="submit-note">Your booking details will be sent to you via email/SMS.</p>
  1878. <button id="previewBtn" class="btn">Preview & Submit</button>
  1879. </div>
  1880. </div>
  1881. <!-- CANCEL BOOK SECTION (DISABLED BUT VISIBLE) -->
  1882. <div id="cancelBookSection" class="cancel-book">
  1883. <div class="summary-card">
  1884. <!-- TOP SUMMARY ROW -->
  1885. <div class="summary-row">
  1886. <div class="summary-left">
  1887. <div class="label">Visiting on</div>
  1888. <div class="label">Time</div>
  1889. <div class="label">Total Guest(s)</div>
  1890. </div>
  1891. <div class="summary-right">
  1892. <div id="confirmDate"></div>
  1893. <div id="confirmRange"></div>
  1894. <div id="confirmPeople"></div>
  1895. </div>
  1896. </div>
  1897. <div class="av-title">AV Experience</div>
  1898. <!-- AV LIST -->
  1899. <div class="av-list">
  1900. <div class="av-row">
  1901. <div class="av-time"></div>
  1902. <div class="av-seats"></div>
  1903. </div>
  1904. </div>
  1905. <!-- Disabled cancel button inside cancel-book -->
  1906. <div class="submit-wrap">
  1907. <button id="cancelBtn" class="btn" type="button">Cancel Booking</button>
  1908. <button id="qrBtn" class="btn" type="button">Get BookingQR</button>
  1909. </div>
  1910. </div>
  1911. <p class="submit-note">
  1912. You can book additional visits only after completing/canceling your current visit.
  1913. </p>
  1914. </div>
  1915. </div>
  1916. </div>
  1917. </main>
  1918. </div>
  1919. <!-- Booking Preview Modal (add to end of <body>) -->
  1920. <div id="vphBookingModalOverlay" class="vph-modal-overlay" aria-hidden="true">
  1921. <div id="vphBookingModal" class="vph-modal" role="dialog" aria-modal="true" aria-labelledby="vphModalTitle"
  1922. tabindex="-1">
  1923. <header class="vph-modal-header">
  1924. <h3 id="vphModalTitle">Your visit details</h3>
  1925. </header>
  1926. <section class="vph-summary-box" aria-live="polite">
  1927. <div class="vph-summary-row">
  1928. <div class="vph-summary-left">
  1929. <div class="vph-label">Visiting on</div>
  1930. <div class="vph-label">Time</div>
  1931. <div class="vph-label">ID Proof</div>
  1932. <div class="vph-label">ID Number</div>
  1933. <div class="vph-label">Total Guest(s)</div>
  1934. </div>
  1935. <div class="vph-summary-right">
  1936. <div id="vphModalDate">—</div>
  1937. <div id="vphModalRange">—</div>
  1938. <div id="vphModalIdType">—</div>
  1939. <div id="vphModalIdNumber">—</div>
  1940. <div id="vphModalPeople">—</div>
  1941. </div>
  1942. </div>
  1943. <!--class="vph-av-title"-->
  1944. <div id="vphAvTitle" class="vph-label">AV Experience</div>
  1945. <div id="vphModalSlotsList" aria-hidden="false">
  1946. <!-- populated by JS -->
  1947. </div>
  1948. </section>
  1949. <!-- Guest Breakdown -->
  1950. <section class="vph-summary-box breakdown-box">
  1951. <div class="breakdown-header">
  1952. <span>Guest Details</span>
  1953. <span>Amount</span>
  1954. </div>
  1955. <div id="previewGuestBreakdown" class="breakdown-body">
  1956. <!-- Filled by JS -->
  1957. </div>
  1958. <div class="breakdown-divider"></div>
  1959. <div class="breakdown-row total">
  1960. <span>Total Amount</span>
  1961. <span id="previewTotalAmount">₹0</span>
  1962. </div>
  1963. </section>
  1964. <footer class="vph-modal-footer">
  1965. <button id="vphModalBack" class="vph-modal-icon-btn" aria-label="Back">
  1966. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
  1967. <path d="M15 18l-6-6 6-6" stroke="#0f4b4a" stroke-width="2" stroke-linecap="round"
  1968. stroke-linejoin="round" />
  1969. </svg>
  1970. </button>
  1971. <button id="vphModalSubmit" class="vph-modal-submit">Proceed to Payment</button>
  1972. </footer>
  1973. <!-- <button id="vphModalClose" class="vph-modal-close" aria-label="Close dialog">×</button> -->
  1974. <button class="vph-modal-close preview-close" aria-label="Close dialog">×</button>
  1975. </div>
  1976. </div>
  1977. <!-- Cancel Booking – 2-step modal -->
  1978. <div id="vphModalOverlay" class="vph-modal-overlay" aria-hidden="true">
  1979. <div id="vphModal" class="vph-modal" role="dialog" aria-modal="true" aria-labelledby="vphModalTitleConfirm"
  1980. tabindex="-1">
  1981. <div class="vph-modal-body">
  1982. <!-- VIEW 1: Confirmation -->
  1983. <div id="vphViewConfirm" class="vph-view" role="document" aria-hidden="true">
  1984. <header class="vph-modal-header">
  1985. <h3 id="vphModalTitleConfirm">Are you sure you want to cancel your visit?</h3>
  1986. </header>
  1987. <footer class="vph-modal-footer">
  1988. <button id="vphConfirmBack" class="vph-modal-icon-btn" aria-label="Go back">
  1989. <!-- left arrow icon -->
  1990. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
  1991. <path d="M15 18l-6-6 6-6" stroke="#0f4b4a" stroke-width="2" stroke-linecap="round"
  1992. stroke-linejoin="round" />
  1993. </svg>
  1994. </button>
  1995. <button id="vphConfirmYes" class="vph-modal-submit primary">
  1996. Yes, Cancel booking
  1997. </button>
  1998. </footer>
  1999. </div>
  2000. <!-- VIEW 2: Result / Success -->
  2001. <div id="vphViewCancelled" class="vph-view" role="document" aria-hidden="true">
  2002. <header class="vph-modal-header">
  2003. <h3 id="vphModalTitleCancelled">Booking Cancelled!</h3>
  2004. </header>
  2005. <footer class="vph-modal-footer column">
  2006. <button id="vphGoMain" class="vph-modal-submit ghost">
  2007. Go back to main page
  2008. </button>
  2009. <button id="vphBookAnother" class="vph-modal-submit primary">
  2010. Book another visit
  2011. </button>
  2012. </footer>
  2013. </div>
  2014. </div>
  2015. <!-- Close X -->
  2016. <!-- <button id="vphModalClose" class="vph-modal-close" aria-label="Close dialog">×</button> -->
  2017. <button class="vph-modal-close cancel-close" aria-label="Close dialog">×</button>
  2018. </div>
  2019. </div>
  2020. <div id="pageLoader"
  2021. style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.8); z-index: 9999; text-align: center;">
  2022. <div style="position: relative; top: 50%; transform: translateY(-50%);">
  2023. <div class="spinner-border text-primary" style="width: 3rem; height: 3rem;"></div>
  2024. <p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p>
  2025. </div>
  2026. </div>
  2027. <script th:replace="~{fragments/user-script :: script}"></script>
  2028. <!-- <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> -->
  2029. <script type="text/javascript">
  2030. function showLoader() {
  2031. $('#pageLoader').show();
  2032. }
  2033. function hideLoader() {
  2034. $('#pageLoader').hide();
  2035. }
  2036. $('#pageLoader').hide();
  2037. //checking
  2038. /* Full corrected booking JS */
  2039. let activeRange = null;
  2040. let activeTimeId = null;
  2041. let bookingId = null;
  2042. let bookingemail = null;
  2043. let bookingmob_no = null;
  2044. let people = 0;
  2045. // const MIN_PEOPLE = 1;
  2046. // const MAX_PEOPLE = 10;
  2047. // Selected time slots map
  2048. const chosenSlots = new Map();
  2049. let slotsGrid;
  2050. let unallocEl, allocEl, dateInput;
  2051. let peopleTypes = [];
  2052. let isIdNumberValid = false;
  2053. $(document).ready(function () {
  2054. updateGuestSummary();
  2055. // DOM refs (safe)
  2056. slotsGrid = document.getElementById('slotsGrid');
  2057. // minusPeople = document.getElementById('minusPeople');
  2058. // plusPeople = document.getElementById('plusPeople');
  2059. // peopleCount = document.getElementById('peopleCount');
  2060. unallocEl = document.querySelector('.summary-item.unallocated strong');
  2061. allocEl = document.querySelector('.summary-item.allocated strong');
  2062. dateInput = document.getElementById('dateInput');
  2063. //Booking / cancel sections
  2064. const bookVisit = document.getElementById("bookVisitSection");
  2065. const cancelBook = document.getElementById("cancelBookSection");
  2066. const cancelBtn = document.getElementById("cancelBtn");
  2067. const previewBtn = document.getElementById('previewBtn');
  2068. const qrBtn = document.getElementById("qrBtn");
  2069. // Basic session handling
  2070. let hasBooking = sessionStorage.getItem("hasBooking") === "true";
  2071. let hasuser = sessionStorage.getItem("hasuser");
  2072. let bookingdetails = sessionStorage.getItem("bookingdetails");
  2073. console.log("→ hasBooking:", hasBooking);
  2074. console.log("→ hasuser:", hasuser);
  2075. console.log("→ bookingdetails:", bookingdetails);
  2076. if (hasuser === null || hasuser.trim() === "") {
  2077. window.location.href = "/victoriapublichall/register";
  2078. return;
  2079. }
  2080. const Displayname = document.getElementById("contactDisplay");
  2081. if (Displayname && hasuser) Displayname.textContent = hasuser;
  2082. let validBooking = false;
  2083. if (bookingdetails && bookingdetails.trim() !== "") {
  2084. try {
  2085. let b = JSON.parse(bookingdetails);
  2086. validBooking = b && b.id; // valid only if id exists
  2087. } catch (e) {
  2088. validBooking = false;
  2089. }
  2090. }
  2091. if (hasBooking || validBooking) {
  2092. bookVisit.classList.add("section-hidden");
  2093. cancelBook.classList.add("section-enabled");
  2094. cancelBtn.disabled = false;
  2095. if (validBooking) {
  2096. let b = JSON.parse(bookingdetails);
  2097. console.log("details>>>>>>>>>",b)
  2098. bookingId = b.id;
  2099. bookingemail = b.email;
  2100. bookingmob_no = b.mob_no;
  2101. bookingorderid=b.order_id;
  2102. console.log(bookingemail);
  2103. console.log(bookingmob_no);
  2104. console.log("bookingorderid>>>>",bookingorderid);
  2105. document.getElementById("confirmDate").textContent = formatDisplayDate(b.visitingDate);
  2106. document.getElementById("confirmRange").textContent = b.timeRange;
  2107. document.getElementById("confirmPeople").textContent =
  2108. String(b.no_of_people).padStart(2, "0");
  2109. const avList = document.querySelector(".av-list");
  2110. avList.innerHTML = "";
  2111. b.allocatedSlots.forEach(s => {
  2112. console.log(s);
  2113. avList.innerHTML += `
  2114. <div class="av-row">
  2115. <div class="av-time">${s.time} (${s.language})</div>
  2116. <div class="av-seats">${s.people} Seat(s)</div>
  2117. </div>
  2118. `;
  2119. });
  2120. }
  2121. } else {
  2122. cancelBook.classList.remove("section-enabled");
  2123. bookVisit.classList.remove("section-hidden");
  2124. cancelBtn.disabled = true;
  2125. }
  2126. if (!dateInput) return;
  2127. // flatpickr setup
  2128. const now = new Date();
  2129. const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  2130. const maxDate = new Date(today);
  2131. maxDate.setDate(maxDate.getDate() + 7);
  2132. const fp = flatpickr(dateInput, {
  2133. dateFormat: "Y-m-d",
  2134. altInput: true,
  2135. altFormat: "d-m-Y",
  2136. minDate: today,
  2137. maxDate: maxDate,
  2138. allowInput: false,
  2139. clickOpens: true,
  2140. disable: [
  2141. function (date) {
  2142. if (!(date instanceof Date)) return false;
  2143. return date.getDay() === 2;
  2144. }
  2145. ],
  2146. onReady: function (selectedDates, dateStr, instance) {
  2147. instance.altInput.setAttribute("placeholder", "dd-mm-yyyy");
  2148. },
  2149. onChange: function (selectedDates, dateStr, instance) {
  2150. if (!dateStr) instance.altInput.setAttribute("placeholder", "dd-mm-yyyy");
  2151. // When date changes we must reset state and reload ranges
  2152. activeTimeId = null;
  2153. activeRange = null;
  2154. chosenSlots.clear();
  2155. if (slotsGrid) slotsGrid.innerHTML = "";
  2156. loadTimeRanges();
  2157. }
  2158. });
  2159. fp.setDate(today);
  2160. // enable opening on click/focus
  2161. dateInput.addEventListener('focus', () => fp.open());
  2162. dateInput.addEventListener('click', () => fp.open());
  2163. loadTimeRanges();
  2164. // modal submit handler
  2165. document.getElementById("vphModalSubmit").addEventListener("click", function () {
  2166. const userInput = (document.getElementById("contactDisplay") || {}).textContent?.trim() || "";
  2167. const visitDate = (dateInput || {}).value || "";
  2168. const no_of_people = people;
  2169. const timeId = activeTimeId;
  2170. const slotDetails = [...chosenSlots].map(([time, obj]) => {
  2171. const el = Array.from(document.querySelectorAll(".slot"))
  2172. .find(s => s.dataset.time === time);
  2173. return {
  2174. slotId: Number(el?.dataset?.slotId || 0),
  2175. people: obj.seats
  2176. };
  2177. });
  2178. /*if (slotDetails.length === 0) {
  2179. Swal.fire({ icon: "warning", text: "Please select at least one show." });
  2180. return;
  2181. }
  2182. */
  2183. // Close modal before showing any Swal to avoid focus-lock issues
  2184. const overlayEl = document.getElementById('vphBookingModalOverlay');
  2185. if (overlayEl) {
  2186. overlayEl.classList.remove("show");
  2187. document.body.style.overflow = "";
  2188. // blur modal to remove active focus
  2189. const modalEl = document.getElementById('vphBookingModal');
  2190. if (modalEl) modalEl.blur();
  2191. }
  2192. $.ajax({
  2193. type: "POST",
  2194. url: "/victoriapublichall/bookingapi/saveRegistration",
  2195. data: {
  2196. userInput: userInput,
  2197. visitDate: visitDate,
  2198. no_of_people: no_of_people,
  2199. timeId: timeId,
  2200. slotDetails: JSON.stringify(slotDetails),
  2201. peopleDetails: JSON.stringify(
  2202. peopleTypes
  2203. .filter(p => p.count > 0)
  2204. .map(p => ({
  2205. id: p.id,
  2206. count: p.count
  2207. }))
  2208. ),
  2209. idType: $("#idType").val() || null,
  2210. idDetails: $("#idNumber").val() || null,
  2211. totalAmount: $("#totalAmount").text().replace("₹", ""),
  2212. source: 1
  2213. },
  2214. success: function (response) {
  2215. console.log("response", response)
  2216. // response might be string or JSON. handle both.
  2217. if (response.status === "success") {
  2218. // 1️ Save booking reference
  2219. const refId = response.refId;
  2220. const amount = $("#totalAmount").text().replace("₹", "");
  2221. sessionStorage.setItem("confirmrefid", response.refId);
  2222. showLoader();
  2223. paymentStart(refId, amount);
  2224. } else if (response.status === "error") {
  2225. hideLoader();
  2226. Swal.fire({ icon: "error", title: "Booking Failed", text: "Slots are fully booked. Please select another slot" });
  2227. } else {
  2228. hideLoader();
  2229. Swal.fire({ icon: "error", title: "Booking Failed", text: response.message || "Failed" });
  2230. }
  2231. },
  2232. error: function (xhr) {
  2233. Swal.fire({ icon: "error", title: "Booking Failed", text: xhr.responseText || "Something went wrong!" });
  2234. }
  2235. });
  2236. });
  2237. /* PEOPLE COUNT – DB DRIVEN (JDBC) */
  2238. const MAX_TOTAL_PEOPLE = 10;
  2239. /* TOTAL PEOPLE */
  2240. function getTotalPeople() {
  2241. return peopleTypes.reduce((sum, p) => sum + p.count, 0);
  2242. }
  2243. /* TOTAL AMOUNT */
  2244. function getTotalAmount() {
  2245. return peopleTypes.reduce((sum, p) => {
  2246. return sum + (p.count * p.amount);
  2247. }, 0);
  2248. }
  2249. /* UPDATE TOTAL AMOUNT UI */
  2250. function updateTotalAmount() {
  2251. const total = getTotalAmount();
  2252. const totalEl = document.getElementById("totalAmount");
  2253. if (totalEl) {
  2254. totalEl.textContent = "₹" + total;
  2255. }
  2256. }
  2257. /* RENDER PEOPLE UI */
  2258. function renderPeopleTypes() {
  2259. const container = document.getElementById("peopleTypeList");
  2260. if (!container) return;
  2261. container.innerHTML = "";
  2262. const total = getTotalPeople();
  2263. peopleTypes.forEach((item, index) => {
  2264. const row = document.createElement("div");
  2265. row.className = "people-type-row";
  2266. row.innerHTML = `
  2267. <div class="people-type-label">${item.user_type}
  2268. <span class="people-amount">(₹${item.amount})</span>
  2269. </div>
  2270. <div class="people-type-controls">
  2271. <button class="small minus-btn" data-i="${index}">−</button>
  2272. <div class="counter">${String(item.count).padStart(2, "0")}</div>
  2273. <button class="small plus-btn" data-i="${index}">+</button>
  2274. </div>
  2275. `;
  2276. container.appendChild(row);
  2277. });
  2278. // minus
  2279. container.querySelectorAll(".minus-btn").forEach(btn => {
  2280. btn.onclick = () => {
  2281. const i = btn.dataset.i;
  2282. if (peopleTypes[i].count > 0) {
  2283. peopleTypes[i].count--;
  2284. renderPeopleTypes();
  2285. syncPeopleWithBooking();
  2286. }
  2287. };
  2288. });
  2289. // plus
  2290. container.querySelectorAll(".plus-btn").forEach(btn => {
  2291. btn.onclick = () => {
  2292. if (getTotalPeople() >= MAX_TOTAL_PEOPLE) return;
  2293. const i = btn.dataset.i;
  2294. peopleTypes[i].count++;
  2295. renderPeopleTypes();
  2296. syncPeopleWithBooking();
  2297. };
  2298. });
  2299. // disable + buttons when max reached
  2300. if (total >= MAX_TOTAL_PEOPLE) {
  2301. container.querySelectorAll(".plus-btn").forEach(btn => btn.disabled = true);
  2302. }
  2303. }
  2304. function syncPeopleWithBooking() {
  2305. const total = getTotalPeople();
  2306. // single source of truth
  2307. window.people = total;
  2308. people = total;
  2309. // auto adjust slot allocations
  2310. autoAllocateExtraGuests();
  2311. deallocateExtraGuests();
  2312. // update summary counts correctly
  2313. updateGuestSummary();
  2314. updateTotalAmount();
  2315. }
  2316. /* LOAD PEOPLE TYPES FROM DB */
  2317. $.ajax({
  2318. url: "/victoriapublichall/bookingapi/getamountbyusertype",
  2319. type: "GET",
  2320. success: function (data) {
  2321. peopleTypes = data.map(row => ({
  2322. id: row.id,
  2323. user_type: row.user_type,
  2324. amount: row.amount,
  2325. count: 0,
  2326. maxCount: row.user_type &&
  2327. row.user_type.trim().toLowerCase() === 'family' ? 5 : 0
  2328. }));
  2329. renderPeopleTypes();
  2330. syncPeopleWithBooking();
  2331. },
  2332. error: function () {
  2333. Swal.fire({
  2334. icon: "error",
  2335. text: "Unable to load people categories"
  2336. });
  2337. }
  2338. });
  2339. $.ajax({
  2340. url: "/victoriapublichall/bookingapi/getidproofs",
  2341. type: "GET",
  2342. success: function (data) {
  2343. const select = document.getElementById("idType");
  2344. data.forEach(row => {
  2345. const opt = document.createElement("option");
  2346. opt.value = row.id; // store ID
  2347. opt.textContent = row.id_name; // show Aadhar Card / Passport
  2348. select.appendChild(opt);
  2349. });
  2350. },
  2351. error: function () {
  2352. Swal.fire({
  2353. icon: "error",
  2354. text: "Unable to load ID proof types"
  2355. });
  2356. }
  2357. });
  2358. /* ENABLE ID NUMBER ONLY AFTER SELECT */
  2359. const idType = document.getElementById("idType");
  2360. const idNumber = document.getElementById("idNumber");
  2361. const idError = document.getElementById("idError");
  2362. idType.addEventListener("change", function () {
  2363. const selectedText = this.options[this.selectedIndex].text.toLowerCase();
  2364. idNumber.disabled = !this.value;
  2365. idNumber.value = "";
  2366. idError.style.display = "none";
  2367. idError.textContent = "";
  2368. if (!this.value) return;
  2369. idNumber.focus();
  2370. // 🔹 AADHAR
  2371. if (selectedText.includes("aadhar")) {
  2372. idNumber.maxLength = 12;
  2373. idNumber.placeholder = "Enter 12-digit Aadhar Number";
  2374. idNumber.oninput = function () {
  2375. this.value = this.value.replace(/\D/g, "");
  2376. if (this.value.length === 12) {
  2377. idError.style.display = "none";
  2378. isIdNumberValid = true;
  2379. } else {
  2380. idError.textContent = "Aadhar number must be exactly 12 digits";
  2381. idError.style.display = "block";
  2382. isIdNumberValid = false;
  2383. }
  2384. };
  2385. }
  2386. // 🔹 PASSPORT
  2387. else if (selectedText.includes("passport")) {
  2388. idNumber.maxLength = 9;
  2389. idNumber.placeholder = "Enter Passport Number";
  2390. idNumber.oninput = function () {
  2391. this.value = this.value.replace(/[^a-zA-Z0-9]/g, "");
  2392. if (this.value.length >= 6 && this.value.length <= 9) {
  2393. idError.style.display = "none";
  2394. isIdNumberValid = true;
  2395. } else {
  2396. idError.textContent = "Passport number must be 6–9 characters";
  2397. idError.style.display = "block";
  2398. isIdNumberValid = false;
  2399. }
  2400. };
  2401. }
  2402. });
  2403. qrBtn && qrBtn.addEventListener("click", function () {
  2404. if (!bookingorderid) {
  2405. Swal.fire({
  2406. icon: "error",
  2407. text: "Order ID not found"
  2408. });
  2409. return;
  2410. }
  2411. const qrUrl =
  2412. "/victoriapublichall/bookingQRCode?orderid=" + bookingorderid;
  2413. window.open(qrUrl, "_blank");
  2414. });
  2415. });
  2416. function formatDisplayDate(dateStr) {
  2417. if (!dateStr || typeof dateStr !== "string") return "--";
  2418. // Trim & normalize
  2419. const s = dateStr.trim();
  2420. // If already in an ISO-ish form, handle both "yyyy-mm-dd" and "dd-mm-yyyy"
  2421. // Detect by first token length: if first token has length 4 -> yyyy-mm-dd
  2422. const parts = s.split("-");
  2423. let yyyy, mm, dd;
  2424. if (parts.length === 3) {
  2425. if (parts[0].length === 4) {
  2426. // format: yyyy-mm-dd
  2427. yyyy = parts[0];
  2428. mm = parts[1];
  2429. dd = parts[2];
  2430. } else {
  2431. // assume dd-mm-yyyy
  2432. dd = parts[0];
  2433. mm = parts[1];
  2434. yyyy = parts[2];
  2435. }
  2436. } else {
  2437. // fallback parse attempt — try Date constructor
  2438. const d = new Date(s);
  2439. if (isNaN(d)) return s;
  2440. return d.toLocaleDateString("en-IN", { day: "2-digit", month: "short", year: "numeric" });
  2441. }
  2442. // safe integer conversion
  2443. const y = Number(yyyy), m = Number(mm) - 1, d = Number(dd);
  2444. const dateObj = new Date(y, m, d);
  2445. if (isNaN(dateObj)) return dateStr;
  2446. // Example output: "13 Dec 2025"
  2447. return dateObj.toLocaleDateString("en-IN", {
  2448. day: "2-digit",
  2449. month: "short",
  2450. year: "numeric"
  2451. });
  2452. }
  2453. /* ---------- API functions ---------- */
  2454. function loadSlotsFromAPI() {
  2455. const dateValue = dateInput?.value;
  2456. if (!dateValue || !activeTimeId) {
  2457. // clear slotsGrid and show message to indicate no selection
  2458. if (slotsGrid) slotsGrid.innerHTML = `<div class="no-slots-message-box">Please select a time range to view AV slots.</div>`;
  2459. return;
  2460. }
  2461. $.ajax({
  2462. type: "GET",
  2463. url: "/victoriapublichall/bookingapi/getTimeSlotDetails",
  2464. data: { visitDate: dateValue, timeId: activeTimeId },
  2465. success: function (data) {
  2466. const slotList = (data || []).map(item => ({
  2467. slotId: item.slot_id,
  2468. time: item.timing,
  2469. seats: item.availCap,
  2470. language: item.language
  2471. }));
  2472. renderSlotsWithData(slotList);
  2473. },
  2474. error: function () {
  2475. console.error("Failed to load slot details");
  2476. }
  2477. });
  2478. }
  2479. function renderSlotsWithData(slotArray) {
  2480. if (!slotsGrid) return;
  2481. slotsGrid.innerHTML = "";
  2482. chosenSlots.clear();
  2483. if (!slotArray || slotArray.length === 0) {
  2484. slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry, No AV time slots available for the selected date.</div>`;
  2485. return;
  2486. }
  2487. slotArray.forEach(item => {
  2488. const time = item.time;
  2489. const seats = Number(item.seats);
  2490. const slotId = item.slotId;
  2491. const language = item.language
  2492. const slotEl = document.createElement("div");
  2493. slotEl.className = "slot";
  2494. slotEl.tabIndex = 0;
  2495. slotEl.dataset.time = time;
  2496. slotEl.dataset.seats = seats;
  2497. slotEl.dataset.originalSeats = seats;
  2498. slotEl.dataset.slotId = slotId;
  2499. slotEl.dataset.language = language;
  2500. if (seats <= 0) {
  2501. slotEl.classList.add("disabled");
  2502. slotEl.style.border = "1px solid #d9534f";
  2503. } else {
  2504. slotEl.style.border = "1px solid #28a745";
  2505. }
  2506. slotEl.innerHTML = `
  2507. <div class="left">
  2508. <div class="time">${time} <span class="slot-lang">(${language})</span></div>
  2509. <div class="meta">${seats <= 0 ? "0 Seats available" : seats + (seats === 1 ? " Seat available" : " Seats available")}</div>
  2510. </div>
  2511. <div class="choose"></div>
  2512. `;
  2513. slotEl.addEventListener("click", () => toggleSlot(slotEl));
  2514. slotsGrid.appendChild(slotEl);
  2515. });
  2516. updateGuestSummary();
  2517. }
  2518. function loadTimeRanges() {
  2519. const timeRanges = document.getElementById("timeRanges");
  2520. const previewBtn = document.getElementById("previewBtn");
  2521. if (!timeRanges) return;
  2522. // clear UI + old messages and reset state
  2523. timeRanges.innerHTML = "";
  2524. const oldMsg = document.getElementById("noSlotsMsg");
  2525. if (oldMsg) oldMsg.remove();
  2526. activeTimeId = null;
  2527. activeRange = null;
  2528. chosenSlots.clear();
  2529. if (slotsGrid) slotsGrid.innerHTML = "";
  2530. if (previewBtn) {
  2531. previewBtn.disabled = true;
  2532. previewBtn.classList.add("disabled-btn");
  2533. }
  2534. const visitDate = dateInput?.value;
  2535. if (!visitDate) {
  2536. return;
  2537. }
  2538. $.ajax({
  2539. type: "GET",
  2540. url: "/victoriapublichall/bookingapi/getTimeMaster",
  2541. data: { visitDate: visitDate,
  2542. view: "book"},
  2543. success: function (data) {
  2544. if (!data || data.length === 0) {
  2545. // No ranges at all
  2546. if (slotsGrid) {
  2547. slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry,No AV time slots available for the selected date.</div>`;
  2548. }
  2549. const msg = document.createElement("div");
  2550. msg.id = "noSlotsMsg";
  2551. msg.className = "no-slots-message";
  2552. msg.textContent = "Sorry, No time slots available for the selected date.";
  2553. timeRanges.after(msg);
  2554. return;
  2555. }
  2556. let firstActiveSet = false;
  2557. let allZero = true;
  2558. // create pills
  2559. data.forEach(item => {
  2560. if (item.availCap > 0) allZero = false;
  2561. const pill = document.createElement("div");
  2562. pill.className = "pill";
  2563. pill.setAttribute("data-range", item.timing);
  2564. pill.setAttribute("data-id", item.time_id);
  2565. pill.innerHTML = `${item.timing}<br><small>${item.availCap} Available</small>`;
  2566. if (item.availCap === 0) {
  2567. pill.classList.add("disabled-pill");
  2568. pill.style.pointerEvents = "none";
  2569. } else if (!firstActiveSet) {
  2570. pill.classList.add("active");
  2571. activeRange = item.timing;
  2572. activeTimeId = item.time_id;
  2573. firstActiveSet = true;
  2574. }
  2575. timeRanges.appendChild(pill);
  2576. });
  2577. attachPillClickHandlers();
  2578. // if any pill active, load slots for it and enable preview
  2579. if (activeTimeId) {
  2580. loadSlotsFromAPI();
  2581. onTimeSelected(activeTimeId,false);
  2582. if (previewBtn) {
  2583. previewBtn.disabled = false;
  2584. previewBtn.classList.remove("disabled-btn");
  2585. }
  2586. }
  2587. // if every pill had zero availability -> show message in slots area
  2588. if (allZero) {
  2589. if (slotsGrid) {
  2590. slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry, No AV time slots available for the selected date and time.</div>`;
  2591. }
  2592. const msg = document.createElement("div");
  2593. msg.id = "noSlotsMsg";
  2594. msg.className = "no-slots-message";
  2595. msg.textContent = "No time slots available for the selected date.";
  2596. timeRanges.after(msg);
  2597. if (previewBtn) {
  2598. previewBtn.disabled = true;
  2599. previewBtn.classList.add("disabled-btn");
  2600. }
  2601. }
  2602. },
  2603. error: function () {
  2604. console.error("Failed to load time ranges");
  2605. }
  2606. });
  2607. }
  2608. function attachPillClickHandlers() {
  2609. const timeRanges = document.getElementById("timeRanges");
  2610. if (!timeRanges) return;
  2611. // since we recreate pills every time, it's safe to add listeners directly
  2612. timeRanges.querySelectorAll('.pill').forEach(pill => {
  2613. pill.addEventListener('click', () => {
  2614. // if this pill is disabled, ignore
  2615. if (pill.classList.contains('disabled-pill')) return;
  2616. // RESET message + Swal state on EVERY time change
  2617. resetAvInfoState();
  2618. // clear active class
  2619. timeRanges.querySelectorAll('.pill').forEach(x => x.classList.remove('active'));
  2620. pill.classList.add('active');
  2621. activeRange = pill.getAttribute('data-range');
  2622. activeTimeId = pill.getAttribute('data-id');
  2623. // reset chosen slots and reload slot grid
  2624. chosenSlots.clear();
  2625. if (slotsGrid) slotsGrid.innerHTML = "";
  2626. onTimeSelected(activeTimeId,true);
  2627. });
  2628. });
  2629. }
  2630. let requiresSlot = true; // default safe value
  2631. let avInfoShownOnce = false;
  2632. let lastTimeId = null;
  2633. function onTimeSelected(timeId,fromClick = false) {
  2634. fetch(`/victoriapublichall/bookingapi/slot-rule?timeId=${timeId}`)
  2635. .then(res => {
  2636. if (!res.ok) throw new Error("Slot rule API failed");
  2637. return res.json();
  2638. })
  2639. .then(data => {
  2640. requiresSlot = Boolean(data.requiresSlot);
  2641. const message = (data.message || "").trim();
  2642. const slotsGrid = document.getElementById("slotsGrid");
  2643. const avSection = slotsGrid ? slotsGrid.closest(".field") : null;
  2644. const infoBox = document.getElementById("noAvInfoMsg");
  2645. if (!avSection || !infoBox) return;
  2646. if (requiresSlot) {
  2647. avSection.style.display = "block";
  2648. infoBox.style.display = "none";
  2649. infoBox.textContent = "";
  2650. chosenSlots.clear();
  2651. loadSlotsFromAPI();
  2652. }
  2653. else {
  2654. avSection.style.display = "none";
  2655. chosenSlots.clear();
  2656. updateGuestSummary();
  2657. // show DB-driven message
  2658. if (message) {
  2659. infoBox.textContent = message;
  2660. infoBox.style.display = "block";
  2661. // show Swal only when switching to a NEW timeId
  2662. if (fromClick) {
  2663. Swal.fire({
  2664. icon: "info",
  2665. title: "Important Information",
  2666. text: message,
  2667. confirmButtonText: "OK",
  2668. allowOutsideClick: false
  2669. });
  2670. }
  2671. } else {
  2672. infoBox.style.display = "none";
  2673. }
  2674. }
  2675. lastTimeId = timeId;
  2676. })
  2677. .catch(err => {
  2678. console.error("Slot rule error:", err);
  2679. });
  2680. }
  2681. function resetAvInfoState() {
  2682. const infoMsg = document.getElementById("noAvInfoMsg");
  2683. if (infoMsg) infoMsg.style.display = "none";
  2684. avInfoShownOnce = false;
  2685. }
  2686. function updateGuestSummary() {
  2687. let totalAllocated = 0;
  2688. chosenSlots.forEach(obj => {
  2689. totalAllocated += Number(obj?.seats || 0);
  2690. });
  2691. const allocated = Math.min(people, totalAllocated);
  2692. const unallocated = Math.max(people - allocated, 0);
  2693. if (unallocEl) unallocEl.textContent = String(unallocated).padStart(2, '0');
  2694. if (allocEl) allocEl.textContent = String(allocated).padStart(2, '0');
  2695. const unallocItem = document.querySelector('.summary-item.unallocated');
  2696. const allocItem = document.querySelector('.summary-item.allocated');
  2697. // Remove old states
  2698. unallocItem?.classList.remove('text-red', 'text-green');
  2699. allocItem?.classList.remove('text-red', 'text-green');
  2700. // Apply new colors
  2701. if (allocated === people) {
  2702. allocItem?.classList.add('text-green'); // fully allocated
  2703. } else {
  2704. unallocItem?.classList.add('text-red'); // still missing allocation
  2705. }
  2706. }
  2707. //ak minus function
  2708. function deallocateExtraGuests() {
  2709. let totalAllocated = 0;
  2710. chosenSlots.forEach(obj => {
  2711. totalAllocated += Number(obj?.seats || 0);
  2712. });
  2713. // Nothing to remove
  2714. if (totalAllocated <= people) return;
  2715. let extraToRemove = totalAllocated - people;
  2716. for (const [time, obj] of chosenSlots.entries()) {
  2717. if (extraToRemove <= 0) break;
  2718. const slotEl = [...document.querySelectorAll('#slotsGrid .slot')]
  2719. .find(s => s.dataset.time === time);
  2720. if (!slotEl) continue;
  2721. const originalSeats = Number(slotEl.dataset.originalSeats || 0);
  2722. const usedSoFar = Number(obj.seats || 0);
  2723. const removeSeats = Math.min(usedSoFar, extraToRemove);
  2724. const newUsed = usedSoFar - removeSeats;
  2725. const newRemaining = originalSeats - newUsed;
  2726. if (newUsed > 0) {
  2727. // preserve language
  2728. chosenSlots.set(time, {
  2729. seats: newUsed,
  2730. language: obj.language
  2731. });
  2732. } else {
  2733. chosenSlots.delete(time);
  2734. slotEl.classList.remove('chosen');
  2735. const choose = slotEl.querySelector('.choose');
  2736. if (choose) choose.textContent = '';
  2737. }
  2738. slotEl.dataset.seats = newRemaining;
  2739. const meta = slotEl.querySelector('.meta');
  2740. if (meta) {
  2741. meta.textContent =
  2742. `${newRemaining} Seat${newRemaining === 1 ? '' : 's'} available`;
  2743. }
  2744. slotEl.classList.remove('disabled');
  2745. extraToRemove -= removeSeats;
  2746. }
  2747. }
  2748. //plus function
  2749. function autoAllocateExtraGuests() {
  2750. let currentlyAllocated = 0;
  2751. chosenSlots.forEach(obj => {
  2752. currentlyAllocated += Number(obj?.seats || 0);
  2753. });
  2754. let remainingGuests = people - currentlyAllocated;
  2755. if (remainingGuests <= 0) return;
  2756. const chosenSlotNodes = document.querySelectorAll('#slotsGrid .slot.chosen');
  2757. chosenSlotNodes.forEach(slotEl => {
  2758. if (remainingGuests <= 0) return;
  2759. const time = slotEl.dataset.time;
  2760. const originalSeats = Number(slotEl.dataset.originalSeats || 0);
  2761. const slotObj = chosenSlots.get(time);
  2762. const usedSoFar = Number(slotObj?.seats || 0);
  2763. const remainingCap = originalSeats - usedSoFar;
  2764. if (remainingCap <= 0) return;
  2765. const extraUse = Math.min(remainingCap, remainingGuests);
  2766. const newUsed = usedSoFar + extraUse;
  2767. const newRemaining = originalSeats - newUsed;
  2768. // keep language intact
  2769. chosenSlots.set(time, {
  2770. seats: newUsed,
  2771. language: slotObj.language
  2772. });
  2773. slotEl.dataset.seats = newRemaining;
  2774. const meta = slotEl.querySelector('.meta');
  2775. if (meta) {
  2776. meta.textContent =
  2777. `${newRemaining} Seat${newRemaining === 1 ? '' : 's'} available`;
  2778. }
  2779. if (newRemaining === 0) {
  2780. slotEl.classList.add('disabled');
  2781. } else {
  2782. slotEl.classList.remove('disabled');
  2783. }
  2784. remainingGuests -= extraUse;
  2785. });
  2786. }
  2787. function toggleSlot(slotEl) {
  2788. const time = slotEl.dataset.time;
  2789. if (chosenSlots.has(time)) {
  2790. slotEl.classList.add('shake');
  2791. // remove shake after animation
  2792. setTimeout(() => {
  2793. slotEl.classList.remove('shake');
  2794. }, 350);
  2795. return;
  2796. }
  2797. const currentSeats = Number(slotEl.dataset.seats || 0);
  2798. const originalSeats = Number(slotEl.dataset.originalSeats || 0);
  2799. /* NOT ENOUGH SEATS FOR SELECTED PEOPLE → SHAKE & STOP */
  2800. if (currentSeats < people) {
  2801. slotEl.classList.add('shake');
  2802. setTimeout(() => slotEl.classList.remove('shake'), 400);
  2803. return;
  2804. }
  2805. if (currentSeats <= 0) return;
  2806. document.querySelectorAll('#slotsGrid .slot.chosen').forEach(el => {
  2807. el.classList.remove('chosen', 'disabled');
  2808. const choose = el.querySelector('.choose');
  2809. const meta = el.querySelector('.meta');
  2810. const origSeats = Number(el.dataset.originalSeats || 0);
  2811. el.dataset.seats = origSeats;
  2812. if (choose) choose.textContent = '';
  2813. if (meta) {
  2814. meta.textContent =
  2815. `${origSeats} Seat${origSeats === 1 ? '' : 's'} available`;
  2816. }
  2817. });
  2818. // Reset selection
  2819. chosenSlots.clear();
  2820. const seatsUsed = Math.min(currentSeats, people);
  2821. const remainingSeats = currentSeats - seatsUsed;
  2822. //chosenSlots.set(time, seatsUsed);
  2823. chosenSlots.set(time, {
  2824. seats: seatsUsed,
  2825. language: slotEl.dataset.language
  2826. });
  2827. slotEl.classList.add('chosen');
  2828. slotEl.dataset.seats = remainingSeats;
  2829. const choose = slotEl.querySelector('.choose');
  2830. const meta = slotEl.querySelector('.meta');
  2831. if (choose) choose.textContent = '✓';
  2832. if (meta) {
  2833. meta.textContent =
  2834. `${remainingSeats} Seat${remainingSeats === 1 ? '' : 's'} available`;
  2835. }
  2836. if (remainingSeats === 0) {
  2837. slotEl.classList.add('disabled');
  2838. }
  2839. updateGuestSummary();
  2840. }
  2841. /* Validation helpers */
  2842. function isEmail(text) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text); }
  2843. function isPhone(text) { const d = (text || '').replace(/\D/g, ''); return d.length === 10; }
  2844. function isValidContact(text) { const v = text.trim(); return isEmail(v) || isPhone(v); }
  2845. </script>
  2846. <script>
  2847. (function () {
  2848. const hambtn = document.getElementById('hambtn');
  2849. const mobileNav = document.getElementById('mobileNav');
  2850. const mobileOverlay = document.getElementById('mobileOverlay');
  2851. const desktopBook = document.getElementById('desktopBookBtn');
  2852. const mobileBook = document.getElementById('mobileBook');
  2853. if (!hambtn || !mobileNav || !mobileOverlay) {
  2854. // nothing to do if elements missing
  2855. } else {
  2856. function openMenu() {
  2857. hambtn.setAttribute('aria-expanded', 'true');
  2858. mobileNav.classList.add('open'); mobileNav.setAttribute('aria-hidden', 'false');
  2859. mobileOverlay.classList.add('show'); mobileOverlay.setAttribute('aria-hidden', 'false');
  2860. document.body.style.overflow = 'hidden';
  2861. const first = mobileNav.querySelector('a'); if (first) first.focus();
  2862. }
  2863. function closeMenu() {
  2864. hambtn.setAttribute('aria-expanded', 'false');
  2865. mobileNav.classList.remove('open'); mobileNav.setAttribute('aria-hidden', 'true');
  2866. mobileOverlay.classList.remove('show'); mobileOverlay.setAttribute('aria-hidden', 'true');
  2867. document.body.style.overflow = '';
  2868. hambtn.focus();
  2869. }
  2870. hambtn.addEventListener('click', () => {
  2871. const isOpen = hambtn.getAttribute('aria-expanded') === 'true';
  2872. if (isOpen) closeMenu(); else openMenu();
  2873. });
  2874. mobileOverlay.addEventListener('click', closeMenu);
  2875. document.addEventListener('keydown', (e) => {
  2876. if (e.key === 'Escape' && mobileNav.classList.contains('open')) closeMenu();
  2877. });
  2878. mobileNav.querySelectorAll('a').forEach(a => {
  2879. a.addEventListener('click', () => {
  2880. // close after clicking any mobile link
  2881. closeMenu();
  2882. });
  2883. });
  2884. // ensure menu closes when resizing to desktop
  2885. window.addEventListener('resize', () => {
  2886. if (window.innerWidth > 900 && mobileNav.classList.contains('open')) closeMenu();
  2887. });
  2888. }
  2889. // Optional: wire desktop Book a Visit to the same booking action as mobile
  2890. if (desktopBook && mobileBook) {
  2891. desktopBook.addEventListener('click', (e) => {
  2892. e.preventDefault();
  2893. // mimic mobile behavior for demo: show alert (replace with real action)
  2894. alert('Book a Visit clicked — open booking flow here.');
  2895. });
  2896. mobileBook.addEventListener('click', (e) => {
  2897. // mobile link closes menu via handler above and then can navigate or open booking flow
  2898. // if you want to override, uncomment below
  2899. // e.preventDefault(); alert('Mobile Book clicked (replace with action).');
  2900. });
  2901. }
  2902. })();
  2903. </script>
  2904. <!-- Booking preview Button -->
  2905. <script>
  2906. (function () {
  2907. const previewBtn = document.getElementById('previewBtn');
  2908. const overlay = document.getElementById('vphBookingModalOverlay');
  2909. const modal = document.getElementById('vphBookingModal');
  2910. const modalDate = document.getElementById('vphModalDate');
  2911. const modalRange = document.getElementById('vphModalRange');
  2912. const modalPeople = document.getElementById('vphModalPeople');
  2913. const modalSlotsList = document.getElementById('vphModalSlotsList');
  2914. const closeBtn = document.querySelector('.preview-close');
  2915. const backBtn = document.getElementById('vphModalBack');
  2916. const submitBtn = document.getElementById('vphModalSubmit');
  2917. if (!previewBtn || !overlay || !modal) {
  2918. console.warn('VPH modal: required elements not found.');
  2919. return;
  2920. }
  2921. function gatherData() {
  2922. // 1️ DATE
  2923. const dateInput = document.getElementById('dateInput');
  2924. const dateVal = dateInput?.value ? new Date(dateInput.value) : null;
  2925. const dateFormat = dateVal
  2926. ? dateVal.toLocaleDateString("en-IN", { day: '2-digit', month: 'short', year: 'numeric' })
  2927. : "";
  2928. // TIME RANGE (active pill)
  2929. const activePill = document.querySelector('.time-ranges .pill.active');
  2930. const timeRangeText = activePill
  2931. ? activePill.getAttribute("data-range")
  2932. : "";
  2933. // PEOPLE BREAKDOWN
  2934. const peopleBreakdown = peopleTypes
  2935. .filter(p => p.count > 0)
  2936. .map(p => ({
  2937. label: p.user_type,
  2938. count: p.count,
  2939. amount: p.amount,
  2940. total: p.count * p.amount
  2941. }));
  2942. // TOTAL AMOUNT
  2943. const totalAmount = peopleBreakdown.reduce(
  2944. (sum, p) => sum + p.total, 0
  2945. );
  2946. // ID PROOF
  2947. const idTypeSelect = document.getElementById("idType");
  2948. const idNumberInput = document.getElementById("idNumber");
  2949. const idProof = {
  2950. type: idTypeSelect?.value || "",
  2951. label: idTypeSelect?.selectedOptions[0]?.text || "",
  2952. number: idNumberInput?.value || ""
  2953. };
  2954. const slots = [...chosenSlots].map(([time, obj]) => ({
  2955. time,
  2956. language: obj.language,
  2957. seats: obj.seats + " Seat(s)"
  2958. }));
  2959. return { dateFormat, timeRangeText, people, slots, peopleBreakdown, totalAmount, idProof };
  2960. }
  2961. // SHOW MODAL WITH FILLED DATA
  2962. function showModal() {
  2963. const {
  2964. dateFormat,
  2965. timeRangeText,
  2966. people,
  2967. slots, peopleBreakdown,
  2968. totalAmount,
  2969. idProof
  2970. } = gatherData();
  2971. // Date validation
  2972. if (!dateFormat) {
  2973. Swal.fire({
  2974. icon: "warning",
  2975. text: "Please select a date before previewing."
  2976. });
  2977. document.getElementById('dateInput').focus();
  2978. return;
  2979. }
  2980. if (!idProof.type) {
  2981. Swal.fire({
  2982. icon: "warning",
  2983. title: "ID proof required",
  2984. text: "Please select an identity proof."
  2985. }).then(() => {
  2986. idTypeSelect?.focus();
  2987. });
  2988. return;
  2989. }
  2990. if (!idProof.number || idProof.number.trim() === "") {
  2991. Swal.fire({
  2992. icon: "warning",
  2993. title: "ID Number Required",
  2994. text: "Please enter your ID number to continue.",
  2995. confirmButtonText: "OK",
  2996. allowOutsideClick: false
  2997. }).then(() => {
  2998. idNumberInput.focus();
  2999. });
  3000. return;
  3001. }
  3002. if (!isIdNumberValid) {
  3003. Swal.fire({
  3004. icon: "error",
  3005. title: "Invalid ID Number",
  3006. text: "Please enter a valid ID number before proceeding."
  3007. });
  3008. idNumberInput.focus();
  3009. return; // BLOCK SUBMIT
  3010. }
  3011. if (!people || people <= 0) {
  3012. Swal.fire({
  3013. icon: "warning",
  3014. title: "People count required",
  3015. text: "Please select at least one visitor before proceeding."
  3016. }).then(() => {
  3017. document.querySelector(".people-type-list")?.focus();
  3018. });
  3019. return;
  3020. }
  3021. // Slot selection validation
  3022. if (requiresSlot && slots.length === 0) {
  3023. Swal.fire({
  3024. icon: "warning",
  3025. text: "Please select at least one show before previewing."
  3026. });
  3027. return;
  3028. }
  3029. modalDate.textContent = dateFormat;
  3030. modalRange.textContent = timeRangeText;
  3031. modalPeople.textContent = people;
  3032. // ADD ID PROOF HERE
  3033. const idTypeEl = document.getElementById("vphModalIdType");
  3034. const idNumberEl = document.getElementById("vphModalIdNumber");
  3035. if (idTypeEl) {
  3036. idTypeEl.textContent = idProof?.label?.trim() || "—";
  3037. }
  3038. if (idNumberEl) {
  3039. idNumberEl.textContent = idProof?.number?.trim() || "—";
  3040. }
  3041. modalSlotsList.innerHTML = "";
  3042. if (slots.length === 0) {
  3043. modalSlotsList.innerHTML = `
  3044. <div class="vph-av-row">
  3045. <div class="vph-av-time">No shows selected</div>
  3046. </div>`;
  3047. } else {
  3048. slots.forEach(s => {
  3049. console.log(s);
  3050. const row = document.createElement("div");
  3051. row.className = "vph-av-row";
  3052. row.innerHTML = `
  3053. <div class="vph-av-time">
  3054. ${s.time} <span class="slot-lang">(${s.language})</span>
  3055. </div>
  3056. <div class="vph-av-meta">${s.seats}</div>
  3057. `;
  3058. modalSlotsList.appendChild(row);
  3059. });
  3060. }
  3061. const breakdownEl = document.getElementById("previewGuestBreakdown");
  3062. const totalAmountEl = document.getElementById("previewTotalAmount");
  3063. breakdownEl.innerHTML = "";
  3064. peopleBreakdown.forEach(p => {
  3065. const row = document.createElement("div");
  3066. row.className = "breakdown-row";
  3067. row.innerHTML = `
  3068. <span class="breakdown-label">
  3069. ${p.label} × ${p.count}
  3070. </span>
  3071. <span class="breakdown-value">
  3072. ₹${p.total}
  3073. </span>
  3074. `;
  3075. breakdownEl.appendChild(row);
  3076. });
  3077. totalAmountEl.textContent = "₹" + totalAmount;
  3078. overlay.classList.add("show");
  3079. modal.focus();
  3080. document.body.style.overflow = "hidden";
  3081. }
  3082. // CLOSE MODAL
  3083. function closeModal() {
  3084. overlay.classList.remove("show");
  3085. document.body.style.overflow = "";
  3086. previewBtn.focus();
  3087. }
  3088. previewBtn.addEventListener(
  3089. "click",
  3090. function (e) {
  3091. e.preventDefault();
  3092. showModal();
  3093. },
  3094. true
  3095. );
  3096. // Close options
  3097. overlay.addEventListener("click", (e) => { if (e.target === overlay) closeModal(); });
  3098. closeBtn?.addEventListener("click", closeModal);
  3099. backBtn?.addEventListener("click", closeModal);
  3100. })();
  3101. </script>
  3102. <script>
  3103. (function () {
  3104. // Button inside the cancel-card
  3105. const cancelBtn = document.getElementById('cancelBtn');
  3106. const overlay = document.getElementById('vphModalOverlay');
  3107. const modal = document.getElementById('vphModal');
  3108. const viewConfirm = document.getElementById('vphViewConfirm');
  3109. const viewCancelled = document.getElementById('vphViewCancelled');
  3110. const confirmBack = document.getElementById('vphConfirmBack');
  3111. const confirmYes = document.getElementById('vphConfirmYes');
  3112. const goMain = document.getElementById('vphGoMain');
  3113. const bookAnother = document.getElementById('vphBookAnother');
  3114. const closeBtn = document.querySelector('.cancel-close');
  3115. if (!cancelBtn || !overlay || !modal) {
  3116. console.warn('Cancel modal: required elements not found.');
  3117. return;
  3118. }
  3119. function showView(view) {
  3120. [viewConfirm, viewCancelled].forEach(v => {
  3121. if (!v) return;
  3122. v.classList.remove('active');
  3123. v.setAttribute('aria-hidden', 'true');
  3124. });
  3125. if (view === 'confirm' && viewConfirm) {
  3126. viewConfirm.classList.add('active');
  3127. viewConfirm.setAttribute('aria-hidden', 'false');
  3128. setTimeout(() => confirmYes && confirmYes.focus(), 40);
  3129. } else if (view === 'cancelled' && viewCancelled) {
  3130. viewCancelled.classList.add('active');
  3131. viewCancelled.setAttribute('aria-hidden', 'false');
  3132. setTimeout(() => bookAnother && bookAnother.focus(), 40);
  3133. }
  3134. }
  3135. function openModal(initialView) {
  3136. overlay.classList.add('show');
  3137. overlay.setAttribute('aria-hidden', 'false');
  3138. document.body.style.overflow = 'hidden';
  3139. showView(initialView || 'confirm');
  3140. }
  3141. function closeModal() {
  3142. overlay.classList.remove('show');
  3143. overlay.setAttribute('aria-hidden', 'true');
  3144. document.body.style.overflow = '';
  3145. cancelBtn && cancelBtn.focus();
  3146. }
  3147. cancelBtn.addEventListener('click', function (e) {
  3148. e.preventDefault();
  3149. openModal('confirm');
  3150. });
  3151. closeBtn && closeBtn.addEventListener('click', closeModal);
  3152. overlay.addEventListener('click', function (e) {
  3153. if (e.target === overlay) closeModal();
  3154. });
  3155. confirmBack && confirmBack.addEventListener('click', closeModal);
  3156. confirmYes && confirmYes.addEventListener('click', function () {
  3157. if (!bookingId) {
  3158. Swal.fire({
  3159. icon: "error",
  3160. text: "Invalid booking. Try again."
  3161. });
  3162. return;
  3163. }
  3164. $.ajax({
  3165. type: "POST",
  3166. url: "/victoriapublichall/bookingapi/cancelBooking",
  3167. data: { id: bookingId },
  3168. success: function (res) {
  3169. if (res.status === "success") {
  3170. showView('cancelled');
  3171. } else {
  3172. closeCancelModal();
  3173. Swal.fire({
  3174. icon: "error",
  3175. text: res.message || "Cancellation failed"
  3176. });
  3177. }
  3178. },
  3179. error: function () {
  3180. closeCancelModal();
  3181. Swal.fire({
  3182. icon: "error",
  3183. text: "Server error. Try again later."
  3184. });
  3185. }
  3186. });
  3187. });
  3188. // success view actions
  3189. goMain && goMain.addEventListener('click', function () {
  3190. window.location.href = '/victoriapublichall/home'; // change if needed
  3191. });
  3192. bookAnother && bookAnother.addEventListener('click', function () {
  3193. let userentry = null;
  3194. if (bookingmob_no && bookingmob_no.trim() !== "") {
  3195. userentry = bookingmob_no;
  3196. }
  3197. else if (bookingemail && bookingemail.trim() !== "") {
  3198. userentry = bookingemail;
  3199. }
  3200. if (userentry) {
  3201. // user used mobile or email earlier → continue booking
  3202. sessionStorage.setItem("hasBooking", "false");
  3203. sessionStorage.setItem("hasuser", userentry);
  3204. sessionStorage.setItem("bookingdetails", ""); // very important!
  3205. window.location.href = '/victoriapublichall/registerbook';
  3206. } else {
  3207. // no mobile or email found; force fresh login
  3208. window.location.href = '/victoriapublichall/register';
  3209. }
  3210. });
  3211. document.addEventListener('keydown', function (e) {
  3212. if (e.key === 'Escape' && overlay.classList.contains('show')) {
  3213. closeModal();
  3214. }
  3215. });
  3216. document.addEventListener('focus', function (e) {
  3217. if (overlay.classList.contains('show') && !modal.contains(e.target)) {
  3218. e.stopPropagation();
  3219. modal.focus();
  3220. }
  3221. }, true);
  3222. })();
  3223. function closeCancelModal() {
  3224. const overlay = document.getElementById('vphModalOverlay');
  3225. overlay.classList.remove('show');
  3226. overlay.setAttribute('aria-hidden', 'true');
  3227. document.body.style.overflow = '';
  3228. }
  3229. </script>
  3230. <script>
  3231. const redirectPath = '/victoriapublichall/register';
  3232. // This runs when the user presses the browser's back button
  3233. window.addEventListener('popstate', function (event) {
  3234. window.location.href = redirectPath;
  3235. });
  3236. // Push a dummy state so that the popstate event triggers properly
  3237. history.pushState(null, null, location.href);
  3238. function storeMobileForBulk() {
  3239. const mobile = sessionStorage.getItem("hasuser"); // already stored
  3240. if (mobile) {
  3241. sessionStorage.setItem("bulkMobile", mobile);
  3242. }
  3243. }
  3244. </script>
  3245. <script>
  3246. sessionStorage.setItem("bulkMobile", contact);
  3247. function handleBulkBookingClick() {
  3248. // Get contact from UI / session
  3249. const contact =
  3250. $("#contactDisplay").text().trim() ||
  3251. sessionStorage.getItem("bulkMobile");
  3252. if (!contact) {
  3253. Swal.fire("Error", "Contact not found", "error");
  3254. return;
  3255. }
  3256. // Call backend to check existing booking
  3257. $.get("/victoriapublichall/bookingapi/checkBulkBooking",
  3258. { contact: contact },
  3259. function (res) {
  3260. if (res.status === "found") {
  3261. window.location.href =
  3262. "/victoriapublichall/acknowledgement?refId=" + res.refId;
  3263. } else {
  3264. sessionStorage.setItem("bulkMobile", contact);
  3265. window.location.href =
  3266. "/victoriapublichall/bulkbooking";
  3267. }
  3268. }
  3269. ).fail(function () {
  3270. Swal.fire("Error", "Unable to verify booking", "error");
  3271. });
  3272. }
  3273. </script>
  3274. </body>
  3275. </html>