src/ApplicationBundle/Modules/BookingDemo/Resources/views/pages/book_demo.html.twig line 1

Open in your IDE?
  1. {% include '@Application/inc/central_header.html.twig' %}
  2. <style>
  3. :root {
  4.     --n-cream:     #F7F5F0;
  5.     --n-cream-2:   #F0EDE5;
  6.     --n-cream-3:   #E8E3D9;
  7.     --n-white:     #FFFFFF;
  8.     --n-dark:      #1A1D2E;
  9.     --n-dark-2:    #252840;
  10.     --n-amber:     #C07D2A;
  11.     --n-amber-lt:  #D4954A;
  12.     --n-amber-dim: rgba(192,125,42,.10);
  13.     --n-sage:      #3D6B52;
  14.     --n-muted:     #6B6E7F;
  15.     --n-muted-2:   #9395A5;
  16.     --n-border:    rgba(26,29,46,.07);
  17.     --n-border-md: rgba(26,29,46,.12);
  18.     --n-shadow-sm: 0 2px 12px rgba(26,29,46,.07);
  19.     --n-shadow-md: 0 8px 32px rgba(26,29,46,.09);
  20.     --n-radius:    12px;
  21.     --n-radius-sm: 8px;
  22.     --n-font:      'DM Sans', 'Poppins', system-ui, sans-serif;
  23. }
  24. *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  25. body { background: var(--n-cream); font-family: var(--n-font); color: var(--n-dark); }
  26. a { text-decoration: none; }
  27. .navbar {
  28.     background: rgba(247,245,240,.96) !important;
  29.     backdrop-filter: blur(16px) saturate(180%);
  30.     border-bottom: 1px solid var(--n-border) !important;
  31.     box-shadow: none !important;
  32. }
  33. .navbar .nav-link       { color: var(--n-dark) !important; font-size: 13.5px; font-weight: 500; opacity: .8; }
  34. .navbar .nav-link:hover { opacity: 1; }
  35. .navbar .login-btn      { background: var(--n-dark) !important; color: #fff !important; border-radius: 8px !important; padding: 8px 20px !important; font-size: 13px !important; font-weight: 600 !important; }
  36. /* ── Layout ─────────────────────────────────────────── */
  37. .n-wrap    { max-width: 1100px; margin: 0 auto; padding: 0 28px; }
  38. .n-sec     { padding: 80px 0 100px; }
  39. /* ── Hero ────────────────────────────────────────────── */
  40. .bk-hero {
  41.     background: linear-gradient(135deg, var(--n-dark) 0%, var(--n-dark-2) 100%);
  42.     padding: 80px 0 60px;
  43.     text-align: center;
  44.     color: #fff;
  45. }
  46. .bk-hero__badge {
  47.     display: inline-block;
  48.     background: var(--n-amber-dim);
  49.     color: var(--n-amber-lt);
  50.     border: 1px solid rgba(192,125,42,.3);
  51.     border-radius: 20px;
  52.     padding: 5px 16px;
  53.     font-size: 12.5px;
  54.     font-weight: 600;
  55.     letter-spacing: .5px;
  56.     text-transform: uppercase;
  57.     margin-bottom: 22px;
  58. }
  59. .bk-hero h1 {
  60.     font-family: 'Montserrat', sans-serif;
  61.     font-size: clamp(30px,4vw,50px);
  62.     font-weight: 800;
  63.     line-height: 1.15;
  64.     margin-bottom: 16px;
  65. }
  66. .bk-hero h1 em { color: var(--n-amber-lt); font-style: normal; }
  67. .bk-hero p {
  68.     color: rgba(255,255,255,.7);
  69.     font-size: 16px;
  70.     max-width: 500px;
  71.     margin: 0 auto;
  72.     line-height: 1.6;
  73. }
  74. /* ── Booking card ─────────────────────────────────────── */
  75. .bk-card {
  76.     background: var(--n-white);
  77.     border-radius: var(--n-radius);
  78.     box-shadow: var(--n-shadow-md);
  79.     padding: 40px;
  80.     margin-top: -40px;
  81.     position: relative;
  82.     z-index: 10;
  83.     max-width: 820px;
  84.     margin-left: auto;
  85.     margin-right: auto;
  86. }
  87. /* ── Steps indicator ──────────────────────────────────── */
  88. .bk-steps {
  89.     display: flex;
  90.     gap: 0;
  91.     margin-bottom: 36px;
  92.     border-bottom: 1px solid var(--n-border-md);
  93.     padding-bottom: 24px;
  94. }
  95. .bk-step {
  96.     flex: 1;
  97.     display: flex;
  98.     align-items: center;
  99.     gap: 10px;
  100.     font-size: 13.5px;
  101.     font-weight: 500;
  102.     color: var(--n-muted);
  103.     padding-bottom: 16px;
  104.     border-bottom: 2px solid transparent;
  105.     margin-bottom: -25px;
  106.     transition: all .2s;
  107. }
  108. .bk-step.active { color: var(--n-amber); border-color: var(--n-amber); }
  109. .bk-step.done   { color: var(--n-sage); border-color: var(--n-sage); }
  110. .bk-step__num {
  111.     width: 26px; height: 26px;
  112.     border-radius: 50%;
  113.     background: var(--n-cream-2);
  114.     display: flex; align-items: center; justify-content: center;
  115.     font-size: 12px; font-weight: 700;
  116.     flex-shrink: 0;
  117. }
  118. .bk-step.active .bk-step__num { background: var(--n-amber); color: #fff; }
  119. .bk-step.done .bk-step__num   { background: var(--n-sage); color: #fff; }
  120. /* ── Calendar ──────────────────────────────────────────── */
  121. .bk-cal {
  122.     display: none;
  123.     flex-direction: column;
  124.     gap: 20px;
  125. }
  126. .bk-cal.active { display: flex; }
  127. .bk-cal__nav {
  128.     display: flex;
  129.     align-items: center;
  130.     justify-content: space-between;
  131.     margin-bottom: 12px;
  132. }
  133. .bk-cal__nav button {
  134.     background: none; border: 1px solid var(--n-border-md);
  135.     border-radius: var(--n-radius-sm);
  136.     padding: 6px 14px; cursor: pointer;
  137.     color: var(--n-dark); font-size: 13px;
  138.     transition: background .15s;
  139. }
  140. .bk-cal__nav button:hover { background: var(--n-cream-2); }
  141. .bk-cal__month { font-weight: 700; font-size: 15px; }
  142. .bk-cal__grid {
  143.     display: grid;
  144.     grid-template-columns: repeat(7, 1fr);
  145.     gap: 4px;
  146.     text-align: center;
  147. }
  148. .bk-cal__wd {
  149.     font-size: 11px;
  150.     font-weight: 600;
  151.     color: var(--n-muted-2);
  152.     padding: 4px 0 8px;
  153.     text-transform: uppercase;
  154.     letter-spacing: .4px;
  155. }
  156. .bk-cal__day {
  157.     padding: 9px 4px;
  158.     border-radius: var(--n-radius-sm);
  159.     font-size: 13.5px;
  160.     cursor: pointer;
  161.     transition: all .15s;
  162.     border: 1px solid transparent;
  163. }
  164. .bk-cal__day:hover:not(.past):not(.empty) { background: var(--n-amber-dim); border-color: var(--n-amber); color: var(--n-amber); }
  165. .bk-cal__day.selected { background: var(--n-amber); color: #fff !important; border-color: var(--n-amber); }
  166. .bk-cal__day.past  { color: var(--n-muted-2); cursor: default; }
  167. .bk-cal__day.empty { cursor: default; }
  168. .bk-cal__day.today { font-weight: 700; }
  169. /* ── Slots ─────────────────────────────────────────────── */
  170. .bk-slots { display: none; flex-direction: column; gap: 20px; }
  171. .bk-slots.active { display: flex; }
  172. .bk-slots__heading {
  173.     font-size: 14px;
  174.     font-weight: 600;
  175.     color: var(--n-muted);
  176.     margin-bottom: 4px;
  177. }
  178. .bk-slots__grid {
  179.     display: grid;
  180.     grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  181.     gap: 8px;
  182. }
  183. .bk-slot {
  184.     padding: 10px 6px;
  185.     border: 1.5px solid var(--n-border-md);
  186.     border-radius: var(--n-radius-sm);
  187.     text-align: center;
  188.     cursor: pointer;
  189.     font-size: 13px;
  190.     font-weight: 500;
  191.     transition: all .15s;
  192.     position: relative;
  193.     user-select: none;
  194. }
  195. .bk-slot:hover:not(.unavailable):not(.selected) { border-color: var(--n-amber); color: var(--n-amber); background: var(--n-amber-dim); }
  196. .bk-slot.selected { background: var(--n-amber); color: #fff; border-color: var(--n-amber); }
  197. .bk-slot.selected .bk-slot__priority { display: inline-flex; }
  198. .bk-slot.unavailable { opacity: .38; cursor: not-allowed; text-decoration: line-through; }
  199. .bk-slot__priority {
  200.     display: none;
  201.     position: absolute;
  202.     top: -8px; right: -8px;
  203.     width: 18px; height: 18px;
  204.     background: var(--n-dark); color: #fff;
  205.     border-radius: 50%; font-size: 10px; font-weight: 700;
  206.     align-items: center; justify-content: center;
  207. }
  208. .bk-slots__tip { font-size: 12.5px; color: var(--n-muted-2); margin-top: 8px; }
  209. /* ── Form ──────────────────────────────────────────────── */
  210. .bk-form { display: none; flex-direction: column; gap: 20px; }
  211. .bk-form.active { display: flex; }
  212. .bk-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
  213. .bk-form__field { display: flex; flex-direction: column; gap: 6px; }
  214. .bk-form__field label { font-size: 13px; font-weight: 600; color: var(--n-dark); }
  215. .bk-form__field input,
  216. .bk-form__field select {
  217.     height: 44px;
  218.     border: 1.5px solid var(--n-border-md);
  219.     border-radius: var(--n-radius-sm);
  220.     padding: 0 14px;
  221.     font-size: 14px;
  222.     font-family: var(--n-font);
  223.     color: var(--n-dark);
  224.     background: var(--n-cream);
  225.     outline: none;
  226.     transition: border-color .15s;
  227. }
  228. .bk-form__field input:focus,
  229. .bk-form__field select:focus { border-color: var(--n-amber); background: #fff; }
  230. .bk-form__field input.error { border-color: #e53e3e; }
  231. /* ── Buttons ───────────────────────────────────────────── */
  232. .bk-btn-row { display: flex; gap: 12px; margin-top: 12px; justify-content: flex-end; }
  233. .bk-btn {
  234.     display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  235.     padding: 12px 28px;
  236.     border-radius: var(--n-radius-sm);
  237.     font-size: 14px; font-weight: 600;
  238.     cursor: pointer; border: none;
  239.     transition: all .15s;
  240.     font-family: var(--n-font);
  241. }
  242. .bk-btn--primary { background: var(--n-amber); color: #fff; }
  243. .bk-btn--primary:hover { background: var(--n-amber-lt); }
  244. .bk-btn--secondary { background: var(--n-cream-2); color: var(--n-dark); }
  245. .bk-btn--secondary:hover { background: var(--n-cream-3); }
  246. .bk-btn:disabled { opacity: .5; cursor: not-allowed; }
  247. /* ── Selected slots summary ────────────────────────────── */
  248. .bk-summary {
  249.     background: var(--n-cream);
  250.     border-radius: var(--n-radius-sm);
  251.     padding: 16px 20px;
  252.     font-size: 13.5px;
  253.     color: var(--n-muted);
  254.     border: 1px solid var(--n-border-md);
  255. }
  256. .bk-summary__slot { display: flex; align-items: center; gap: 8px; margin-top: 6px; color: var(--n-dark); font-weight: 500; }
  257. .bk-summary__slot::before { content: ''; display: block; width: 8px; height: 8px; border-radius: 50%; background: var(--n-amber); flex-shrink: 0; }
  258. /* ── Success state ─────────────────────────────────────── */
  259. .bk-success {
  260.     display: none;
  261.     flex-direction: column;
  262.     align-items: center;
  263.     text-align: center;
  264.     gap: 16px;
  265.     padding: 40px 20px;
  266. }
  267. .bk-success.show { display: flex; }
  268. .bk-success__icon { font-size: 52px; }
  269. .bk-success h2 { font-size: 22px; font-weight: 700; }
  270. .bk-success p { color: var(--n-muted); font-size: 15px; max-width: 420px; }
  271. /* ── Alert ─────────────────────────────────────────────── */
  272. .bk-alert {
  273.     padding: 12px 16px;
  274.     border-radius: var(--n-radius-sm);
  275.     font-size: 13.5px;
  276.     display: none;
  277. }
  278. .bk-alert.error   { background: #fff5f5; border: 1px solid #feb2b2; color: #c53030; }
  279. .bk-alert.success { background: #f0fff4; border: 1px solid #9ae6b4; color: #276749; }
  280. .bk-alert.show    { display: block; }
  281. /* ── Loading spinner ───────────────────────────────────── */
  282. .bk-spinner {
  283.     display: none;
  284.     width: 20px; height: 20px;
  285.     border: 2px solid rgba(255,255,255,.4);
  286.     border-top-color: #fff;
  287.     border-radius: 50%;
  288.     animation: spin .6s linear infinite;
  289. }
  290. @keyframes spin { to { transform: rotate(360deg); } }
  291. /* ── Responsive ────────────────────────────────────────── */
  292. @media (max-width: 600px) {
  293.     .bk-card { padding: 24px 18px; }
  294.     .bk-form__row { grid-template-columns: 1fr; }
  295.     .bk-steps { gap: 4px; }
  296.     .bk-step span:not(.bk-step__num) { display: none; }
  297. }
  298. </style>
  299. <!-- Hero -->
  300. <section class="bk-hero">
  301.     <div class="n-wrap">
  302.         <div class="bk-hero__badge">20-Minute Live Demo</div>
  303.         <h1>Book Your <em>Free</em><br>HoneyBee ERP Demo</h1>
  304.         <p>See exactly how HoneyBee can transform your business — live, personalised, no sales pressure.</p>
  305.     </div>
  306. </section>
  307. <!-- Booking card -->
  308. <section class="n-sec">
  309.     <div class="n-wrap">
  310.         <div class="bk-card">
  311.             <!-- Steps -->
  312.             <div class="bk-steps">
  313.                 <div class="bk-step active" id="step-ind-1">
  314.                     <div class="bk-step__num">1</div>
  315.                     <span>Pick Date &amp; Slots</span>
  316.                 </div>
  317.                 <div class="bk-step" id="step-ind-2">
  318.                     <div class="bk-step__num">2</div>
  319.                     <span>Your Details</span>
  320.                 </div>
  321.                 <div class="bk-step" id="step-ind-3">
  322.                     <div class="bk-step__num">3</div>
  323.                     <span>Confirm</span>
  324.                 </div>
  325.             </div>
  326.             <!-- Alert -->
  327.             <div class="bk-alert" id="bk-alert"></div>
  328.             <!-- Step 1: Calendar + slots -->
  329.             <div class="bk-cal active" id="step-1">
  330.                 <div>
  331.                     <div class="bk-cal__nav">
  332.                         <button id="cal-prev">&larr;</button>
  333.                         <span class="bk-cal__month" id="cal-month-label"></span>
  334.                         <button id="cal-next">&rarr;</button>
  335.                     </div>
  336.                     <div class="bk-cal__grid" id="cal-grid"></div>
  337.                 </div>
  338.                 <!-- Time slots for selected date -->
  339.                 <div class="bk-slots active" id="slots-panel">
  340.                     <div class="bk-slots__heading" id="slots-heading">Select a date to see available slots</div>
  341.                     <div class="bk-slots__grid" id="slots-grid"></div>
  342.                     <div class="bk-slots__tip" id="slots-tip"></div>
  343.                 </div>
  344.                 <div class="bk-btn-row">
  345.                     <button class="bk-btn bk-btn--primary" id="btn-to-step2" disabled>
  346.                         Next: Your Details &rarr;
  347.                     </button>
  348.                 </div>
  349.             </div>
  350.             <!-- Step 2: Personal details form -->
  351.             <div class="bk-form" id="step-2">
  352.                 <!-- Selected slots summary -->
  353.                 <div class="bk-summary">
  354.                     <strong>Your selected slots:</strong>
  355.                     <div id="summary-slots"></div>
  356.                 </div>
  357.                 <div class="bk-form__row">
  358.                     <div class="bk-form__field">
  359.                         <label for="f-name">Full Name <span style="color:#e53e3e">*</span></label>
  360.                         <input type="text" id="f-name" placeholder="Jane Smith" required>
  361.                     </div>
  362.                     <div class="bk-form__field">
  363.                         <label for="f-email">Work Email <span style="color:#e53e3e">*</span></label>
  364.                         <input type="email" id="f-email" placeholder="jane@company.com" required>
  365.                     </div>
  366.                 </div>
  367.                 <div class="bk-form__row">
  368.                     <div class="bk-form__field">
  369.                         <label for="f-phone">Phone Number</label>
  370.                         <input type="tel" id="f-phone" placeholder="+44 7700 900000">
  371.                     </div>
  372.                     <div class="bk-form__field">
  373.                         <label for="f-company">Company Name <span style="color:#e53e3e">*</span></label>
  374.                         <input type="text" id="f-company" placeholder="Acme Ltd" required>
  375.                     </div>
  376.                 </div>
  377.                 <div class="bk-form__field">
  378.                     <label for="f-type">Business Type</label>
  379.                     <select id="f-type">
  380.                         <option value="">— Select —</option>
  381.                         <option value="retail">Retail</option>
  382.                         <option value="manufacturing">Manufacturing</option>
  383.                         <option value="services">Services</option>
  384.                         <option value="wholesale">Wholesale</option>
  385.                         <option value="construction">Construction</option>
  386.                         <option value="hospitality">Hospitality</option>
  387.                         <option value="healthcare">Healthcare</option>
  388.                         <option value="other">Other</option>
  389.                     </select>
  390.                 </div>
  391.                 <div class="bk-btn-row">
  392.                     <button class="bk-btn bk-btn--secondary" id="btn-back-step1">&larr; Back</button>
  393.                     <button class="bk-btn bk-btn--primary" id="btn-submit">
  394.                         <span id="btn-submit-text">Request Demo</span>
  395.                         <span class="bk-spinner" id="btn-submit-spinner"></span>
  396.                     </button>
  397.                 </div>
  398.             </div>
  399.             <!-- Success state -->
  400.             <div class="bk-success" id="step-success">
  401.                 <div class="bk-success__icon">🎉</div>
  402.                 <h2>Request Received!</h2>
  403.                 <p>Thank you. We've sent a confirmation to your email. Our team will confirm your preferred slot shortly.</p>
  404.                 <a href="{{ url('honeybee_about_us') }}" class="bk-btn bk-btn--primary">Back to Home</a>
  405.             </div>
  406.         </div>
  407.     </div>
  408. </section>
  409. <script>
  410. (function () {
  411.     'use strict';
  412.     // ── State ──────────────────────────────────────────────────────
  413.     const today      = new Date(); today.setHours(0,0,0,0);
  414.     let curYear      = today.getFullYear();
  415.     let curMonth     = today.getMonth();
  416.     let selectedDate = null;
  417.     let selectedSlots = []; // [{date, time, label}]
  418.     const MAX_SLOTS = 3;
  419.     const SLOTS_URL = '{{ path("book_demo_slots", {"date": "__DATE__"}) }}';
  420.     const SUBMIT_URL = '{{ path("book_demo_submit") }}';
  421.     // ── DOM refs ───────────────────────────────────────────────────
  422.     const calGrid      = document.getElementById('cal-grid');
  423.     const calLabel     = document.getElementById('cal-month-label');
  424.     const slotsPanel   = document.getElementById('slots-panel');
  425.     const slotsGrid    = document.getElementById('slots-grid');
  426.     const slotsHeading = document.getElementById('slots-heading');
  427.     const slotsTip     = document.getElementById('slots-tip');
  428.     const alertEl      = document.getElementById('bk-alert');
  429.     const step1        = document.getElementById('step-1');
  430.     const step2        = document.getElementById('step-2');
  431.     const stepSuccess  = document.getElementById('step-success');
  432.     const btnToStep2   = document.getElementById('btn-to-step2');
  433.     const btnBack      = document.getElementById('btn-back-step1');
  434.     const btnSubmit    = document.getElementById('btn-submit');
  435.     const summaryEl    = document.getElementById('summary-slots');
  436.     const MONTHS = ['January','February','March','April','May','June',
  437.                     'July','August','September','October','November','December'];
  438.     const WDS    = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  439.     // ── Calendar render ────────────────────────────────────────────
  440.     function renderCalendar() {
  441.         calLabel.textContent = MONTHS[curMonth] + ' ' + curYear;
  442.         calGrid.innerHTML    = '';
  443.         WDS.forEach(d => {
  444.             const el = document.createElement('div');
  445.             el.className   = 'bk-cal__wd';
  446.             el.textContent = d;
  447.             calGrid.appendChild(el);
  448.         });
  449.         const first = new Date(curYear, curMonth, 1).getDay();
  450.         const days  = new Date(curYear, curMonth + 1, 0).getDate();
  451.         for (let i = 0; i < first; i++) {
  452.             const el = document.createElement('div');
  453.             el.className = 'bk-cal__day empty';
  454.             calGrid.appendChild(el);
  455.         }
  456.         for (let d = 1; d <= days; d++) {
  457.             const dt  = new Date(curYear, curMonth, d);
  458.             const dow = dt.getDay();
  459.             const el  = document.createElement('div');
  460.             el.className   = 'bk-cal__day';
  461.             el.textContent = d;
  462.             if (dt < today || dow === 0 || dow === 6) {
  463.                 el.classList.add('past');
  464.             } else {
  465.                 if (dt.toDateString() === today.toDateString()) el.classList.add('today');
  466.                 const ds = fmt(dt);
  467.                 if (selectedDate === ds) el.classList.add('selected');
  468.                 el.addEventListener('click', () => selectDate(ds));
  469.             }
  470.             calGrid.appendChild(el);
  471.         }
  472.     }
  473.     function fmt(dt) {
  474.         const y = dt.getFullYear();
  475.         const m = String(dt.getMonth()+1).padStart(2,'0');
  476.         const d = String(dt.getDate()).padStart(2,'0');
  477.         return `${y}-${m}-${d}`;
  478.     }
  479.     // ── Date select ────────────────────────────────────────────────
  480.     function selectDate(dateStr) {
  481.         selectedDate = dateStr;
  482.         renderCalendar();
  483.         loadSlots(dateStr);
  484.     }
  485.     function loadSlots(dateStr) {
  486.         slotsHeading.textContent = 'Loading slots…';
  487.         slotsGrid.innerHTML      = '';
  488.         slotsTip.textContent     = '';
  489.         const url = SLOTS_URL.replace('__DATE__', dateStr);
  490.         fetch(url)
  491.             .then(r => r.json())
  492.             .then(data => renderSlots(dateStr, data.slots || []))
  493.             .catch(() => { slotsHeading.textContent = 'Error loading slots. Please try again.'; });
  494.     }
  495.     function renderSlots(dateStr, slots) {
  496.         const dt    = new Date(dateStr + 'T00:00:00');
  497.         const label = dt.toLocaleDateString('en-GB', {weekday:'long', day:'numeric', month:'long'});
  498.         slotsHeading.textContent = 'Available slots for ' + label + ':';
  499.         if (!slots.length) {
  500.             slotsGrid.innerHTML = '<p style="color:var(--n-muted-2);font-size:13px;">No slots available on this date.</p>';
  501.             return;
  502.         }
  503.         slotsGrid.innerHTML = '';
  504.         slots.forEach(slot => {
  505.             const el   = document.createElement('div');
  506.             const key  = dateStr + '|' + slot.time;
  507.             const alreadySel = selectedSlots.find(s => s.key === key);
  508.             el.className   = 'bk-slot' + (!slot.available ? ' unavailable' : '') + (alreadySel ? ' selected' : '');
  509.             el.textContent = slot.time;
  510.             const badge = document.createElement('span');
  511.             badge.className = 'bk-slot__priority';
  512.             if (alreadySel) badge.textContent = selectedSlots.indexOf(alreadySel) + 1;
  513.             el.appendChild(badge);
  514.             if (slot.available) {
  515.                 el.addEventListener('click', () => toggleSlot(dateStr, slot.time, el, badge));
  516.             }
  517.             slotsGrid.appendChild(el);
  518.         });
  519.         updateTip();
  520.     }
  521.     function toggleSlot(dateStr, time, el, badge) {
  522.         const key = dateStr + '|' + time;
  523.         const idx = selectedSlots.findIndex(s => s.key === key);
  524.         if (idx > -1) {
  525.             selectedSlots.splice(idx, 1);
  526.             el.classList.remove('selected');
  527.         } else {
  528.             if (selectedSlots.length >= MAX_SLOTS) {
  529.                 showAlert('You can select up to 3 preferred slots.', 'error');
  530.                 return;
  531.             }
  532.             const dt   = new Date(dateStr + 'T' + time + ':00');
  533.             const lbl  = dt.toLocaleDateString('en-GB', {weekday:'short', day:'numeric', month:'short'}) + ' at ' + time;
  534.             selectedSlots.push({key, date: dateStr, time, label: lbl});
  535.             el.classList.add('selected');
  536.         }
  537.         // Re-render all badges
  538.         document.querySelectorAll('.bk-slot.selected .bk-slot__priority').forEach(b => b.textContent = '');
  539.         selectedSlots.forEach((s, i) => {
  540.             const k  = s.key.replace('|', '\\|');
  541.             const sl = slotsGrid.querySelector('.bk-slot.selected');
  542.             // refresh current view
  543.         });
  544.         renderSlots(dateStr, Array.from(slotsGrid.querySelectorAll('.bk-slot')).map(el => ({
  545.             time: el.textContent.replace(/\d$/, '').trim(),
  546.             available: !el.classList.contains('unavailable'),
  547.         })));
  548.         // reload properly
  549.         loadSlots(dateStr);
  550.         btnToStep2.disabled = selectedSlots.length === 0;
  551.         updateTip();
  552.         hideAlert();
  553.     }
  554.     function updateTip() {
  555.         slotsTip.textContent = selectedSlots.length > 0
  556.             ? `${selectedSlots.length} of ${MAX_SLOTS} slots selected (priority order).`
  557.             : 'Pick up to 3 preferred time slots. First = highest priority.';
  558.     }
  559.     // ── Step navigation ────────────────────────────────────────────
  560.     btnToStep2.addEventListener('click', () => {
  561.         if (selectedSlots.length === 0) return;
  562.         step1.classList.remove('active');
  563.         step2.classList.add('active');
  564.         setStep(2);
  565.         summaryEl.innerHTML = '';
  566.         selectedSlots.forEach((s, i) => {
  567.             const el = document.createElement('div');
  568.             el.className   = 'bk-summary__slot';
  569.             el.textContent = `#${i+1} — ${s.label}`;
  570.             summaryEl.appendChild(el);
  571.         });
  572.     });
  573.     btnBack.addEventListener('click', () => {
  574.         step2.classList.remove('active');
  575.         step1.classList.add('active');
  576.         setStep(1);
  577.         hideAlert();
  578.     });
  579.     function setStep(n) {
  580.         document.querySelectorAll('.bk-step').forEach((el, i) => {
  581.             el.classList.remove('active', 'done');
  582.             if (i + 1 < n) el.classList.add('done');
  583.             if (i + 1 === n) el.classList.add('active');
  584.         });
  585.     }
  586.     // ── Submit ─────────────────────────────────────────────────────
  587.     btnSubmit.addEventListener('click', () => {
  588.         const name    = document.getElementById('f-name').value.trim();
  589.         const email   = document.getElementById('f-email').value.trim();
  590.         const company = document.getElementById('f-company').value.trim();
  591.         if (!name || !email || !company) {
  592.             showAlert('Please fill in all required fields.', 'error');
  593.             return;
  594.         }
  595.         if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  596.             showAlert('Please enter a valid email address.', 'error');
  597.             return;
  598.         }
  599.         btnSubmit.disabled = true;
  600.         document.getElementById('btn-submit-spinner').style.display = 'inline-block';
  601.         document.getElementById('btn-submit-text').textContent = 'Sending…';
  602.         hideAlert();
  603.         const body = new URLSearchParams();
  604.         body.append('name',          name);
  605.         body.append('email',         email);
  606.         body.append('phone',         document.getElementById('f-phone').value.trim());
  607.         body.append('company_name',  company);
  608.         body.append('business_type', document.getElementById('f-type').value);
  609.         selectedSlots.forEach(s => body.append('slots[]', s.key));
  610.         fetch(SUBMIT_URL, {method: 'POST', body, headers: {'X-Requested-With': 'XMLHttpRequest'}})
  611.             .then(r => r.json())
  612.             .then(data => {
  613.                 if (data.success) {
  614.                     step2.classList.remove('active');
  615.                     stepSuccess.classList.add('show');
  616.                     document.querySelector('.bk-steps').style.display = 'none';
  617.                 } else {
  618.                     showAlert(data.message || 'Something went wrong. Please try again.', 'error');
  619.                     resetBtn();
  620.                 }
  621.             })
  622.             .catch(() => {
  623.                 showAlert('Network error. Please check your connection.', 'error');
  624.                 resetBtn();
  625.             });
  626.     });
  627.     function resetBtn() {
  628.         btnSubmit.disabled = false;
  629.         document.getElementById('btn-submit-spinner').style.display = 'none';
  630.         document.getElementById('btn-submit-text').textContent = 'Request Demo';
  631.     }
  632.     // ── Alert helpers ──────────────────────────────────────────────
  633.     function showAlert(msg, type) {
  634.         alertEl.textContent = msg;
  635.         alertEl.className   = 'bk-alert ' + type + ' show';
  636.     }
  637.     function hideAlert() {
  638.         alertEl.className = 'bk-alert';
  639.     }
  640.     // ── Month nav ──────────────────────────────────────────────────
  641.     document.getElementById('cal-prev').addEventListener('click', () => {
  642.         curMonth--;
  643.         if (curMonth < 0) { curMonth = 11; curYear--; }
  644.         renderCalendar();
  645.     });
  646.     document.getElementById('cal-next').addEventListener('click', () => {
  647.         curMonth++;
  648.         if (curMonth > 11) { curMonth = 0; curYear++; }
  649.         renderCalendar();
  650.     });
  651.     // ── Init ───────────────────────────────────────────────────────
  652.     renderCalendar();
  653.     updateTip();
  654.     slotsHeading.textContent = 'Select a date above to see available time slots.';
  655. })();
  656. </script>
  657. {% include '@HoneybeeWeb/footer/central_footer.html.twig' %}