|
|
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{fragments/user-head :: head}"></head>
<header th:replace="~{user/razor-pay :: header}"></header>
<style> :root { --accent: #12363b; --muted: #6b6b6b; --card-bg: #ffffff; --slot-radius: 10px; --green-start: #16a34a; --green-end: #09905a; }
* { 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; }
/* 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; }
.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; }
#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); }
/* 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; }
/****************************** 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; }
.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 */ }
.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: 0px !important; /* ⬅ FIXES CENTER ISSUE */ }
.input { width: 100%; padding: 14px 16px; border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.06); background: #fff; font-size: 16px; }
.btn { display: inline-block; padding: 14px 18px; border-radius: 12px; background: var(--accent); color: #fff; font-weight: 700; border: none; cursor: pointer; box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12); }
/* CSS - responsive behavior */
/* default field behaviour */ .field { width: 100%; max-width: 760px; /* original maximum */ margin: 0 auto 18px; /* center and spacing */ margin-top: 0 auto 18px; }
/* compact variant to match your 536x48 measurement */ .field .input.compact { box-sizing: border-box; width: 100%; height: 48px; /* your requested height */ padding: 10px 14px; /* inner spacing */ border-radius: 10px; border: 1px solid rgba(0, 0, 0, 0.06); font-size: 15px; background-color: white; }
#contactDisplay { color: #666; font-weight: 700; padding: 10px 14px; border-radius: 10px; min-width: 180px;
}
#changeContact { color: #0478D0; text-decoration: underline; cursor: pointer; /*margin-left: 40px;*/ font-size: medium; }
.muted-label { display: block; margin-bottom: 8px;
color: #000; font-weight: 700; }
/* Desktop */ .people-row { display: flex; align-items: center; gap: 16px; /*white-space: nowrap;*/ }
.muted-label-inline { font-weight: 600; color: black; /*margin-right: 320px;*/ font-size: 16px; }
.small { height: 40px; width: 40px; display: flex; align-items: center; justify-content: center; border-radius: 8px; border: 1px solid #ddd; background: #fff; cursor: pointer; }
.counter { min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; font-weight: 700; border: 1px solid #ddd; background: #fff; border-radius: 8px; }
.people-support { margin-top: 10px; color: #000000 !important; font-size: 13px; }
.people-support .link { color: #0478D0; text-decoration: none; }
/* a little spacing when controls are inline with other content */ .spacer { width: 12px; }
.date-wrap { display: flex; align-items: center; gap: 10px; position: relative; }
/* main date input visual */ .date-wrap .input[type="date"] { font-family: "Inter", sans-serif !important; font-size: 16px; color: #0000 !important; padding: 12px 44px 12px 12px; /* reserve space on right for icon */ width: 100%; background: #fff; border: 1px solid #ccc; border-radius: 12px; box-sizing: border-box; cursor: pointer; background-color: white;
/* keep native appearance so browsers show their indicator */ -webkit-appearance: textfield; appearance: textfield; }
/* Force inner pieces to be black in WebKit browsers */ .date-wrap .input[type="date"]::-webkit-datetime-edit, .date-wrap .input[type="date"]::-webkit-datetime-edit-text, .date-wrap .input[type="date"]::-webkit-datetime-edit-month-field, .date-wrap .input[type="date"]::-webkit-datetime-edit-day-field, .date-wrap .input[type="date"]::-webkit-datetime-edit-year-field { color: #000 !important; }
.date-wrap .input[type="date"]::-webkit-calendar-picker-indicator { position: absolute; /* allow us to place it over the input area */ right: 12px; /* matches padding-right */ top: 50%; transform: translateY(-50%); width: 22px; height: 22px; opacity: 1; /* ensure visible */ cursor: pointer; filter: none; /* remove inversion if any */ -webkit-appearance: auto; /* let browser draw its icon */ }
.date-wrap .input[type="date"]::-moz-focus-inner { border: 0; }
/* fallback: custom inline SVG icon placed absolutely (only shows if native icon not visible) */ .date-wrap::after { content: ""; position: absolute; right: 12px; top: 50%; transform: translateY(-50%); width: 22px; height: 22px; pointer-events: none; /* indicator should not intercept clicks; native indicator will */ 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>"); background-repeat: no-repeat; background-position: center; background-size: 20px 20px; opacity: 0.9; }
.date-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 10px; background: rgba(0, 0, 0, 0.04); }
.counter { width: 46px; text-align: center; font-weight: 700; }
/* time range pills */ /* pills */ .time-ranges { display: flex; gap: 10px; flex-wrap: wrap; margin: 14px 0 8px; }
.pill { padding: 10px 12px; border-radius: 10px; background: rgba(255, 255, 255, 0.95); border: 1px solid rgba(0, 0, 0, 0.06); min-width: 110px; text-align: center; cursor: pointer; user-select: none; }
.pill small { display: block; color: var(--muted); margin-top: 6px; font-weight: 600; font-size: 12px; }
.pill.active { background: linear-gradient(180deg, var(--green-start), var(--green-end)); color: #fff; border-color: transparent; box-shadow: 0 8px 26px rgba(0, 0, 0, 0.08); }
.pill.active small { color: #fff !important; }
/* info row: left multi-line, right inline summary */ .info-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 18px; margin-top: 6px; }
.preview-note-box { width: 320px; font-size: 13px; color: var(--muted); line-height: 1.45; }
.summary-inline { display: flex; gap: 18px; align-items: center; /*white-space: nowrap;*/ flex-shrink: 1; flex-wrap: wrap; }
.summary-item { font-size: 13px; color: var(--muted); }
.summary-item strong { margin-left: 6px; font-weight: 700; }
/* once a slot is chosen and locked, make it look non-clickable */ .slot.locked { cursor: not-allowed; }
/* slots grid and slot styles */ .slots-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 14px; }
.slot { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-radius: 10px; background: var(--card-bg); border: 1px solid rgba(0, 0, 0, 0.06); cursor: pointer; }
.slot.disabled { background: #f1f1f1; color: #8b8b8b; cursor: not-allowed; }
.left { display: flex; flex-direction: column; gap: 6px; }
.time { font-weight: 700; }
.meta { font-size: 13px; color: var(--muted); }
.choose { width: 36px; height: 36px; border-radius: 8px; border: 1px solid rgba(0, 0, 0, 0.08); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 16px; }
.slot .time { font-weight: 700; color: #000 !important; /* always black */ }
.slot.chosen { background: #ffffff !important; /* keep slot white */ color: #fff; border-color: transparent; border-color: rgba(0, 0, 0, 0.06) !important; /* optional subtle glow around the entire slot (comment out if not wanted) */ box-shadow: 0 6px 18px rgba(9, 144, 90, 0.04); }
/* .slot.chosen .meta{ color: rgba(255,255,255,0.9); } */ .slot.chosen .choose { background: linear-gradient(180deg, var(--green-start), var(--green-end)) !important; color: #fff; border-color: transparent !important; box-shadow: 0 6px 16px rgba(9, 144, 90, 0.12) !important; }
.info-row { display: flex !important; flex-direction: column !important; /* always stacked */ align-items: center !important; /* center items horizontally */ justify-content: flex-start; gap: 14px; width: 100%; max-width: 820px; margin: 0 auto; box-sizing: border-box; }
/* preview note becomes a centered block (fluid width) */ .preview-note-box { width: 100%; max-width: 640px; flex: 0 0 auto; font-size: 13px; color: #000000; line-height: 1.45; text-align: left !important; /* ← LEFT aligned */ padding: 6px 8px; }
.summary-inline { width: 100% !important; /* use the full container width */ max-width: 100% !important; /* don't constrain to 420px here */ display: flex !important; align-items: center !important; justify-content: flex-end !important; /* left / right extremes */ gap: 24px !important; padding: 6px 8px !important; box-sizing: border-box !important; margin: 0 auto !important; white-space: nowrap !important; }
/* Make each item expand to fill half the line */ .summary-item { display: flex !important; justify-content: flex-end !important; /* both right-aligned */ /* align-items: center !important; */ gap: 8px !important; line-height: 1 !important; flex: 0 0 auto !important; /* allow items to grow and take space */ min-width: 0 !important; /* prevent overflow due to long text */ text-align: right !important; }
/* Left block: align contents to left */ .summary-item.unallocated { justify-content: flex-end !important; text-align: right !important; /* color: #E63946 !important; */ }
/* Right block: align contents to right */ .summary-item.allocated { justify-content: flex-end !important; text-align: right !important; /* color: #16A34A !important; */ }
/* Keep numeric counter compact and centered inside its area */ .summary-item strong { min-width: 28px !important; display: inline-block !important; text-align: center !important; }
/* DEFAULT color = black */ .summary-item span, .summary-item strong { color: #000 !important; }
/* Red */ .text-red span, .text-red strong { color: #E63946 !important; }
/* Green */ .text-green span, .text-green strong { color: #16A34A !important; }
.submit-note { font-family: "Inter", sans-serif; font-size: 13px; color: #000000; text-align: left; /* left aligned */ margin: 3px 0 10px 0; /* top:3px, bottom:10px */ margin-left: 3px; }
.submit-wrap { margin-top: 16px; }
/* Book Visit — visible by default */ .book-vist { opacity: 1; pointer-events: auto; }
/* Cancel Book — HIDDEN by default */ .cancel-book { display: none; /* ⬅ start hidden */ }
/* Utility classes that JS toggles */ .section-hidden { display: none !important; }
.section-enabled { display: block !important; opacity: 1 !important; pointer-events: auto !important; }
/* When Cancel is visible, style the button normally */ .cancel-book.section-enabled .btn { background: #043B55; color: #ffffff; border-radius: 10px; border: none; cursor: pointer; font-weight: 700; box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12); }
/* summary card inside cancel section (you already had this) */ .summary-card {
max-width: 100%; background: #fff; border-radius: 10px; padding: 18px 20px; border: 2px solid rgba(0, 0, 0, 0.12); box-sizing: border-box; font-family: Inter, sans-serif; color: #111; margin: 0 auto; }
/* Top row */ .summary-row { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
.summary-left .label { font-size: 13px; font-weight: 700; margin-bottom: 10px; color: black; }
.summary-right div { font-size: 13px; font-weight: 400; margin-bottom: 10px; text-align: right; color: black; }
/* AV */ .av-title { font-size: 13px; font-weight: 700; color: black; margin: 6px 0 10px; }
.av-list { display: flex; flex-direction: column; gap: 8px; }
.av-row { display: flex; justify-content: space-between; font-size: 13px; }
.av-time, .av-seats { font-size: 13px; font-weight: 400; margin-bottom: 10px; }
/* main buttons (book visit / preview etc.) */ .submit-wrap { margin-top: 16px; }
.btn { width: 100%; padding: 12px 26px; background: #043B55; color: #ffffff; border-radius: 10px; border: none; cursor: pointer; font-weight: 700; box-sizing: border-box; margin-bottom: 15px; }
/* small note text under cards */ .submit-note { font-family: "Inter", sans-serif; font-size: 13px; color: #000000; text-align: left; margin: 10px 0; margin-left: 3px; font-weight: 700; }
/* make the button INSIDE cancel-book clearly disabled */ .cancel-book.section-enabled .btn { background: #fff; /* SAME background */ color: #000; border-radius: 10px; border: 3px solid #000; /* SAME border */ cursor: pointer; font-weight: 700; box-shadow: 0 8px 26px rgba(18, 54, 59, 0.12); }
/* ---------- Mobile polish (drop this at the end of your CSS) ---------- */ @media (max-width: 520px) {
/* page / body */ body { padding: 12px 10px; background-position: center top; background-size: cover; -webkit-font-smoothing: antialiased; }
/* container & panels */ .container, .panel-wrap, .page { width: 100%; max-width: 420px; margin: 0 auto; padding: 0 8px; box-sizing: border-box; }
.panel-wrap { padding: 6px 0 18px; }
/* header / nav */ .logo { width: 61px; height: 61px; }
nav.primary { display: none !important; }
#desktopBookBtn { display: none !important; }
.hamburger { display: flex !important; }
/* hero */ .page h1 { font-size: 32px !important; width: 100%; max-width: 360px; margin: 6px auto 8px; }
.hero .lead { font-size: 14px !important; max-width: 320px; margin-bottom: 12px; }
/* fields */ .field { width: calc(100% - 20px); margin-left: 10px; margin-right: 10px; max-width: none; }
.field .input, .field .input.compact { width: 100%; font-size: 15px; padding: 12px 14px; height: 46px; border-radius: 10px; }
/* contact row */ #contactLine { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
#contactDisplay { min-width: 0; flex: 1 1 auto; padding: 10px 12px; font-weight: 700; border-radius: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-left: -14px }
#changeContact { margin-left: 10px; }
/* label and inline people row */ .muted-label { font-size: 16px; margin-left: 0 !important; display: block; }
/* whole row */ .people-row { display: flex; align-items: center; width: 100%; gap: 8px; /* IMPORTANT: allow wrapping so row doesn't overflow */ white-space: normal; }
/* label on the left */ .muted-label-inline { flex: 1 1 auto; /* take left side space */ margin-right: 0; font-size: 15px; font-weight: 600; color: black; text-align: left; }
/* Hover effect for Select Date input */ .date-wrap .input[type="date"]:hover { border-color: #043B55; /* brand color */ box-shadow: 0 0 0 2px rgba(4, 59, 85, 0.12); cursor: pointer; }
/* Focus (keyboard / click) */ .date-wrap .input[type="date"]:focus { outline: none; border-color: #043B55; box-shadow: 0 0 0 3px rgba(4, 59, 85, 0.18); }
/* push controls to the right */ #minusPeople { margin-left: auto; /* this pushes the group to the right */ }
#peopleCount, #plusPeople { margin-left: 6px; /* spacing between controls */ }
/* touch-friendly sizes */ .small { width: 40px; height: 40px; border-radius: 10px; font-size: 18px; }
.counter { min-width: 52px; height: 40px; border-radius: 10px; font-size: 16px; }
/* pills / time ranges */ .time-ranges { gap: 8px; margin: 12px 0; justify-content: flex-start; overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 6px; }
.time-ranges::-webkit-scrollbar { display: none; }
.pill { min-width: 96px; padding: 5px; border-radius: 10px; flex: 0 0 auto; }
/* slots grid -> single column */ .slots-grid { grid-template-columns: 1fr; gap: 10px; margin-top: 12px; }
.slot { padding: 10px 12px; border-radius: 10px; }
/* info row -> stack */ .info-row { flex-direction: column; align-items: stretch; gap: 10px; margin-top: 10px; }
.preview-note-box { width: 100%; max-width: none; font-size: 13px; }
.summary-inline { justify-content: flex-end !important; gap: 12px !important; flex-wrap: wrap !important; white-space: normal !important; }
.summary-item { flex: 0 0 48% !important; /* two-column layout on small screens */ box-sizing: border-box !important; }
.summary-item.unallocated { justify-content: flex-start !important; text-align: left !important; }
.summary-item.allocated { justify-content: flex-end !important; text-align: right !important; }
/* submit area */ .submit-wrap { margin-top: 18px; padding-bottom: 18px; }
.btn { width: 100%; padding: 12px 14px; font-size: 16px; border-radius: 12px; }
/* small text adjustments */ .muted, .people-support, .summary-item { font-size: 13px; line-height: 1.3; }
/* avoid long fixed widths on mobile that break layout */ .page h1, .hero .lead, .preview-note-box, .summary-inline, .field { max-width: 100%; }
/* small helper: ensure form controls never overflow */ input[type="text"], input[type="date"], textarea, select { max-width: 100%; box-sizing: border-box; }
/* interaction: slightly larger tap areas on interactive elements */ .pill, .slot, .small, .btn { touch-action: manipulation; -webkit-tap-highlight-color: rgba(0, 0, 0, 0.06); }
}
/* small helpers */ .link { color: #0b76c6; text-decoration: underline; cursor: pointer; font-size: 14px; }
.vph-modal-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.56); display: none; align-items: center; justify-content: center; z-index: 9999; padding: 28px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
.vph-modal-overlay.show { display: flex !important; }
/* modal card */ .vph-modal { width: 500px; max-width: 100%; background: #fff; border-radius: 12px; box-shadow: 0 30px 80px rgba(7, 25, 30, 0.45); padding: 20px 22px; position: relative; outline: none;
/* keep on-screen */ overflow: hidden; /* inner area handles overflow */ display: flex; flex-direction: column; gap: 12px; transition: transform .18s ease, opacity .18s ease; transform: translateY(0); }
/* when overlay appears the modal can fade in (optional) */ .vph-modal-overlay.show .vph-modal { transform: translateY(0); opacity: 1; }
/* header */ .vph-modal-header { text-align: center; margin-top: 2px; }
.vph-modal-header h3 { margin: 0; font-weight: 700; color: #3B1E14; font-size: 18px; letter-spacing: 0; font-family: "Inter", sans-serif !important; /* Add Inter font */ }
/* content area (scrollable) */ .vph-modal .vph-modal-content { flex: 1 1 auto; overflow-y: auto; padding-right: 6px; }
/* summary box (top) */ .vph-summary-box {
min-height: auto; /* ✅ allow growth */ height: auto; /* ✅ dynamic */
background: #f3f4f2; /* subtle off-white */ border-radius: 8px; padding: 12px; margin: 0; border: 1px solid rgba(0, 0, 0, 0.02); }
/* two-column summary row */ .vph-summary-row { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; }
.vph-summary-left, .vph-summary-right { width: 48%; display: flex; flex-direction: column; gap: 10px; }
.vph-summary-left .vph-label { color: #000 !important; /* Black text */ font-size: 13px; font-weight: 700 !important; /* Normal, not bold */ font-family: "Inter", sans-serif !important; /* Add Inter font */ }
.vph-summary-right { text-align: right; color: #0b0b0b; font-size: 14px; font-weight: 400;
}
/* AV Experience box */ .vph-av-box { /* width:562px; */ /* height:114px; */ max-height: 30vh; overflow-y: auto; background: #f3f4f2; border-radius: 8px; padding: 10px 12px; border: 1px solid rgba(0, 0, 0, 0.02); }
.vph-av-box::-webkit-scrollbar { width: 4px; }
.vph-av-box::-webkit-scrollbar-thumb { background: #b5b5b5; border-radius: 5px; }
.vph-av-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-radius: 6px; background: transparent; }
.vph-av-time { font-weight: 400; color: #0b0b0b; font-size: 14px; }
.vph-av-meta { font-weight: 400; color: #0b0b0b; font-size: 14px; }
/* footer area — controls */ .vph-modal-footer { display: flex; gap: 12px; align-items: center; margin-top: auto; flex-shrink: 0; }
/* left icon button (circle) */ .vph-modal-icon-btn { Width: 66px; Height: 66px; border-radius: 10px; border: 1px solid #043B55; background: #fff; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 6px 18px rgba(7, 25, 30, 0.04); }
.vph-modal-submit { width: 100%; /* responsive */ max-width: 482px; /* desktop size */ height: 66px;
padding: 12px 36px; border-radius: 10px; background: #043B55; color: #fff; border: none; font-weight: 700; cursor: pointer; box-shadow: 0 10px 30px rgba(11, 75, 72, 0.14); }
/* close X */ .vph-modal-close { position: absolute; right: 12px; top: 12px; width: 36px; height: 36px; border-radius: 8px; border: none; background: transparent; font-size: 20px; cursor: pointer; color: #6b6b6b; }
/* ===== Cancel Booking modal helpers (re-use existing .vph-modal styles) ===== */
/* two-step views */ .vph-view { display: none; }
.vph-view.active { display: block; }
/* scrollable content area if you ever need body text */
/* footer: normal row + column variant for success view */ .vph-modal-footer { display: flex; gap: 12px; align-items: center; justify-content: space-between; margin-top: 25px; }
.vph-modal-footer.column { flex-direction: column; align-items: stretch; gap: 12px; margin-top: 14px; }
/* icon button on left */ .vph-modal-icon-btn { width: 66px; height: 50px; border-radius: 10px; border: 1px solid #043B55; background: #fff; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 6px 18px rgba(7, 25, 30, 0.04); }
/* primary + ghost buttons for cancel modal */ .vph-modal-submit.primary { height: 50px; width: 100%; padding: 14px 18px; border-radius: 10px; background: #043B55; color: #fff; border: none; font-weight: 700; cursor: pointer; box-shadow: 0 10px 30px rgba(11, 75, 72, 0.14); }
.vph-modal-submit.ghost { height: 50px; width: 100%; padding: 12px 16px; border-radius: 10px; background: #fff; color: #0b0b0b; border: 2px solid rgba(0, 0, 0, 0.08); font-weight: 700; cursor: pointer; }
/* small compact info box (if needed later) */ .vph-summary-box.small { background: #f7f7f6; border-radius: 8px; padding: 10px; border: 1px solid rgba(0, 0, 0, 0.04); margin-bottom: 6px; text-align: center; }
/* small screens: stack and make scrollable */ @media (max-width: 520px) { .vph-modal { width: 94%; padding: 14px; border-radius: 12px; max-height: 92vh; }
.vph-summary-row { gap: 10px; flex-direction: row; }
.vph-summary-left, .vph-summary-right { width: 48%; }
/*.vph-av-list {*/ /* max-height: 30vh;*/ /*}*/
.vph-modal-footer { gap: 10px; align-items: center; }
.vph-modal-submit { width: 56%; padding: 12px; width: 256px; } }
/* optional: thin custom scrollbar inside modal for better UX */
.vph-modal ::-webkit-scrollbar { height: 8px; width: 8px; }
.vph-modal ::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.08); border-radius: 6px; }
/* make sure text wrapping and truncation are tidy */ .vph-summary-right div, .vph-summary-left div, .vph-av-row { word-break: break-word; }
.panel { background: transparent; }
.date-wrap .form-control[readonly] { background-color: #fff !important; color: #000 !important; }
.disabled-pill { background-color: #eee !important; border-color: #ddd !important; }
.no-slots-message { margin-top: 10px; color: #c62828; font-size: 14px; font-weight: 600; }
.no-slots-message-box { padding: 12px; margin-top: 10px; text-align: center; color: #d9534f; /* red */ font-weight: 600; font-size: 14px; /* background: #ffeaea; */ /* soft red background */ border-radius: 6px; }
/* make sure summary left labels are visible and take up space */ .summary-row { display: flex; align-items: flex-start; gap: 12px; }
.summary-left, .summary-right { width: 50%; box-sizing: border-box; }
.summary-left .label { display: block; color: #111 !important; /* force readable color */ font-size: 13px; font-weight: 700; margin-bottom: 10px; }
.summary-right>div { display: block; color: #111 !important; font-weight: 400; text-align: right; margin-bottom: 10px; }
/* ========================================= CALENDAR HOVER — FINAL GUARANTEED FIX ========================================= */
/* Target the REAL visible flatpickr input */ .date-wrap input.input.form-control { cursor: pointer; background-color: #fff !important; transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease; }
/* Hover */ .date-wrap input.input.form-control:hover { border-color: #043B55 !important; box-shadow: 0 0 0 2px rgba(4, 59, 85, 0.18) !important; background-color: #fafafa !important; }
/* Focus */ .date-wrap input.input.form-control:focus { outline: none !important; border-color: #043B55 !important; box-shadow: 0 0 0 3px rgba(4, 59, 85, 0.28) !important; }
/* Optional: hover anywhere in wrapper */ .date-wrap:hover input.input.form-control { border-color: #043B55 !important; }
/* FIX: Cancel Booking button should NOT be full width */ #cancelBookSection .submit-wrap { display: flex; gap:12px; justify-content: center;
flex-wrap: wrap; /* allow stacking */ }
#cancelBookSection .submit-wrap .btn { width: auto !important; max-width: none !important; min-width: 240px; padding: 12px 32px; margin-right: 4px }
/* bugs follows */
/* Interchange font sizes */
/* Make preview note text bigger */ .preview-note-box { font-size: 16px !important; font-weight: 500; line-height: 1.6; }
/* Make the label slightly smaller */ .field>.muted-label { font-size: 14px !important; font-weight: 600; }
@keyframes shake { 0% { transform: translateX(0); }
20% { transform: translateX(-5px); }
40% { transform: translateX(5px); }
60% { transform: translateX(-5px); }
80% { transform: translateX(5px); }
100% { transform: translateX(0); } }
.slot.shake { animation: shake 0.35s ease; border-color: #dc2626 !important; }
.border-field { border: 1px solid rgba(0, 0, 0, 0.15); /* visible but subtle */ border-radius: 12px; /* matches your UI */ padding: 16px 18px; /* space inside box */ background: #ffffff; /* white card look */ }
/* age group details Container */ /* Container */ .people-type-list { margin-top: 12px; display: grid; /*grid-template-columns: repeat(2, 1fr); !* 2 by 2 *!*/ column-gap: 24px; row-gap: 16px; }
/* Each card */ .people-type-row { display: flex; justify-content: space-between; align-items: center; }
/* Label */ .people-type-label { font-weight: 600; }
/* Controls */ .people-type-controls { display: flex; align-items: center; gap: 10px; }
.people-total-row { display: flex; justify-content: flex-end; /* ✅ pushes content to right */ align-items: center; margin-top: 20px; font-weight: 700; font-size: 20px; }
.people-total-label { margin-right: 15px; }
.people-total-value { color: #043B55; }
/* aadhar & passport */ .id-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 6px; padding-top: 10px; padding-bottom: 10px; }
/* ================================ PREVIEW MODAL – EXTRA DETAILS ================================ */
/* Shared box (re-uses your visual language) */ .breakdown-box { background: #f3f4f2; border-radius: 8px; padding: 14px 16px; margin-top: 14px; border: 1px solid rgba(0, 0, 0, 0.05); }
/* Header row */ .breakdown-header { display: flex; justify-content: space-between; align-items: center; font-family: "Inter", sans-serif; font-weight: 700; font-size: 14px; color: #111; margin-bottom: 10px; }
/* Rows container */ .breakdown-body { display: flex; flex-direction: column; gap: 8px; }
/* Each row */ .breakdown-row { display: grid; grid-template-columns: 1fr auto; align-items: center; font-family: "Inter", sans-serif; font-size: 14px; }
/* Labels */ .breakdown-label { color: #111; font-weight: 500; }
/* Amount values */ .breakdown-value { font-weight: 600; text-align: right; min-width: 70px; }
/* Divider line */ .breakdown-divider { margin: 10px 0; border-top: 1px solid #d1d5db; }
/* Total row */ .breakdown-row.total { font-size: 16px; font-weight: 700; }
.breakdown-row.total .breakdown-value { color: #043B55; }
/* ================================ ID PROOF SECTION ================================ */
.breakdown-idproof { margin-top: 8px; font-family: "Inter", sans-serif; font-size: 14px; color: #111; display: flex; gap: 8px; flex-wrap: wrap; }
.breakdown-idproof strong { font-weight: 700; }
@media (min-width: 768px) {
.muted-label-inline { margin-right: 320px; }
.people-type-list { grid-template-columns: repeat(2, 1fr); } }
@media(min-width: 768px) {
#contactLine { display: flex; gap: 360px; margin-left: -14px; } }
@media (max-width: 520px) { .people-type-list { grid-template-columns: 1fr; } }
@media (min-width: 521px) { #cancelBookSection .submit-wrap .btn { min-width: 200px; width: auto !important; } }
/* Mobile */ @media (max-width: 520px) { #cancelBookSection .submit-wrap { flex-direction: column; align-items: stretch; }
#cancelBookSection .submit-wrap .btn { width: 100% !important; min-width: unset !important; margin-right: 0 !important; } }
/* Cancel button – Red */ #cancelBtn { background: #dc2626 !important; /* red-600 */ color: #fff !important; border: none; }
#cancelBtn:hover { background: #b91c1c !important; /* darker red */ }
/* QR button – Green */ #qrBtn { background: #16a34a !important; /* green-600 */ color: #fff !important; border: none; }
#qrBtn:hover { background: #15803d !important; /* darker green */ }
</style>
<!-- </head> -->
<body> <div class="container"> <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>
<main class="page" role="main" aria-labelledby="hero-heading"> <div class="hero" aria-hidden="false"> <h1 id="hero-heading">Victoria Public Hall</h1> <div class="lead">Experience Chennai's First Town Hall</div> </div>
<div class="panel-wrap">
<div class="panel" role="region" aria-label="Booking panel">
<div class="people-support"> For booking more than 10 people, please click here: <!-- <a class="link" href="/victoriapublichall/bulkbooking" onclick="storeMobileForBulk()"> </a>--> <a class="link" href="javascript:void(0)" onclick="handleBulkBookingClick()"> Bulk Booking Request </a>
</div>
<!-- phone/email & OTP -->
<div class="field mt-4"> <label class="muted-label">Phone / Email</label> <div id="contactLine"> <div id="contactDisplay">
</div> <a id="changeContact" href="/victoriapublichall/register"> Change </a> </div> </div>
<div id="bookVisitSection" class="book-vist">
<!-- select date --> <div class="field"> <label class="muted-label">Select Date</label> <div class="date-wrap"> <input id="dateInput" class="input" type="date" aria-label="Select date"> </div> </div> <div class="field"> <div class="date-note"> <strong>Note:</strong> Victoria Public Hall remains closed every Tuesday for maintenance.
</div> </div>
<div class="field"> <label class="muted-label">Identity Proof</label>
<div class="id-row"> <!-- DROPDOWN --> <select id="idType" class="input compact"> <option value="">Select ID Proof</option> </select>
<!-- NUMBER INPUT --> <input type="text" id="idNumber" class="input compact" placeholder="Enter ID Number" disabled /> <small id="idError" style="color:#dc2626; display:none; font-weight:600;"></small> </div> </div>
<div class="field" style="margin-bottom:30px;">
<!-- SINGLE LABEL --> <label class="muted-label-inline">Number of people</label>
<!-- DYNAMIC LIST --> <div id="peopleTypeList" class="people-type-list"></div>
<div class="field"> <div class="people-total-row"> <span class="people-total-label">Total Amount</span> <span id="totalAmount" class="people-total-value">₹0</span> </div> </div>
</div>
<hr>
<br>
<!-- Pick your time ranges --> <!-- time-range pills -->
<div class="field border-field"> <label class="muted-label">Pick your time</label> <div class="time-ranges" id="timeRanges" role="tablist" aria-label="Time ranges">
</div>
<div id="noAvInfoMsg" class="no-slots-message-box" style="display:none;"> </div>
<!-- <div id="noAvInfoMsg" class="no-slots-message-box" style="display:none;">
During the above time slots, the public will be permitted only within the premises of Victoria Public Hall. Entry into the building will not be allowed. </div>-->
<!--<div class="info-row">
<div class="preview-note-box"> <Strong>Your visit includes a audio visual experience, please select as many shows as required to accommodate all visitors.</Strong> </div>
<div class="summary-inline" aria-hidden="false"> <div class="summary-item unallocated" aria-label="Guest unallocated"> <span>Guest unallocated</span> <strong>03</strong> </div>
<div class="summary-item allocated" aria-label="Guest allocated"> <span>Guest allocated</span> <strong>03</strong> </div> </div>
</div>-->
</div>
<!-- slots grid --> <div class="field border-field">
<div class="info-row"> <div class="preview-note-box"> <Strong>Your visit includes a audio visual experience, please select as many shows as required to accommodate all visitors.</Strong> </div>
<div class="summary-inline" aria-hidden="false"> <div class="summary-item unallocated" aria-label="Guest unallocated"> <span>Guest unallocated</span> <strong>03</strong> </div>
<div class="summary-item allocated" aria-label="Guest allocated"> <span>Guest allocated</span> <strong>03</strong> </div> </div>
</div> <label style="margin:8px 0 6px 0" class="muted-label">Pick one or more shows for all visitors</label> <div id="slotsGrid" class="slots-grid" aria-live="polite"></div> </div>
<div class="submit-wrap"> <p class="submit-note">Your booking details will be sent to you via email/SMS.</p> <button id="previewBtn" class="btn">Preview & Submit</button> </div>
</div>
<!-- CANCEL BOOK SECTION (DISABLED BUT VISIBLE) --> <div id="cancelBookSection" class="cancel-book"> <div class="summary-card">
<!-- TOP SUMMARY ROW --> <div class="summary-row"> <div class="summary-left"> <div class="label">Visiting on</div> <div class="label">Time</div> <div class="label">Total Guest(s)</div> </div>
<div class="summary-right"> <div id="confirmDate"></div> <div id="confirmRange"></div> <div id="confirmPeople"></div> </div> </div>
<div class="av-title">AV Experience</div> <!-- AV LIST --> <div class="av-list"> <div class="av-row"> <div class="av-time"></div> <div class="av-seats"></div> </div> </div>
<!-- Disabled cancel button inside cancel-book --> <div class="submit-wrap"> <button id="cancelBtn" class="btn" type="button">Cancel Booking</button> <button id="qrBtn" class="btn" type="button">Get BookingQR</button>
</div>
</div>
<p class="submit-note"> You can book additional visits only after completing/canceling your current visit. </p> </div>
</div> </div> </main> </div>
<!-- Booking Preview Modal (add to end of <body>) --> <div id="vphBookingModalOverlay" class="vph-modal-overlay" aria-hidden="true"> <div id="vphBookingModal" class="vph-modal" role="dialog" aria-modal="true" aria-labelledby="vphModalTitle" tabindex="-1"> <header class="vph-modal-header"> <h3 id="vphModalTitle">Your visit details</h3> </header>
<section class="vph-summary-box" aria-live="polite"> <div class="vph-summary-row"> <div class="vph-summary-left"> <div class="vph-label">Visiting on</div> <div class="vph-label">Time</div> <div class="vph-label">ID Proof</div> <div class="vph-label">ID Number</div> <div class="vph-label">Total Guest(s)</div> </div> <div class="vph-summary-right"> <div id="vphModalDate">—</div> <div id="vphModalRange">—</div> <div id="vphModalIdType">—</div> <div id="vphModalIdNumber">—</div> <div id="vphModalPeople">—</div> </div> </div> <!--class="vph-av-title"--> <div id="vphAvTitle" class="vph-label">AV Experience</div> <div id="vphModalSlotsList" aria-hidden="false"> <!-- populated by JS --> </div>
</section>
<!-- Guest Breakdown --> <section class="vph-summary-box breakdown-box"> <div class="breakdown-header"> <span>Guest Details</span> <span>Amount</span> </div>
<div id="previewGuestBreakdown" class="breakdown-body"> <!-- Filled by JS --> </div>
<div class="breakdown-divider"></div>
<div class="breakdown-row total"> <span>Total Amount</span> <span id="previewTotalAmount">₹0</span> </div> </section>
<footer class="vph-modal-footer"> <button id="vphModalBack" class="vph-modal-icon-btn" aria-label="Back"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"> <path d="M15 18l-6-6 6-6" stroke="#0f4b4a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button>
<button id="vphModalSubmit" class="vph-modal-submit">Proceed to Payment</button> </footer>
<!-- <button id="vphModalClose" class="vph-modal-close" aria-label="Close dialog">×</button> --> <button class="vph-modal-close preview-close" aria-label="Close dialog">×</button>
</div> </div>
<!-- Cancel Booking – 2-step modal --> <div id="vphModalOverlay" class="vph-modal-overlay" aria-hidden="true"> <div id="vphModal" class="vph-modal" role="dialog" aria-modal="true" aria-labelledby="vphModalTitleConfirm" tabindex="-1">
<div class="vph-modal-body">
<!-- VIEW 1: Confirmation --> <div id="vphViewConfirm" class="vph-view" role="document" aria-hidden="true"> <header class="vph-modal-header"> <h3 id="vphModalTitleConfirm">Are you sure you want to cancel your visit?</h3> </header>
<footer class="vph-modal-footer"> <button id="vphConfirmBack" class="vph-modal-icon-btn" aria-label="Go back"> <!-- left arrow icon --> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"> <path d="M15 18l-6-6 6-6" stroke="#0f4b4a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button>
<button id="vphConfirmYes" class="vph-modal-submit primary"> Yes, Cancel booking </button> </footer> </div>
<!-- VIEW 2: Result / Success --> <div id="vphViewCancelled" class="vph-view" role="document" aria-hidden="true"> <header class="vph-modal-header"> <h3 id="vphModalTitleCancelled">Booking Cancelled!</h3> </header>
<footer class="vph-modal-footer column"> <button id="vphGoMain" class="vph-modal-submit ghost"> Go back to main page </button> <button id="vphBookAnother" class="vph-modal-submit primary"> Book another visit </button> </footer> </div>
</div>
<!-- Close X --> <!-- <button id="vphModalClose" class="vph-modal-close" aria-label="Close dialog">×</button> --> <button class="vph-modal-close cancel-close" aria-label="Close dialog">×</button>
</div> </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 src="https://cdn.jsdelivr.net/npm/flatpickr"></script> -->
<script type="text/javascript">
function showLoader() { $('#pageLoader').show(); }
function hideLoader() { $('#pageLoader').hide(); }
$('#pageLoader').hide();
//checking
/* Full corrected booking JS */ let activeRange = null; let activeTimeId = null; let bookingId = null; let bookingemail = null; let bookingmob_no = null;
let people = 0; // const MIN_PEOPLE = 1; // const MAX_PEOPLE = 10;
// Selected time slots map const chosenSlots = new Map(); let slotsGrid; let unallocEl, allocEl, dateInput;
let peopleTypes = [];
let isIdNumberValid = false;
$(document).ready(function () {
updateGuestSummary(); // DOM refs (safe) slotsGrid = document.getElementById('slotsGrid'); // minusPeople = document.getElementById('minusPeople'); // plusPeople = document.getElementById('plusPeople'); // peopleCount = document.getElementById('peopleCount'); unallocEl = document.querySelector('.summary-item.unallocated strong'); allocEl = document.querySelector('.summary-item.allocated strong'); dateInput = document.getElementById('dateInput'); //Booking / cancel sections const bookVisit = document.getElementById("bookVisitSection"); const cancelBook = document.getElementById("cancelBookSection"); const cancelBtn = document.getElementById("cancelBtn");
const previewBtn = document.getElementById('previewBtn');
const qrBtn = document.getElementById("qrBtn");
// Basic session handling let hasBooking = sessionStorage.getItem("hasBooking") === "true"; let hasuser = sessionStorage.getItem("hasuser"); let bookingdetails = sessionStorage.getItem("bookingdetails"); console.log("→ hasBooking:", hasBooking); console.log("→ hasuser:", hasuser); console.log("→ bookingdetails:", bookingdetails);
if (hasuser === null || hasuser.trim() === "") { window.location.href = "/victoriapublichall/register"; return; }
const Displayname = document.getElementById("contactDisplay"); if (Displayname && hasuser) Displayname.textContent = hasuser;
let validBooking = false;
if (bookingdetails && bookingdetails.trim() !== "") { try { let b = JSON.parse(bookingdetails); validBooking = b && b.id; // valid only if id exists } catch (e) { validBooking = false; } } if (hasBooking || validBooking) {
bookVisit.classList.add("section-hidden"); cancelBook.classList.add("section-enabled"); cancelBtn.disabled = false;
if (validBooking) { let b = JSON.parse(bookingdetails);
console.log("details>>>>>>>>>",b)
bookingId = b.id;
bookingemail = b.email; bookingmob_no = b.mob_no;
bookingorderid=b.order_id;
console.log(bookingemail); console.log(bookingmob_no);
console.log("bookingorderid>>>>",bookingorderid);
document.getElementById("confirmDate").textContent = formatDisplayDate(b.visitingDate); document.getElementById("confirmRange").textContent = b.timeRange; document.getElementById("confirmPeople").textContent = String(b.no_of_people).padStart(2, "0");
const avList = document.querySelector(".av-list"); avList.innerHTML = "";
b.allocatedSlots.forEach(s => { console.log(s); avList.innerHTML += ` <div class="av-row"> <div class="av-time">${s.time} (${s.language})</div> <div class="av-seats">${s.people} Seat(s)</div> </div> `; }); }
} else {
cancelBook.classList.remove("section-enabled"); bookVisit.classList.remove("section-hidden"); cancelBtn.disabled = true; }
if (!dateInput) return;
// flatpickr setup const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const maxDate = new Date(today); maxDate.setDate(maxDate.getDate() + 7);
const fp = flatpickr(dateInput, { dateFormat: "Y-m-d", altInput: true, altFormat: "d-m-Y", minDate: today, maxDate: maxDate, allowInput: false, clickOpens: true, disable: [ function (date) { if (!(date instanceof Date)) return false; return date.getDay() === 2; } ], onReady: function (selectedDates, dateStr, instance) { instance.altInput.setAttribute("placeholder", "dd-mm-yyyy"); },
onChange: function (selectedDates, dateStr, instance) { if (!dateStr) instance.altInput.setAttribute("placeholder", "dd-mm-yyyy"); // When date changes we must reset state and reload ranges activeTimeId = null; activeRange = null; chosenSlots.clear(); if (slotsGrid) slotsGrid.innerHTML = ""; loadTimeRanges(); } });
fp.setDate(today);
// enable opening on click/focus dateInput.addEventListener('focus', () => fp.open()); dateInput.addEventListener('click', () => fp.open());
loadTimeRanges();
// modal submit handler document.getElementById("vphModalSubmit").addEventListener("click", function () {
const userInput = (document.getElementById("contactDisplay") || {}).textContent?.trim() || ""; const visitDate = (dateInput || {}).value || ""; const no_of_people = people; const timeId = activeTimeId;
const slotDetails = [...chosenSlots].map(([time, obj]) => { const el = Array.from(document.querySelectorAll(".slot")) .find(s => s.dataset.time === time);
return { slotId: Number(el?.dataset?.slotId || 0), people: obj.seats
}; });
/*if (slotDetails.length === 0) { Swal.fire({ icon: "warning", text: "Please select at least one show." }); return; } */ // Close modal before showing any Swal to avoid focus-lock issues const overlayEl = document.getElementById('vphBookingModalOverlay'); if (overlayEl) { overlayEl.classList.remove("show"); document.body.style.overflow = ""; // blur modal to remove active focus const modalEl = document.getElementById('vphBookingModal'); if (modalEl) modalEl.blur(); }
$.ajax({ type: "POST", url: "/victoriapublichall/bookingapi/saveRegistration", data: { userInput: userInput, visitDate: visitDate, no_of_people: no_of_people, timeId: timeId, slotDetails: JSON.stringify(slotDetails),
peopleDetails: JSON.stringify( peopleTypes .filter(p => p.count > 0) .map(p => ({ id: p.id, count: p.count })) ),
idType: $("#idType").val() || null, idDetails: $("#idNumber").val() || null, totalAmount: $("#totalAmount").text().replace("₹", ""), source: 1
}, success: function (response) { console.log("response", response) // response might be string or JSON. handle both.
if (response.status === "success") { // 1️ Save booking reference const refId = response.refId; const amount = $("#totalAmount").text().replace("₹", "");
sessionStorage.setItem("confirmrefid", response.refId);
showLoader(); paymentStart(refId, amount);
} else if (response.status === "error") { hideLoader(); Swal.fire({ icon: "error", title: "Booking Failed", text: "Slots are fully booked. Please select another slot" }); } else { hideLoader(); Swal.fire({ icon: "error", title: "Booking Failed", text: response.message || "Failed" }); }
}, error: function (xhr) { Swal.fire({ icon: "error", title: "Booking Failed", text: xhr.responseText || "Something went wrong!" }); } }); });
/* PEOPLE COUNT – DB DRIVEN (JDBC) */
const MAX_TOTAL_PEOPLE = 10;
/* TOTAL PEOPLE */ function getTotalPeople() { return peopleTypes.reduce((sum, p) => sum + p.count, 0); }
/* TOTAL AMOUNT */ function getTotalAmount() { return peopleTypes.reduce((sum, p) => { return sum + (p.count * p.amount); }, 0); }
/* UPDATE TOTAL AMOUNT UI */ function updateTotalAmount() { const total = getTotalAmount(); const totalEl = document.getElementById("totalAmount"); if (totalEl) { totalEl.textContent = "₹" + total; } }
/* RENDER PEOPLE UI */ function renderPeopleTypes() { const container = document.getElementById("peopleTypeList"); if (!container) return;
container.innerHTML = "";
const total = getTotalPeople();
peopleTypes.forEach((item, index) => { const row = document.createElement("div"); row.className = "people-type-row";
row.innerHTML = ` <div class="people-type-label">${item.user_type} <span class="people-amount">(₹${item.amount})</span> </div> <div class="people-type-controls"> <button class="small minus-btn" data-i="${index}">−</button> <div class="counter">${String(item.count).padStart(2, "0")}</div> <button class="small plus-btn" data-i="${index}">+</button> </div> `;
container.appendChild(row); });
// minus container.querySelectorAll(".minus-btn").forEach(btn => { btn.onclick = () => { const i = btn.dataset.i; if (peopleTypes[i].count > 0) { peopleTypes[i].count--; renderPeopleTypes(); syncPeopleWithBooking(); } }; });
// plus container.querySelectorAll(".plus-btn").forEach(btn => { btn.onclick = () => { if (getTotalPeople() >= MAX_TOTAL_PEOPLE) return; const i = btn.dataset.i; peopleTypes[i].count++; renderPeopleTypes(); syncPeopleWithBooking(); }; });
// disable + buttons when max reached if (total >= MAX_TOTAL_PEOPLE) { container.querySelectorAll(".plus-btn").forEach(btn => btn.disabled = true); } }
function syncPeopleWithBooking() { const total = getTotalPeople();
// single source of truth window.people = total; people = total;
// auto adjust slot allocations autoAllocateExtraGuests(); deallocateExtraGuests();
// update summary counts correctly updateGuestSummary();
updateTotalAmount(); }
/* LOAD PEOPLE TYPES FROM DB */ $.ajax({ url: "/victoriapublichall/bookingapi/getamountbyusertype", type: "GET", success: function (data) { peopleTypes = data.map(row => ({ id: row.id, user_type: row.user_type, amount: row.amount, count: 0, maxCount: row.user_type && row.user_type.trim().toLowerCase() === 'family' ? 5 : 0 }));
renderPeopleTypes(); syncPeopleWithBooking(); }, error: function () { Swal.fire({ icon: "error", text: "Unable to load people categories" }); } });
$.ajax({ url: "/victoriapublichall/bookingapi/getidproofs", type: "GET", success: function (data) { const select = document.getElementById("idType");
data.forEach(row => { const opt = document.createElement("option"); opt.value = row.id; // store ID opt.textContent = row.id_name; // show Aadhar Card / Passport select.appendChild(opt); }); }, error: function () { Swal.fire({ icon: "error", text: "Unable to load ID proof types" }); } });
/* ENABLE ID NUMBER ONLY AFTER SELECT */ const idType = document.getElementById("idType"); const idNumber = document.getElementById("idNumber"); const idError = document.getElementById("idError");
idType.addEventListener("change", function () { const selectedText = this.options[this.selectedIndex].text.toLowerCase();
idNumber.disabled = !this.value; idNumber.value = ""; idError.style.display = "none"; idError.textContent = "";
if (!this.value) return;
idNumber.focus();
// 🔹 AADHAR
if (selectedText.includes("aadhar")) { idNumber.maxLength = 12; idNumber.placeholder = "Enter 12-digit Aadhar Number";
idNumber.oninput = function () { this.value = this.value.replace(/\D/g, "");
if (this.value.length === 12) { idError.style.display = "none"; isIdNumberValid = true; } else { idError.textContent = "Aadhar number must be exactly 12 digits"; idError.style.display = "block"; isIdNumberValid = false; } }; }
// 🔹 PASSPORT
else if (selectedText.includes("passport")) { idNumber.maxLength = 9; idNumber.placeholder = "Enter Passport Number";
idNumber.oninput = function () { this.value = this.value.replace(/[^a-zA-Z0-9]/g, "");
if (this.value.length >= 6 && this.value.length <= 9) { idError.style.display = "none"; isIdNumberValid = true; } else { idError.textContent = "Passport number must be 6–9 characters"; idError.style.display = "block"; isIdNumberValid = false; } }; } });
qrBtn && qrBtn.addEventListener("click", function () {
if (!bookingorderid) { Swal.fire({ icon: "error", text: "Order ID not found" }); return; }
const qrUrl = "/victoriapublichall/bookingQRCode?orderid=" + bookingorderid;
window.open(qrUrl, "_blank"); });
});
function formatDisplayDate(dateStr) { if (!dateStr || typeof dateStr !== "string") return "--";
// Trim & normalize const s = dateStr.trim();
// If already in an ISO-ish form, handle both "yyyy-mm-dd" and "dd-mm-yyyy" // Detect by first token length: if first token has length 4 -> yyyy-mm-dd const parts = s.split("-"); let yyyy, mm, dd;
if (parts.length === 3) { if (parts[0].length === 4) { // format: yyyy-mm-dd yyyy = parts[0]; mm = parts[1]; dd = parts[2]; } else { // assume dd-mm-yyyy dd = parts[0]; mm = parts[1]; yyyy = parts[2]; } } else { // fallback parse attempt — try Date constructor const d = new Date(s); if (isNaN(d)) return s; return d.toLocaleDateString("en-IN", { day: "2-digit", month: "short", year: "numeric" }); }
// safe integer conversion const y = Number(yyyy), m = Number(mm) - 1, d = Number(dd); const dateObj = new Date(y, m, d); if (isNaN(dateObj)) return dateStr;
// Example output: "13 Dec 2025" return dateObj.toLocaleDateString("en-IN", { day: "2-digit", month: "short", year: "numeric" }); }
/* ---------- API functions ---------- */
function loadSlotsFromAPI() { const dateValue = dateInput?.value; if (!dateValue || !activeTimeId) { // clear slotsGrid and show message to indicate no selection if (slotsGrid) slotsGrid.innerHTML = `<div class="no-slots-message-box">Please select a time range to view AV slots.</div>`; return; }
$.ajax({ type: "GET", url: "/victoriapublichall/bookingapi/getTimeSlotDetails", data: { visitDate: dateValue, timeId: activeTimeId }, success: function (data) { const slotList = (data || []).map(item => ({ slotId: item.slot_id, time: item.timing, seats: item.availCap, language: item.language })); renderSlotsWithData(slotList); }, error: function () { console.error("Failed to load slot details"); } }); }
function renderSlotsWithData(slotArray) { if (!slotsGrid) return;
slotsGrid.innerHTML = ""; chosenSlots.clear();
if (!slotArray || slotArray.length === 0) { slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry, No AV time slots available for the selected date.</div>`; return; }
slotArray.forEach(item => { const time = item.time; const seats = Number(item.seats); const slotId = item.slotId; const language = item.language
const slotEl = document.createElement("div"); slotEl.className = "slot"; slotEl.tabIndex = 0;
slotEl.dataset.time = time; slotEl.dataset.seats = seats; slotEl.dataset.originalSeats = seats; slotEl.dataset.slotId = slotId; slotEl.dataset.language = language;
if (seats <= 0) { slotEl.classList.add("disabled"); slotEl.style.border = "1px solid #d9534f"; } else { slotEl.style.border = "1px solid #28a745"; }
slotEl.innerHTML = ` <div class="left"> <div class="time">${time} <span class="slot-lang">(${language})</span></div> <div class="meta">${seats <= 0 ? "0 Seats available" : seats + (seats === 1 ? " Seat available" : " Seats available")}</div> </div> <div class="choose"></div> `;
slotEl.addEventListener("click", () => toggleSlot(slotEl)); slotsGrid.appendChild(slotEl); });
updateGuestSummary(); }
function loadTimeRanges() { const timeRanges = document.getElementById("timeRanges"); const previewBtn = document.getElementById("previewBtn"); if (!timeRanges) return;
// clear UI + old messages and reset state timeRanges.innerHTML = ""; const oldMsg = document.getElementById("noSlotsMsg"); if (oldMsg) oldMsg.remove();
activeTimeId = null; activeRange = null; chosenSlots.clear(); if (slotsGrid) slotsGrid.innerHTML = "";
if (previewBtn) { previewBtn.disabled = true; previewBtn.classList.add("disabled-btn"); }
const visitDate = dateInput?.value; if (!visitDate) { return; }
$.ajax({ type: "GET", url: "/victoriapublichall/bookingapi/getTimeMaster", data: { visitDate: visitDate, view: "book"}, success: function (data) { if (!data || data.length === 0) { // No ranges at all if (slotsGrid) { slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry,No AV time slots available for the selected date.</div>`; } const msg = document.createElement("div"); msg.id = "noSlotsMsg"; msg.className = "no-slots-message"; msg.textContent = "Sorry, No time slots available for the selected date."; timeRanges.after(msg); return; }
let firstActiveSet = false; let allZero = true;
// create pills data.forEach(item => { if (item.availCap > 0) allZero = false;
const pill = document.createElement("div"); pill.className = "pill"; pill.setAttribute("data-range", item.timing); pill.setAttribute("data-id", item.time_id); pill.innerHTML = `${item.timing}<br><small>${item.availCap} Available</small>`;
if (item.availCap === 0) { pill.classList.add("disabled-pill"); pill.style.pointerEvents = "none"; } else if (!firstActiveSet) { pill.classList.add("active"); activeRange = item.timing; activeTimeId = item.time_id; firstActiveSet = true; }
timeRanges.appendChild(pill); });
attachPillClickHandlers();
// if any pill active, load slots for it and enable preview if (activeTimeId) { loadSlotsFromAPI();
onTimeSelected(activeTimeId,false); if (previewBtn) { previewBtn.disabled = false; previewBtn.classList.remove("disabled-btn"); } }
// if every pill had zero availability -> show message in slots area if (allZero) { if (slotsGrid) { slotsGrid.innerHTML = `<div class="no-slots-message-box">Sorry, No AV time slots available for the selected date and time.</div>`; } const msg = document.createElement("div"); msg.id = "noSlotsMsg"; msg.className = "no-slots-message"; msg.textContent = "No time slots available for the selected date."; timeRanges.after(msg); if (previewBtn) { previewBtn.disabled = true; previewBtn.classList.add("disabled-btn"); } } }, error: function () { console.error("Failed to load time ranges"); } }); }
function attachPillClickHandlers() { const timeRanges = document.getElementById("timeRanges"); if (!timeRanges) return;
// since we recreate pills every time, it's safe to add listeners directly timeRanges.querySelectorAll('.pill').forEach(pill => { pill.addEventListener('click', () => { // if this pill is disabled, ignore if (pill.classList.contains('disabled-pill')) return;
// RESET message + Swal state on EVERY time change resetAvInfoState();
// clear active class timeRanges.querySelectorAll('.pill').forEach(x => x.classList.remove('active')); pill.classList.add('active');
activeRange = pill.getAttribute('data-range'); activeTimeId = pill.getAttribute('data-id');
// reset chosen slots and reload slot grid chosenSlots.clear(); if (slotsGrid) slotsGrid.innerHTML = "";
onTimeSelected(activeTimeId,true);
}); }); }
let requiresSlot = true; // default safe value let avInfoShownOnce = false;
let lastTimeId = null;
function onTimeSelected(timeId,fromClick = false) {
fetch(`/victoriapublichall/bookingapi/slot-rule?timeId=${timeId}`) .then(res => { if (!res.ok) throw new Error("Slot rule API failed"); return res.json(); }) .then(data => {
requiresSlot = Boolean(data.requiresSlot); const message = (data.message || "").trim();
const slotsGrid = document.getElementById("slotsGrid"); const avSection = slotsGrid ? slotsGrid.closest(".field") : null; const infoBox = document.getElementById("noAvInfoMsg");
if (!avSection || !infoBox) return;
if (requiresSlot) {
avSection.style.display = "block"; infoBox.style.display = "none"; infoBox.textContent = "";
chosenSlots.clear(); loadSlotsFromAPI();
}
else {
avSection.style.display = "none"; chosenSlots.clear(); updateGuestSummary();
// show DB-driven message if (message) { infoBox.textContent = message; infoBox.style.display = "block";
// show Swal only when switching to a NEW timeId if (fromClick) { Swal.fire({ icon: "info", title: "Important Information", text: message, confirmButtonText: "OK", allowOutsideClick: false }); } } else { infoBox.style.display = "none"; } }
lastTimeId = timeId; }) .catch(err => { console.error("Slot rule error:", err); }); }
function resetAvInfoState() { const infoMsg = document.getElementById("noAvInfoMsg"); if (infoMsg) infoMsg.style.display = "none";
avInfoShownOnce = false; }
function updateGuestSummary() { let totalAllocated = 0;
chosenSlots.forEach(obj => { totalAllocated += Number(obj?.seats || 0); });
const allocated = Math.min(people, totalAllocated); const unallocated = Math.max(people - allocated, 0);
if (unallocEl) unallocEl.textContent = String(unallocated).padStart(2, '0'); if (allocEl) allocEl.textContent = String(allocated).padStart(2, '0');
const unallocItem = document.querySelector('.summary-item.unallocated'); const allocItem = document.querySelector('.summary-item.allocated');
// Remove old states unallocItem?.classList.remove('text-red', 'text-green'); allocItem?.classList.remove('text-red', 'text-green');
// Apply new colors if (allocated === people) { allocItem?.classList.add('text-green'); // fully allocated } else { unallocItem?.classList.add('text-red'); // still missing allocation } }
//ak minus function function deallocateExtraGuests() { let totalAllocated = 0;
chosenSlots.forEach(obj => { totalAllocated += Number(obj?.seats || 0); });
// Nothing to remove if (totalAllocated <= people) return;
let extraToRemove = totalAllocated - people;
for (const [time, obj] of chosenSlots.entries()) { if (extraToRemove <= 0) break;
const slotEl = [...document.querySelectorAll('#slotsGrid .slot')] .find(s => s.dataset.time === time);
if (!slotEl) continue;
const originalSeats = Number(slotEl.dataset.originalSeats || 0); const usedSoFar = Number(obj.seats || 0);
const removeSeats = Math.min(usedSoFar, extraToRemove); const newUsed = usedSoFar - removeSeats; const newRemaining = originalSeats - newUsed;
if (newUsed > 0) { // preserve language chosenSlots.set(time, { seats: newUsed, language: obj.language }); } else { chosenSlots.delete(time); slotEl.classList.remove('chosen'); const choose = slotEl.querySelector('.choose'); if (choose) choose.textContent = ''; }
slotEl.dataset.seats = newRemaining;
const meta = slotEl.querySelector('.meta'); if (meta) { meta.textContent = `${newRemaining} Seat${newRemaining === 1 ? '' : 's'} available`; }
slotEl.classList.remove('disabled'); extraToRemove -= removeSeats; } }
//plus function function autoAllocateExtraGuests() { let currentlyAllocated = 0;
chosenSlots.forEach(obj => { currentlyAllocated += Number(obj?.seats || 0); });
let remainingGuests = people - currentlyAllocated; if (remainingGuests <= 0) return;
const chosenSlotNodes = document.querySelectorAll('#slotsGrid .slot.chosen');
chosenSlotNodes.forEach(slotEl => { if (remainingGuests <= 0) return;
const time = slotEl.dataset.time; const originalSeats = Number(slotEl.dataset.originalSeats || 0);
const slotObj = chosenSlots.get(time); const usedSoFar = Number(slotObj?.seats || 0);
const remainingCap = originalSeats - usedSoFar; if (remainingCap <= 0) return;
const extraUse = Math.min(remainingCap, remainingGuests); const newUsed = usedSoFar + extraUse; const newRemaining = originalSeats - newUsed;
// keep language intact chosenSlots.set(time, { seats: newUsed, language: slotObj.language });
slotEl.dataset.seats = newRemaining;
const meta = slotEl.querySelector('.meta'); if (meta) { meta.textContent = `${newRemaining} Seat${newRemaining === 1 ? '' : 's'} available`; }
if (newRemaining === 0) { slotEl.classList.add('disabled'); } else { slotEl.classList.remove('disabled'); }
remainingGuests -= extraUse; }); }
function toggleSlot(slotEl) { const time = slotEl.dataset.time;
if (chosenSlots.has(time)) { slotEl.classList.add('shake');
// remove shake after animation setTimeout(() => { slotEl.classList.remove('shake'); }, 350);
return; }
const currentSeats = Number(slotEl.dataset.seats || 0); const originalSeats = Number(slotEl.dataset.originalSeats || 0); /* NOT ENOUGH SEATS FOR SELECTED PEOPLE → SHAKE & STOP */ if (currentSeats < people) { slotEl.classList.add('shake'); setTimeout(() => slotEl.classList.remove('shake'), 400); return; }
if (currentSeats <= 0) return;
document.querySelectorAll('#slotsGrid .slot.chosen').forEach(el => { el.classList.remove('chosen', 'disabled');
const choose = el.querySelector('.choose'); const meta = el.querySelector('.meta'); const origSeats = Number(el.dataset.originalSeats || 0);
el.dataset.seats = origSeats; if (choose) choose.textContent = ''; if (meta) { meta.textContent = `${origSeats} Seat${origSeats === 1 ? '' : 's'} available`; } });
// Reset selection chosenSlots.clear();
const seatsUsed = Math.min(currentSeats, people); const remainingSeats = currentSeats - seatsUsed;
//chosenSlots.set(time, seatsUsed); chosenSlots.set(time, { seats: seatsUsed, language: slotEl.dataset.language });
slotEl.classList.add('chosen'); slotEl.dataset.seats = remainingSeats;
const choose = slotEl.querySelector('.choose'); const meta = slotEl.querySelector('.meta');
if (choose) choose.textContent = '✓'; if (meta) { meta.textContent = `${remainingSeats} Seat${remainingSeats === 1 ? '' : 's'} available`; }
if (remainingSeats === 0) { slotEl.classList.add('disabled'); }
updateGuestSummary(); }
/* Validation helpers */ function isEmail(text) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text); } function isPhone(text) { const d = (text || '').replace(/\D/g, ''); return d.length === 10; } function isValidContact(text) { const v = text.trim(); return isEmail(v) || isPhone(v); } </script>
<script>
(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).'); }); } })();
</script>
<!-- Booking preview Button --> <script> (function () { const previewBtn = document.getElementById('previewBtn'); const overlay = document.getElementById('vphBookingModalOverlay'); const modal = document.getElementById('vphBookingModal');
const modalDate = document.getElementById('vphModalDate'); const modalRange = document.getElementById('vphModalRange'); const modalPeople = document.getElementById('vphModalPeople'); const modalSlotsList = document.getElementById('vphModalSlotsList');
const closeBtn = document.querySelector('.preview-close'); const backBtn = document.getElementById('vphModalBack'); const submitBtn = document.getElementById('vphModalSubmit');
if (!previewBtn || !overlay || !modal) { console.warn('VPH modal: required elements not found.'); return; }
function gatherData() {
// 1️ DATE const dateInput = document.getElementById('dateInput'); const dateVal = dateInput?.value ? new Date(dateInput.value) : null; const dateFormat = dateVal ? dateVal.toLocaleDateString("en-IN", { day: '2-digit', month: 'short', year: 'numeric' }) : "";
// TIME RANGE (active pill) const activePill = document.querySelector('.time-ranges .pill.active'); const timeRangeText = activePill ? activePill.getAttribute("data-range") : "";
// PEOPLE BREAKDOWN const peopleBreakdown = peopleTypes .filter(p => p.count > 0) .map(p => ({ label: p.user_type, count: p.count, amount: p.amount, total: p.count * p.amount }));
// TOTAL AMOUNT const totalAmount = peopleBreakdown.reduce( (sum, p) => sum + p.total, 0 );
// ID PROOF const idTypeSelect = document.getElementById("idType"); const idNumberInput = document.getElementById("idNumber");
const idProof = { type: idTypeSelect?.value || "", label: idTypeSelect?.selectedOptions[0]?.text || "", number: idNumberInput?.value || "" };
const slots = [...chosenSlots].map(([time, obj]) => ({ time, language: obj.language, seats: obj.seats + " Seat(s)" }));
return { dateFormat, timeRangeText, people, slots, peopleBreakdown, totalAmount, idProof }; }
// SHOW MODAL WITH FILLED DATA
function showModal() {
const { dateFormat, timeRangeText, people, slots, peopleBreakdown, totalAmount, idProof } = gatherData();
// Date validation if (!dateFormat) { Swal.fire({ icon: "warning", text: "Please select a date before previewing." }); document.getElementById('dateInput').focus(); return; }
if (!idProof.type) { Swal.fire({ icon: "warning", title: "ID proof required", text: "Please select an identity proof." }).then(() => { idTypeSelect?.focus(); }); return; }
if (!idProof.number || idProof.number.trim() === "") { Swal.fire({ icon: "warning", title: "ID Number Required", text: "Please enter your ID number to continue.", confirmButtonText: "OK", allowOutsideClick: false }).then(() => { idNumberInput.focus(); }); return; }
if (!isIdNumberValid) { Swal.fire({ icon: "error", title: "Invalid ID Number", text: "Please enter a valid ID number before proceeding." }); idNumberInput.focus(); return; // BLOCK SUBMIT }
if (!people || people <= 0) { Swal.fire({ icon: "warning", title: "People count required", text: "Please select at least one visitor before proceeding." }).then(() => { document.querySelector(".people-type-list")?.focus();
}); return; }
// Slot selection validation if (requiresSlot && slots.length === 0) { Swal.fire({ icon: "warning", text: "Please select at least one show before previewing." }); return; }
modalDate.textContent = dateFormat; modalRange.textContent = timeRangeText; modalPeople.textContent = people;
// ADD ID PROOF HERE const idTypeEl = document.getElementById("vphModalIdType"); const idNumberEl = document.getElementById("vphModalIdNumber");
if (idTypeEl) { idTypeEl.textContent = idProof?.label?.trim() || "—"; }
if (idNumberEl) { idNumberEl.textContent = idProof?.number?.trim() || "—"; }
modalSlotsList.innerHTML = ""; if (slots.length === 0) { modalSlotsList.innerHTML = ` <div class="vph-av-row"> <div class="vph-av-time">No shows selected</div> </div>`; } else { slots.forEach(s => { console.log(s); const row = document.createElement("div"); row.className = "vph-av-row"; row.innerHTML = ` <div class="vph-av-time"> ${s.time} <span class="slot-lang">(${s.language})</span> </div> <div class="vph-av-meta">${s.seats}</div> `; modalSlotsList.appendChild(row); });
}
const breakdownEl = document.getElementById("previewGuestBreakdown"); const totalAmountEl = document.getElementById("previewTotalAmount");
breakdownEl.innerHTML = "";
peopleBreakdown.forEach(p => { const row = document.createElement("div"); row.className = "breakdown-row"; row.innerHTML = ` <span class="breakdown-label"> ${p.label} × ${p.count} </span> <span class="breakdown-value"> ₹${p.total} </span> `; breakdownEl.appendChild(row); });
totalAmountEl.textContent = "₹" + totalAmount;
overlay.classList.add("show"); modal.focus(); document.body.style.overflow = "hidden"; }
// CLOSE MODAL function closeModal() { overlay.classList.remove("show"); document.body.style.overflow = ""; previewBtn.focus(); }
previewBtn.addEventListener( "click", function (e) { e.preventDefault();
showModal(); }, true );
// Close options overlay.addEventListener("click", (e) => { if (e.target === overlay) closeModal(); }); closeBtn?.addEventListener("click", closeModal); backBtn?.addEventListener("click", closeModal);
})();
</script>
<script> (function () { // Button inside the cancel-card const cancelBtn = document.getElementById('cancelBtn'); const overlay = document.getElementById('vphModalOverlay'); const modal = document.getElementById('vphModal'); const viewConfirm = document.getElementById('vphViewConfirm'); const viewCancelled = document.getElementById('vphViewCancelled'); const confirmBack = document.getElementById('vphConfirmBack'); const confirmYes = document.getElementById('vphConfirmYes'); const goMain = document.getElementById('vphGoMain'); const bookAnother = document.getElementById('vphBookAnother'); const closeBtn = document.querySelector('.cancel-close');
if (!cancelBtn || !overlay || !modal) { console.warn('Cancel modal: required elements not found.'); return; }
function showView(view) { [viewConfirm, viewCancelled].forEach(v => { if (!v) return; v.classList.remove('active'); v.setAttribute('aria-hidden', 'true'); }); if (view === 'confirm' && viewConfirm) { viewConfirm.classList.add('active'); viewConfirm.setAttribute('aria-hidden', 'false'); setTimeout(() => confirmYes && confirmYes.focus(), 40); } else if (view === 'cancelled' && viewCancelled) { viewCancelled.classList.add('active'); viewCancelled.setAttribute('aria-hidden', 'false'); setTimeout(() => bookAnother && bookAnother.focus(), 40); } }
function openModal(initialView) { overlay.classList.add('show'); overlay.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; showView(initialView || 'confirm'); }
function closeModal() { overlay.classList.remove('show'); overlay.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; cancelBtn && cancelBtn.focus(); }
cancelBtn.addEventListener('click', function (e) { e.preventDefault(); openModal('confirm'); });
closeBtn && closeBtn.addEventListener('click', closeModal); overlay.addEventListener('click', function (e) { if (e.target === overlay) closeModal(); });
confirmBack && confirmBack.addEventListener('click', closeModal);
confirmYes && confirmYes.addEventListener('click', function () { if (!bookingId) { Swal.fire({ icon: "error", text: "Invalid booking. Try again." }); return; }
$.ajax({ type: "POST", url: "/victoriapublichall/bookingapi/cancelBooking", data: { id: bookingId }, success: function (res) { if (res.status === "success") { showView('cancelled'); } else { closeCancelModal(); Swal.fire({ icon: "error", text: res.message || "Cancellation failed" }); } }, error: function () { closeCancelModal(); Swal.fire({ icon: "error", text: "Server error. Try again later." }); } });
});
// success view actions goMain && goMain.addEventListener('click', function () { window.location.href = '/victoriapublichall/home'; // change if needed });
bookAnother && bookAnother.addEventListener('click', function () {
let userentry = null;
if (bookingmob_no && bookingmob_no.trim() !== "") { userentry = bookingmob_no; } else if (bookingemail && bookingemail.trim() !== "") { userentry = bookingemail; }
if (userentry) { // user used mobile or email earlier → continue booking sessionStorage.setItem("hasBooking", "false"); sessionStorage.setItem("hasuser", userentry); sessionStorage.setItem("bookingdetails", ""); // very important! window.location.href = '/victoriapublichall/registerbook'; } else { // no mobile or email found; force fresh login window.location.href = '/victoriapublichall/register'; }
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && overlay.classList.contains('show')) { closeModal(); } });
document.addEventListener('focus', function (e) { if (overlay.classList.contains('show') && !modal.contains(e.target)) { e.stopPropagation(); modal.focus(); } }, true); })();
function closeCancelModal() { const overlay = document.getElementById('vphModalOverlay'); overlay.classList.remove('show'); overlay.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; } </script>
<script> const redirectPath = '/victoriapublichall/register'; // This runs when the user presses the browser's back button window.addEventListener('popstate', function (event) {
window.location.href = redirectPath;
}); // Push a dummy state so that the popstate event triggers properly history.pushState(null, null, location.href);
function storeMobileForBulk() { const mobile = sessionStorage.getItem("hasuser"); // already stored if (mobile) { sessionStorage.setItem("bulkMobile", mobile); } } </script>
<script>
sessionStorage.setItem("bulkMobile", contact);
function handleBulkBookingClick() {
// Get contact from UI / session const contact = $("#contactDisplay").text().trim() || sessionStorage.getItem("bulkMobile");
if (!contact) { Swal.fire("Error", "Contact not found", "error"); return; }
// Call backend to check existing booking $.get("/victoriapublichall/bookingapi/checkBulkBooking", { contact: contact }, function (res) {
if (res.status === "found") {
window.location.href = "/victoriapublichall/acknowledgement?refId=" + res.refId;
} else {
sessionStorage.setItem("bulkMobile", contact); window.location.href = "/victoriapublichall/bulkbooking"; } } ).fail(function () { Swal.fire("Error", "Unable to verify booking", "error"); }); }
</script>
</body>
</html>
|