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.
1276 lines
34 KiB
1276 lines
34 KiB
<!DOCTYPE html>
|
|
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
|
|
|
<head th:replace="~{fragments/user-head :: head}"></head>
|
|
<style>
|
|
:root {
|
|
--bg: #f6f3ef;
|
|
--accent: #12363b;
|
|
--dark: #2f1f1b;
|
|
--muted: #7b7773;
|
|
--panel-w: 660px;
|
|
--radius: 14px;
|
|
}
|
|
|
|
/* Reset */
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0
|
|
}
|
|
|
|
html,
|
|
body {
|
|
height: 100%
|
|
}
|
|
|
|
|
|
|
|
body {
|
|
font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
|
|
|
|
background:
|
|
linear-gradient(rgba(255, 255, 255, 0.70), rgba(255, 255, 255, 0.82)),
|
|
url('/victoriapublichall/images/vph-background_v1.jpg') top center / cover no-repeat,
|
|
url('/victoriapublichall/images/vph-background_v1.jpg') bottom center / cover no-repeat;
|
|
|
|
min-height: 100vh;
|
|
background-attachment: fixed;
|
|
/* keeps both images static */
|
|
background-repeat: no-repeat;
|
|
background-size: cover;
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
min-height: 100vh;
|
|
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
padding: 18px 24px;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Mobile view image should be FULL COVER, NO REPEAT */
|
|
@media (max-width: 520px) {
|
|
body {
|
|
background:
|
|
linear-gradient(rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0.90)),
|
|
url('/victoriapublichall/images/vph-background_v1.jpg');
|
|
|
|
background-size: 100em;
|
|
/* full cover */
|
|
background-repeat: no-repeat;
|
|
background-position: center center;
|
|
background-attachment: scroll;
|
|
/* prevents weird zooming on mobile */
|
|
|
|
/* remove second image on mobile */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* container */
|
|
.container {
|
|
width: 100%;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/******************************
|
|
NAVBAR (desktop + mobile)
|
|
******************************/
|
|
.topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 10px 0;
|
|
position: relative;
|
|
z-index: 1300;
|
|
}
|
|
|
|
.leftbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
/* small logo + layout helpers */
|
|
.nav-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.logo {
|
|
width: 72px;
|
|
height: 72px;
|
|
object-fit: contain;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* default desktop nav */
|
|
nav.primary {
|
|
display: flex;
|
|
gap: 18px;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Desktop Navigation Links (Figma Accurate) */
|
|
nav.primary a {
|
|
font-family: "Playfair Display", serif !important;
|
|
font-weight: 400 !important;
|
|
/* Regular */
|
|
font-style: normal !important;
|
|
font-size: 15px !important;
|
|
/* Figma size */
|
|
line-height: 100% !important;
|
|
letter-spacing: 0 !important;
|
|
|
|
color: #3C1E12;
|
|
/* matches Book a Visit color */
|
|
text-decoration: none;
|
|
|
|
padding: 6px 8px;
|
|
/* keep your layout spacing */
|
|
border-radius: 6px;
|
|
|
|
transition: background 0.2s ease;
|
|
}
|
|
|
|
/* Hover state (optional subtle effect like Figma preview) */
|
|
nav.primary a:hover {
|
|
background: rgba(0, 0, 0, 0.06);
|
|
color: #3C1E12;
|
|
/* no change in color */
|
|
}
|
|
|
|
/* Mobile menu links */
|
|
.mobile-nav a {
|
|
font-family: "Playfair Display", serif !important;
|
|
font-weight: 400 !important;
|
|
font-size: 15px !important;
|
|
letter-spacing: 0 !important;
|
|
color: #3C1E12;
|
|
}
|
|
|
|
.rightbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* desktop CTA (Book a Visit) */
|
|
|
|
/* Desktop CTA (Book a Visit) — Updated with Figma specs */
|
|
|
|
/* Desktop CTA (Book a Visit) — With Figma Border */
|
|
#desktopBookBtn {
|
|
width: 174px;
|
|
/* Figma button width */
|
|
height: 57px;
|
|
/* Figma button height */
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
background: transparent;
|
|
border-radius: 8px;
|
|
/* Figma corner radius */
|
|
|
|
/* Border from Figma (thin outline) */
|
|
border: 1px solid rgba(60, 30, 18, 0.40);
|
|
/* darker visible border */
|
|
|
|
cursor: pointer;
|
|
|
|
/* Figma Typography */
|
|
font-family: "Playfair Display", serif !important;
|
|
font-weight: 700 !important;
|
|
/* Bold */
|
|
font-style: italic !important;
|
|
font-size: 24px !important;
|
|
line-height: 100% !important;
|
|
|
|
/* Letter spacing -6% = -0.06em */
|
|
letter-spacing: -0.06em !important;
|
|
|
|
color: #3C1E12 !important;
|
|
}
|
|
|
|
/* Hover effect (Figma-like subtle shadow) */
|
|
#desktopBookBtn:hover {
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.10);
|
|
}
|
|
|
|
|
|
/* Keep existing responsive behavior */
|
|
@media (max-width: 900px) {
|
|
nav.primary {
|
|
display: none !important;
|
|
}
|
|
|
|
#desktopBookBtn {
|
|
display: none !important;
|
|
}
|
|
|
|
.hamburger {
|
|
display: flex !important;
|
|
}
|
|
|
|
/* Mobile logo size */
|
|
.logo {
|
|
width: 44px;
|
|
height: 44px;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* hamburger (hidden on desktop) */
|
|
.hamburger {
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
}
|
|
|
|
.hamburger .bar {
|
|
display: block;
|
|
width: 18px;
|
|
height: 2px;
|
|
background: var(--dark);
|
|
margin: 3px 0;
|
|
transition: transform .2s, opacity .2s;
|
|
}
|
|
|
|
/* mobile floating menu + overlay (initial hidden) */
|
|
.mobile-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.22);
|
|
z-index: 1350;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity .22s ease;
|
|
}
|
|
|
|
.mobile-overlay.show {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.mobile-nav {
|
|
position: fixed;
|
|
left: 12px;
|
|
right: 12px;
|
|
top: 68px;
|
|
max-width: 420px;
|
|
margin: 0 auto;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 14px;
|
|
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.16);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
z-index: 1400;
|
|
transform: translateY(-8px) scale(.995);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity .22s ease, transform .22s ease;
|
|
}
|
|
|
|
.mobile-nav.open {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.mobile-nav a {
|
|
display: block;
|
|
padding: 10px 12px;
|
|
color: var(--dark);
|
|
text-decoration: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.mobile-nav a+a {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.mobile-nav .book-cta {
|
|
background: #f7f9f9;
|
|
text-align: center;
|
|
padding: 12px;
|
|
color: var(--accent);
|
|
font-weight: 700;
|
|
}
|
|
|
|
|
|
|
|
/* small screens tweak */
|
|
@media (max-width:520px) {
|
|
.logo {
|
|
width: 61px !important;
|
|
/* Figma width */
|
|
height: 61px !important;
|
|
/* Figma height */
|
|
object-fit: contain;
|
|
}
|
|
|
|
.mobile-nav {
|
|
left: 12px;
|
|
right: 12px;
|
|
top: 68px;
|
|
max-width: 420px;
|
|
margin: 0 auto;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
padding: 14px;
|
|
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.16);
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
z-index: 1400;
|
|
transform: translateY(-8px) scale(.995);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity .22s ease, transform .22s ease;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/******************************
|
|
PAGE / HERO / PANEL
|
|
******************************/
|
|
.page {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
|
|
|
|
.panel-wrap {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin-top: 18px;
|
|
}
|
|
|
|
.hero {
|
|
width: 100%;
|
|
max-width: 920px;
|
|
text-align: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
/* Desktop style */
|
|
.page h1 {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
font-size: 48px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
|
|
width: 100%;
|
|
max-width: 444px;
|
|
/* Figma width */
|
|
margin: 0 auto 10px;
|
|
/* Center + bottom spacing */
|
|
color: #3B1E14 !important;
|
|
}
|
|
|
|
/* Mobile view */
|
|
@media (max-width:520px) {
|
|
.page h1 {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
|
|
font-size: 32px !important;
|
|
/* Figma size */
|
|
line-height: 100% !important;
|
|
/* Figma line height */
|
|
letter-spacing: 0;
|
|
|
|
width: 296px;
|
|
/* Figma width */
|
|
max-width: 90%;
|
|
/* Responsive safety */
|
|
margin: 0 auto;
|
|
/* Center horizontally */
|
|
|
|
text-align: center;
|
|
/* Figma alignment */
|
|
color: #3B1E14 !important;
|
|
/* Requested color */
|
|
}
|
|
}
|
|
|
|
|
|
/* Desktop version */
|
|
.hero .lead {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 400;
|
|
font-style: normal;
|
|
font-size: 20px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
|
|
width: 100%;
|
|
max-width: 417px;
|
|
/* From Figma */
|
|
margin: 0 auto 20px;
|
|
/* Center + spacing */
|
|
color: #000000;
|
|
/* BLACK COLOR as requested */
|
|
}
|
|
|
|
/* Mobile version */
|
|
@media (max-width:520px) {
|
|
.hero .lead {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 400;
|
|
font-style: normal;
|
|
|
|
font-size: 14px !important;
|
|
/* Figma size */
|
|
line-height: 100% !important;
|
|
/* Figma line height */
|
|
letter-spacing: 0;
|
|
|
|
width: 275px;
|
|
/* Figma width */
|
|
max-width: 90%;
|
|
/* keep responsive */
|
|
margin: 0 auto 20px;
|
|
/* center align */
|
|
|
|
text-align: center;
|
|
/* Figma alignment */
|
|
color: #000000 !important;
|
|
/* Text color */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Desktop version */
|
|
.panel h3 {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
/* bold */
|
|
font-style: normal;
|
|
font-size: 20px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
color: #000000;
|
|
/* BLACK TEXT */
|
|
|
|
width: 100%;
|
|
max-width: 336px;
|
|
/* Figma width */
|
|
margin: 0 auto 14px;
|
|
/* center + bottom spacing */
|
|
}
|
|
|
|
/* Mobile version */
|
|
@media (max-width:520px) {
|
|
.panel h3 {
|
|
font-size: 24px;
|
|
/* slightly smaller */
|
|
max-width: 120%;
|
|
line-height: 140%;
|
|
}
|
|
}
|
|
|
|
|
|
/* Desktop version */
|
|
.muted-label {
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
font-size: 16px;
|
|
line-height: 100%;
|
|
letter-spacing: 0;
|
|
color: #3B1E14;
|
|
|
|
width: 100%;
|
|
max-width: 100%;
|
|
margin-bottom: 8px;
|
|
|
|
text-align: left;
|
|
display: block;
|
|
|
|
margin-left: 40px !important;
|
|
/* ⬅ FIXES CENTER ISSUE */
|
|
}
|
|
|
|
/* Mobile version */
|
|
@media (max-width:520px) {
|
|
.muted-label {
|
|
font-size: 16px;
|
|
max-width: 1400%;
|
|
line-height: 140%;
|
|
text-align: left;
|
|
margin-left: 0 !important;
|
|
/* keep left */
|
|
}
|
|
}
|
|
|
|
|
|
/* Desktop version */
|
|
.input {
|
|
width: 100%;
|
|
max-width: 536px;
|
|
/* Figma width */
|
|
padding: 16px 18px;
|
|
/* creates approx 65px height */
|
|
border-radius: 14px;
|
|
/* Figma radius */
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
background: #ffffff;
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 16px;
|
|
outline: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* Focus glow */
|
|
.input:focus {
|
|
box-shadow: 0 8px 26px rgba(20, 30, 30, 0.04);
|
|
border-color: rgba(0, 0, 0, 0.14);
|
|
}
|
|
|
|
/* Mobile version */
|
|
@media (max-width:520px) {
|
|
.input {
|
|
max-width: 140%;
|
|
line-height: 140%;
|
|
padding: 16px 18px;
|
|
/* better fit on mobile */
|
|
border-radius: 12px;
|
|
}
|
|
}
|
|
|
|
|
|
/* Desktop version */
|
|
.btn {
|
|
width: 100%;
|
|
max-width: 536px;
|
|
/* Figma width */
|
|
padding: 18px 20px;
|
|
/* visual height approx 66px */
|
|
border-radius: 11px;
|
|
/* Figma radius */
|
|
|
|
background: var(--accent);
|
|
/* your theme greenish color */
|
|
color: #ffffff;
|
|
font-family: "Inter", sans-serif;
|
|
font-weight: 700;
|
|
font-size: 16px;
|
|
|
|
border: none;
|
|
cursor: pointer;
|
|
box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12);
|
|
|
|
transition: none !important;
|
|
/* Animate: Instant */
|
|
}
|
|
|
|
/* Disabled version */
|
|
.btn[disabled] {
|
|
opacity: .6;
|
|
cursor: not-allowed;
|
|
box-shadow: none;
|
|
}
|
|
|
|
/* Mobile version */
|
|
@media (max-width:520px) {
|
|
.btn {
|
|
max-width: 140%;
|
|
line-height: 140%;
|
|
padding: 16px 18px;
|
|
/* smaller height on mobile */
|
|
border-radius: 12px;
|
|
font-size: 18px;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.links-row {
|
|
width: 100%;
|
|
max-width: 536px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
/* pushes them to opposite sides */
|
|
align-items: center;
|
|
margin: 8px auto 0;
|
|
/* center align inside panel */
|
|
padding: 0 4px;
|
|
/* small spacing for balance */
|
|
}
|
|
|
|
|
|
/* Change phone/email */
|
|
.changeLinkrow a {
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
line-height: 100%;
|
|
color: #0478D0;
|
|
text-decoration: underline;
|
|
text-decoration-thickness: 1px;
|
|
text-underline-offset: 2px;
|
|
cursor: pointer;
|
|
transition: none !important;
|
|
}
|
|
|
|
.changeLinkrow a.disabled {
|
|
color: rgb(0, 128, 192) !important;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Resend OTP */
|
|
.resendLinkrow {
|
|
text-align: right;
|
|
}
|
|
|
|
.resendLinkrow a {
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
line-height: 100%;
|
|
color: #0478D0;
|
|
text-decoration: underline;
|
|
text-decoration-thickness: 1px;
|
|
text-underline-offset: 2px;
|
|
cursor: pointer;
|
|
transition: none !important;
|
|
}
|
|
|
|
.resendLinkrow a.disabled {
|
|
color: #0478D0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.otp-input {
|
|
width: 536px;
|
|
/* height: 65px; */
|
|
max-width: 100%;
|
|
/* keeps it responsive on mobile */
|
|
|
|
padding: 16px 18px;
|
|
border-radius: 14px;
|
|
/* Figma: Radius 14px */
|
|
|
|
background: #ffffff;
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
|
|
font-family: "Inter", sans-serif;
|
|
font-size: 20px;
|
|
/* OTP input usually bigger for readability */
|
|
font-weight: 400;
|
|
line-height: 100%;
|
|
|
|
outline: none;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* Focus effect (same style as your other inputs) */
|
|
.otp-input:focus {
|
|
border-color: rgba(0, 0, 0, 0.12);
|
|
box-shadow: 0 8px 26px rgba(20, 30, 30, 0.04);
|
|
}
|
|
|
|
/* Mobile View – make it fit screen */
|
|
@media (max-width: 520px) {
|
|
.otp-input {
|
|
max-width: 140%;
|
|
line-height: 140%;
|
|
padding: 16px 18px;
|
|
width: 100%;
|
|
height: 55px;
|
|
font-size: 18px;
|
|
border-radius: 12px;
|
|
}
|
|
}
|
|
|
|
.panel {
|
|
width: 100%;
|
|
max-width: var(--panel-w);
|
|
|
|
/* GLASS EFFECT PANEL */
|
|
background: rgba(255, 255, 255, 0);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
padding: 26px;
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<!-- ============================
|
|
NAVBAR: REPLACE/EDIT HERE
|
|
(Brand + desktop nav + CTA; mobile hamburger + menu below)
|
|
============================ -->
|
|
<!-- NAVBAR (replace your existing header / mobile nav markup with this) -->
|
|
<header class="topbar" role="banner">
|
|
<div class="leftbar">
|
|
<div class="nav-left">
|
|
<!-- logo image — use same img src you already have -->
|
|
<img th:src="@{images/VPH-logo_main-EN 1.png}" alt="VPH logo" class="logo">
|
|
</div>
|
|
|
|
<!-- desktop nav (hidden on mobile) -->
|
|
<nav class="primary" aria-label="Primary navigation">
|
|
<a href="/victoriapublichall/home">Home</a>
|
|
<a href="#">About VPH</a>
|
|
<a href="/victoriapublichall/visit">Visit</a>
|
|
<a href="#">Contact Us</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- right side: hamburger (mobile) + desktop CTA -->
|
|
<div class="rightbar">
|
|
<button id="hambtn" class="hamburger" aria-label="Open menu" aria-expanded="false" aria-controls="mobileNav">
|
|
<span class="bar bar1" aria-hidden="true"></span>
|
|
<span class="bar bar2" aria-hidden="true"></span>
|
|
<span class="bar bar3" aria-hidden="true"></span>
|
|
</button>
|
|
|
|
|
|
</div>
|
|
</header>
|
|
|
|
<!-- overlay (click to close) -->
|
|
<div id="mobileOverlay" class="mobile-overlay" aria-hidden="true"></div>
|
|
|
|
<!-- mobile nav -->
|
|
<nav id="mobileNav" class="mobile-nav" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Mobile menu">
|
|
<a href="/victoriapublichall/home">Home</a>
|
|
<a href="#">About VPH</a>
|
|
<a href="/victoriapublichall/visit">Visit</a>
|
|
<a href="#">Contact Us</a>
|
|
|
|
</nav>
|
|
|
|
|
|
<!-- ============================
|
|
PAGE CONTENT (HERO + OTP PANEL)
|
|
============================ -->
|
|
<main class="page" role="main" aria-labelledby="hero-heading">
|
|
<div class="hero">
|
|
<h1 id="hero-heading">Victoria Public Hall</h1>
|
|
<div class="lead">Experience Chennai's First Town Hall</div>
|
|
|
|
<div class="panel-wrap">
|
|
<div class="panel" role="region" aria-label="OTP booking panel">
|
|
<h3>Book your visit / Manage Bookings</h3>
|
|
<br>
|
|
|
|
<span class="muted-label" for="contactInput">Enter phone number or email to proceed</span>
|
|
|
|
<!-- initial input -->
|
|
<div id="input-block">
|
|
<input id="contactInput" class="input" type="text" inputmode="text" placeholder="Phone | Email"
|
|
autocomplete="off" aria-label="Phone or Email">
|
|
<div style="height:12px"></div>
|
|
<button id="sendBtn" class="btn" type="button">Send OTP</button>
|
|
</div>
|
|
|
|
<!-- after-send block -->
|
|
<div id="sent-block" style="display:none">
|
|
<input id="contactInputDisabled" class="input" type="text" disabled>
|
|
<div style="height:8px"></div>
|
|
<button id="sendBtnDisabled" class="btn" disabled>Send OTP</button>
|
|
|
|
<div class="links-row" style="margin-top:8px">
|
|
<div class="changeLinkrow">
|
|
<a href="#" id="changeLink">Change phone/email</a>
|
|
</div>
|
|
<div class="resendLinkrow">
|
|
<a href="#" id="resendLink" class="disabled">Resend OTP</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="otp-area" id="otpArea" aria-hidden="true">
|
|
<span class="muted-label" for="otpInput" style="margin-top:12px">Enter OTP</span>
|
|
<input id="otpInput" class="otp-input" type="text" maxlength="5" placeholder="Enter OTP"
|
|
aria-label="OTP">
|
|
<div style="height:12px"></div>
|
|
<button id="submitOtpBtn" class="btn" type="button">Submit</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<div id="pageLoader"
|
|
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;">
|
|
<div style="position: relative; top: 50%; transform: translateY(-50%);">
|
|
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;"></div>
|
|
<p style="margin-top: 1rem; font-size: 1.2rem; color: #007bff;">Please wait...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script th:replace="~{fragments/user-script :: script}"></script>
|
|
|
|
|
|
<script>
|
|
|
|
$(document).ready(function () {
|
|
$('#otpInput').on('input', function () {
|
|
// Allow only digits
|
|
this.value = this.value.replace(/\D/g, '');
|
|
|
|
// Max 10 digits
|
|
if (this.value.length > 5) {
|
|
this.value = this.value.slice(0, 5);
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
/* ========================
|
|
MOBILE NAV (improved)
|
|
======================== */
|
|
(function () {
|
|
const hambtn = document.getElementById('hambtn');
|
|
const mobileNav = document.getElementById('mobileNav');
|
|
const mobileOverlay = document.getElementById('mobileOverlay');
|
|
const desktopBook = document.getElementById('desktopBookBtn');
|
|
const mobileBook = document.getElementById('mobileBook');
|
|
|
|
if (!hambtn || !mobileNav || !mobileOverlay) {
|
|
// nothing to do if elements missing
|
|
} else {
|
|
function openMenu() {
|
|
hambtn.setAttribute('aria-expanded', 'true');
|
|
mobileNav.classList.add('open'); mobileNav.setAttribute('aria-hidden', 'false');
|
|
mobileOverlay.classList.add('show'); mobileOverlay.setAttribute('aria-hidden', 'false');
|
|
document.body.style.overflow = 'hidden';
|
|
const first = mobileNav.querySelector('a'); if (first) first.focus();
|
|
}
|
|
function closeMenu() {
|
|
hambtn.setAttribute('aria-expanded', 'false');
|
|
mobileNav.classList.remove('open'); mobileNav.setAttribute('aria-hidden', 'true');
|
|
mobileOverlay.classList.remove('show'); mobileOverlay.setAttribute('aria-hidden', 'true');
|
|
document.body.style.overflow = '';
|
|
hambtn.focus();
|
|
}
|
|
|
|
hambtn.addEventListener('click', () => {
|
|
const isOpen = hambtn.getAttribute('aria-expanded') === 'true';
|
|
if (isOpen) closeMenu(); else openMenu();
|
|
});
|
|
|
|
mobileOverlay.addEventListener('click', closeMenu);
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && mobileNav.classList.contains('open')) closeMenu();
|
|
});
|
|
|
|
mobileNav.querySelectorAll('a').forEach(a => {
|
|
a.addEventListener('click', () => {
|
|
// close after clicking any mobile link
|
|
closeMenu();
|
|
});
|
|
});
|
|
|
|
// ensure menu closes when resizing to desktop
|
|
window.addEventListener('resize', () => {
|
|
if (window.innerWidth > 900 && mobileNav.classList.contains('open')) closeMenu();
|
|
});
|
|
}
|
|
|
|
// Optional: wire desktop Book a Visit to the same booking action as mobile
|
|
if (desktopBook && mobileBook) {
|
|
desktopBook.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
// mimic mobile behavior for demo: show alert (replace with real action)
|
|
alert('Book a Visit clicked — open booking flow here.');
|
|
});
|
|
mobileBook.addEventListener('click', (e) => {
|
|
// mobile link closes menu via handler above and then can navigate or open booking flow
|
|
// if you want to override, uncomment below
|
|
// e.preventDefault(); alert('Mobile Book clicked (replace with action).');
|
|
});
|
|
}
|
|
})();
|
|
|
|
|
|
/* ========================
|
|
OTP PANEL (demo logic) - start timer only on explicit Resend click
|
|
======================== */
|
|
(function () {
|
|
const contactInput = document.getElementById('contactInput');
|
|
const sendBtn = document.getElementById('sendBtn');
|
|
const sentBlock = document.getElementById('sent-block');
|
|
const inputBlock = document.getElementById('input-block');
|
|
const contactInputDisabled = document.getElementById('contactInputDisabled');
|
|
const sendBtnDisabled = document.getElementById('sendBtnDisabled');
|
|
const changeLink = document.getElementById('changeLink');
|
|
const resendLink = document.getElementById('resendLink');
|
|
const otpArea = document.getElementById('otpArea');
|
|
const otpInput = document.getElementById('otpInput');
|
|
const submitOtpBtn = document.getElementById('submitOtpBtn');
|
|
|
|
let resendTimer = null;
|
|
let secondsLeft = 0;
|
|
const RESEND_DELAY = 30; // seconds
|
|
|
|
function isEmail(text) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text); }
|
|
function isPhone(text) {
|
|
const digits = text.replace(/\D/g, '');
|
|
return digits.length === 10; // EXACT 10 digits only
|
|
}
|
|
function isValidContact(text) { if (!text) return false; const trimmed = text.trim(); return isEmail(trimmed) || isPhone(trimmed); }
|
|
|
|
function setResendDisabled(state) {
|
|
if (!resendLink) return;
|
|
if (state) {
|
|
resendLink.classList.add('disabled');
|
|
resendLink.setAttribute('aria-disabled', 'true');
|
|
resendLink.style.pointerEvents = 'none';
|
|
} else {
|
|
resendLink.classList.remove('disabled');
|
|
resendLink.removeAttribute('aria-disabled');
|
|
resendLink.style.pointerEvents = '';
|
|
resendLink.textContent = 'Resend OTP';
|
|
}
|
|
}
|
|
|
|
function startResendCountdown(sec = RESEND_DELAY) {
|
|
if (!resendLink) return;
|
|
if (resendTimer) { clearInterval(resendTimer); resendTimer = null; }
|
|
secondsLeft = sec;
|
|
// lock the resend link and show countdown
|
|
setResendDisabled(true);
|
|
resendLink.textContent = `Resend OTP (${secondsLeft}s)`;
|
|
resendTimer = setInterval(() => {
|
|
secondsLeft--;
|
|
if (secondsLeft <= 0) {
|
|
clearInterval(resendTimer);
|
|
resendTimer = null;
|
|
setResendDisabled(false);
|
|
} else {
|
|
resendLink.textContent = `Resend OTP (${secondsLeft}s)`;
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function demoSendOtp(contact) {
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: '/victoriapublichall/otpapi/send-otp',
|
|
data: { 'userInput': contact },
|
|
success: function (response) {
|
|
if (response.status === "success") {
|
|
|
|
if (otpArea) { otpArea.style.display = 'block'; otpArea.setAttribute('aria-hidden', 'false'); }
|
|
if (otpInput) { otpInput.value = ''; setTimeout(() => otpInput.focus(), 120); } startResendCountdown();
|
|
} else if (response === "error") {
|
|
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Error',
|
|
text: 'Invalid Mobile Number or Email ID.',
|
|
});
|
|
return;
|
|
}
|
|
else {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Error',
|
|
text: 'Error in OTP',
|
|
});
|
|
return;
|
|
}
|
|
},
|
|
error: function () {
|
|
alert("Error While Sending OTP.");
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
function showSentState(contact) {
|
|
inputBlock.style.display = 'none'; inputBlock.setAttribute('aria-hidden', 'true');
|
|
sentBlock.style.display = 'block'; sentBlock.setAttribute('aria-hidden', 'false');
|
|
if (contactInputDisabled) contactInputDisabled.value = contact;
|
|
if (sendBtnDisabled) sendBtnDisabled.disabled = true;
|
|
// ensure resend link is enabled for the user to click (timer will only start on explicit click)
|
|
setResendDisabled(false);
|
|
}
|
|
|
|
// Send OTP (initial send)
|
|
if (sendBtn) {
|
|
sendBtn.addEventListener('click', () => {
|
|
const raw = (contactInput && contactInput.value) ? contactInput.value.trim() : '';
|
|
// If only digits -> treat as phone
|
|
if (/^\d+$/.test(raw) && raw.length !== 10) {
|
|
// alert('Mobile number must be exactly 10 digits.');
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Validation Error',
|
|
text: 'Mobile number must be exactly 10 digits.',
|
|
});
|
|
contactInput.focus();
|
|
return;
|
|
}
|
|
if (!isValidContact(raw)) {
|
|
// alert('Enter a valid 10-digit phone number or email.');
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Validation Error',
|
|
text: 'Enter a valid 10-digit phone number or email.',
|
|
});
|
|
contactInput.focus();
|
|
return;
|
|
}
|
|
|
|
showSentState(raw);
|
|
// demo send OTP - do NOT start countdown here per requirement
|
|
demoSendOtp(raw);
|
|
document.getElementById('otpArea').scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
});
|
|
}
|
|
|
|
// Resend OTP - only starts countdown when user explicitly clicks Resend
|
|
if (resendLink) {
|
|
resendLink.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if (resendLink.classList.contains('disabled')) return; // safety
|
|
const to = (contactInputDisabled && contactInputDisabled.value) ? contactInputDisabled.value : (contactInput && contactInput.value ? contactInput.value : '');
|
|
if (!to) return;
|
|
if (/^\d+$/.test(to) && to.length !== 10) {
|
|
// alert('Mobile number must be exactly 10 digits.');
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Validation Error',
|
|
text: 'Mobile number must be exactly 10 digits.',
|
|
});
|
|
|
|
return;
|
|
}
|
|
if (!isValidContact(to)) {
|
|
// alert('Enter a valid 10-digit phone number or email.');
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Validation Error',
|
|
text: 'Enter a valid 10-digit phone number or email.',
|
|
});
|
|
|
|
return;
|
|
}
|
|
// send new OTP and start the countdown now
|
|
demoSendOtp(to);
|
|
startResendCountdown();
|
|
});
|
|
}
|
|
|
|
// Change link: go back to input state and reset
|
|
if (changeLink) {
|
|
changeLink.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if (resendTimer) { clearInterval(resendTimer); resendTimer = null; }
|
|
setResendDisabled(false);
|
|
sentBlock.style.display = 'none'; sentBlock.setAttribute('aria-hidden', 'true');
|
|
inputBlock.style.display = 'block'; inputBlock.setAttribute('aria-hidden', 'false');
|
|
if (contactInput) contactInput.focus();
|
|
if (otpArea) { otpArea.style.display = 'none'; otpArea.setAttribute('aria-hidden', 'true'); }
|
|
if (otpInput) otpInput.value = '';
|
|
if (contactInput) contactInput.value = '';
|
|
|
|
});
|
|
}
|
|
|
|
// Submit OTP
|
|
if (submitOtpBtn) {
|
|
submitOtpBtn.addEventListener('click', () => {
|
|
const otp = otpInput ? otpInput.value.trim() : '';
|
|
const userInput = (contactInputDisabled && contactInputDisabled.value) ? contactInputDisabled.value : (contactInput && contactInput.value ? contactInput.value : '');
|
|
if (!otp) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Missing OTP',
|
|
text: 'Please Enter OTP',
|
|
});
|
|
otpInput.focus(); return;
|
|
}
|
|
|
|
if (!userInput) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Missing Phone Number Or Email',
|
|
text: 'Please Enter Phone Number Or Email',
|
|
});
|
|
otpInput.focus(); return;
|
|
}
|
|
|
|
/* if(otp === currentOtp){
|
|
// demo success behavior: you can replace this with a redirect or server call
|
|
setTimeout(()=> {
|
|
alert('OTP verified (demo). You can now proceed to bookings.');
|
|
// optional: automatically switch back to input or navigate
|
|
// here we simulate closing the OTP flow (reset)
|
|
changeLink.click();
|
|
}, 700);
|
|
} else {
|
|
alert('Invalid OTP. Please try again.');
|
|
otpInput.focus();
|
|
} */
|
|
|
|
|
|
$.ajax({
|
|
type: "GET",
|
|
url: '/victoriapublichall/otpapi/verifyOTP',
|
|
data: { 'otp': otp, 'userInput': userInput },
|
|
success: function (response) {
|
|
|
|
console.log("API Response:", response);
|
|
|
|
// Success case
|
|
if (response.verifiedotp === "success") {
|
|
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: response.message,
|
|
//text: 'Click OK to continue',
|
|
showConfirmButton: false,
|
|
timer: 2000,
|
|
allowOutsideClick: false,
|
|
allowEscapeKey: false
|
|
|
|
}).then(() => {
|
|
|
|
console.log("Booking Details: ", response.regDetails);
|
|
|
|
let hasBooking = false;
|
|
let hasuser = response.userInput;
|
|
|
|
// regDetails is an array → check if contains items
|
|
if (response.regDetails && typeof response.regDetails === "object" && !Array.isArray(response.regDetails)) {
|
|
hasBooking = true;
|
|
console.log("step1");
|
|
}
|
|
|
|
// regDetails is a string (No booking found) → treat as NO booking
|
|
else if (typeof response.regDetails === "string") {
|
|
hasBooking = false;
|
|
console.log("step2");
|
|
}
|
|
|
|
sessionStorage.setItem("hasBooking", hasBooking);
|
|
sessionStorage.setItem("hasuser", hasuser);
|
|
sessionStorage.setItem("bookingdetails", JSON.stringify(response.regDetails));
|
|
//sessionStorage.setItem("bookingdetails", response.regDetails);
|
|
|
|
// Redirect with the correctly determined flag
|
|
//window.location.href = "/victoriapublichall/registerbook?active=" + hasBooking+"user="+hasuser;
|
|
|
|
window.location.href = "/victoriapublichall/registerbook";
|
|
$('#pageLoader').show();
|
|
|
|
});
|
|
|
|
}
|
|
// Error case returned as 200 OK (badRequest still JSON)
|
|
else if (response.verifiedotp === "error") {
|
|
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Invalid OTP',
|
|
text: response.message
|
|
});
|
|
}
|
|
},
|
|
error: function (xhr) {
|
|
|
|
console.log("Error Response:", xhr);
|
|
|
|
let msg = "Something went wrong";
|
|
|
|
// Extract JSON error response
|
|
try {
|
|
let err = JSON.parse(xhr.responseText);
|
|
msg = err.message;
|
|
} catch (e) {
|
|
msg = "Server error. Try again.";
|
|
}
|
|
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Error',
|
|
text: msg
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
});
|
|
}
|
|
|
|
// keyboard shortcuts
|
|
if (contactInput) contactInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') sendBtn.click(); });
|
|
if (otpInput) otpInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') submitOtpBtn.click(); });
|
|
|
|
// input smoothing: if user types digits only, limit to 10 digits (for phone)
|
|
if (contactInput) {
|
|
contactInput.addEventListener('input', () => {
|
|
if (/^\d*$/.test(contactInput.value)) {
|
|
contactInput.value = contactInput.value.replace(/\D/g, '').slice(0, 10);
|
|
}
|
|
});
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|