1/* Needham Market FC â Book the Venue hub. 2 Splits the bookings page into two tabs: 3 1) Hire a facility (the existing date/slot venue-hire UI) 4 2) Matchday Hospitality & Sponsorship (1st-team Sat/Tue matchdays) 5 Matchday products: Sponsor a Match + the Highway Assurance Suite, M&S Suite 6 and Double-Size VIP Suite. Prices are POA (to be added later); each is an 7 enquiry/request that lands in the same admin bookings list. The suites are 8 also available for general private hire via the "Hire a facility" tab. */ 9(function () { 10 "use strict"; 11 12 var PACKAGES = [ 13 { id: "sponsor", name: "Sponsor a Match", tag: "Match Sponsorship", 14 desc: "Put your brand front and centre for a 1st-team fixture â matchball, programme and ground branding, announcements, and hospitality for your guests on the day." }, 15 { id: "highway", name: "Highway Assurance Suite", tag: "Matchday Hospitality", 16 desc: "Premium matchday hospitality in the Highway Assurance Suite â a relaxed space to enjoy the game with food and drink." }, 17 { id: "ms", name: "M&S Suite", tag: "Matchday Hospitality",
18 desc: "Matchday hospitality in the M&S Suite â a great spot to host guests, clients or family for a 1st-team game." }, 19 { id: "vip", name: "Double-Size VIP Suite", tag: "Matchday Hospitality", 20 desc: "Our largest hospitality space â the Double-Size VIP Suite, ideal for bigger groups and corporate guests." } 21 ]; 22 23 // matchday package -> bookings.facility key (must match booking-config.js + DB constraint) 24 var FAC_MAP = { sponsor: "hospitality", highway: "highway-suite", ms: "ms-suite", vip: "vip-suite" }; 25 // these facility keys are POA (rate 0) until prices are set 26 var POA_FACILITIES = ["highway-suite", "ms-suite", "vip-suite"]; 27 28 function ready(fn) { if (document.readyState !== "loading") fn(); else document.addEventListener("DOMContentLoaded", fn); } 29 function client() { 30 try { 31 if (window.__nmfcBookClient) return window.__nmfcBookClient; 32 if (window.__nmfcMagicClient) return window.__nmfcMagicClient; 33 if (!window.supabase || !window.NMFC_SUPABASE_URL) return null; 34 window.__nmfcBookClient = window.supabase.createClient(window.NMFC_SUPABASE_URL, window.NMFC_SUPABASE_ANON_KEY); 35 return window.__nmfcBookClient; 36 } catch (e) { return null; } 37 } 38 function ymd(d) { return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); } 39 function fxDate(mon, day) { 40 if (!mon || !/^\d+$/.test(String(day || "").trim())) return null; 41 var mi = new Date(mon + " 1, 2000").getMonth(); if (isNaN(mi)) return null; 42 var t = new Date(); t.setHours(0, 0, 0, 0); 43 for (var off = -30; off <= 400; off++) { var d = new Date(t); d.setDate(d.getDate() + off); if (d.getMonth() === mi && d.getDate() === parseInt(day, 10)) return d; } 44 return null; 45 } 46 47 function injectCSS() { 48 var css = [ 49 ".bk-tabs{max-width:1280px;margin:22px auto 0;padding:0 20px;display:flex;gap:8px;justify-content:center;flex-wrap:wrap}", 50 ".bk-tab{appearance:none;border:1px solid rgba(27,27,34,.14);background:#fff;color:#1B1B22;font:inherit;font-weight:700;font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;padding:12px 20px;border-radius:999px;cursor:pointer}", 51 ".bk-tab.on{background:#16140f;color:#fff;border-color:#16140f}", 52 ".mh{max-width:1100px;margin:0 auto;padding:30px 20px 56px}", 53 ".mh-head{text-align:center;max-width:680px;margin:0 auto 22px}", 54 ".mh-head .ey{color:#C8102E;font-weight:700;letter-spacing:.14em;text-transform:uppercase;font-size:12px;margin-bottom:8px}", 55 ".mh-head h2{font-family:'Anton',sans-serif;text-transform:uppercase;font-size:clamp(28px,5vw,44px);line-height:1;color:#1B1B22}", 56 ".mh-head p{color:#5a5a62;font-size:15px;line-height:1.6;margin-top:12px}", 57 ".mh-fx{display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap;margin:0 0 24px}", 58 ".mh-fx label{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#8C8A82;font-weight:700}", 59 ".mh-fx select{padding:11px 12px;border:1px solid #d8d4c8;border-radius:8px;font:inherit;min-width:280px;background:#fff}", 60 ".mh-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}", 61 "@media(max-width:760px){.mh-grid{grid-template-columns:1fr}}", 62 ".mh-card{display:flex;flex-direction:column;background:#FBF8F1;border:1px solid rgba(27,27,34,.1);border-radius:14px;padding:24px}", 63 ".mh-card.on{border-color:#C8102E;box-shadow:0 10px 26px rgba(200,16,46,.10)}", 64 ".mh-card .tag{font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#C8102E}", 65 ".mh-card h3{font-family:'Anton',sans-serif;text-transform:uppercase;font-size:23px;margin:8px 0 10px;line-height:1.05}", 66 ".mh-card p{color:#5a5a62;font-size:14px;line-height:1.55;flex:1}", 67 ".mh-card .price{margin-top:14px;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#8C8A82;font-weight:700}", 68 ".mh-card .pick{margin-top:14px;align-self:flex-start;background:#16140f;color:#fff;border:0;font:inherit;font-weight:700;font-size:11px;letter-spacing:.14em;text-transform:uppercase;padding:11px 20px;border-radius:8px;
68cursor:pointer}", 69 ".mh-card.on .pick{background:#C8102E}", 70 ".mh-form{max-width:620px;margin:26px auto 0;background:#fff;border:1px solid rgba(27,27,34,.1);border-radius:14px;padding:24px;display:none}", 71 ".mh-form.show{display:block}", 72 ".mh-form h3{font-family:'Anton',sans-serif;text-transform:uppercase;font-size:20px;margin-bottom:4px}", 73 ".mh-form .sel{color:#C8102E;font-weight:700;font-size:13px;margin-bottom:14px}", 74 ".mh-form label{display:block;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#8C8A82;margin:10px 0 4px}", 75 ".mh-form input,.mh-form textarea{width:100%;padding:11px;border:1px solid #d8d4c8;border-radius:8px;font:inherit;box-sizing:border-box}", 76 ".mh-form .row{display:flex;gap:12px}.mh-form .row>div{flex:1}", 77 ".mh-form .go{margin-top:16px;width:100%;padding:13px;border:0;border-radius:8px;background:#C8102E;color:#fff;font-weight:700;letter-spacing:.06em;cursor:pointer}", 78 ".mh-form .go:disabled{opacity:.6;cursor:default}", 79 ".mh-msg{margin-top:12px;font-size:13px}", 80 ".mh-note{text-align:center;color:#8C8A82;font-size:12.5px;margin-top:26px}" 81 ].join(""); 82 var s = document.createElement("style"); s.textContent = css; document.head.appendChild(s); 83 } 84 85 var selected = null; 86 87 function buildMatchday(container) { 88 container.innerHTML = 89 '<div class="mh-head"><p class="ey">Matchday at Bloomfields</p>' + 90 "<h2>Matchday Hospitality & Sponsorship</h2>" + 91 "<p>Enjoy a 1st-team home game in style, or put your business in the spotlight by sponsoring a match. Available on Saturday & Tuesday 1st-team matchdays. Choose a fixture and a package below and weâll be in touch to confirm details and pricing.</p></div>" + 92 '<div class="mh-fx"><label for="mh-fixture">Fixture</label><select id="mh-fixture"><option value="">Loading fixturesâ¦</option></select></div>' + 93 '<div class="mh-grid" id="mh-grid"></div>' + 94 '<form class="mh-form" id="mh-form"><h3>Request this package</h3><div class="sel" id="mh-sel"></div>' + 95 '<div class="row"><div><label>Your name</label><input id="mh-name" required maxlength="120"></div><div><label>Phone</label><input id="mh-phone" inputmode="tel"></div></div>' + 96 '<label>Email</label><input id="mh-email" type="email" required>' + 97 '<label>Anything else? (guest numbers, requirements)</label><textarea id="mh-notes" rows="2" maxlength="400"></textarea>' + 98 '<button type="submit" class="go">Send request â</button><div class="mh-msg" id="mh-msg"></div></form>' + 99 '<p class="mh-note">The named suites are also available for private hire on non-matchdays â see the <strong>Hire a facility</strong> tab.</p>'; 100 101 var grid = container.querySelector("#mh-grid"); 102 PACKAGES.forEach(function (p) { 103 var card = document.createElement("div"); card.className = "mh-card"; card.setAttribute("data-id", p.id); 104 card.innerHTML = '<span class="tag">' + p.tag + "</span><h3>" + p.name + "</h3><p>" + p.desc + "</p>" + 105 '<div class="price">Price on application</div><button type="button" class="pick">Select</button>'; 106 card.querySelector(".pick").addEventListener("click", function () { 107 selected = p; 108 [].forEach.call(grid.querySelectorAll(".mh-card"), function (c) { c.classList.toggle("on", c === card); }); 109 var form = container.querySelector("#mh-form"); 110 container.querySelector("#mh-sel").textContent = p.name + " · " + p.tag; 111 form.classList.add("show"); 112 form.scrollIntoView({ behavior: "smooth", block: "center" }); 113 }); 114 grid.appendChild(card); 115 }); 116 117 // populate fixtures (home, Saturday or Tuesday) 118 var sel = container.querySelector("#mh-fixture"); 119 fetch("/data.json?cb=" + Date.now()).then(function (r) { return r.json(); }).then(function (data) { 120 var opts = '<option value="">Select a 1st-team matchdayâ¦</option>'; 121 var any = false; 122 (data.fixtures || []).forEach(function (f) { 123 if (!f.home) return; 124 var d = fxDate(f.month, f.day); 125 var tbc = !d; 126 if (!tbc) { var wd = d.getDay(); if (wd !== 6 && wd !== 2) return; } // Sat=6, Tue=2 127 any = true; 128 var label = f.opponent + " (H) · " + (tbc ? (f.month + " â date TBC") : d.toLocaleDateString("en-GB", { weekday: "short", day: "
128numeric", month: "short" })); 129 var val = (tbc ? "tbc" : ymd(d)) + "|" + f.opponent; 130 opts += '<option value="' + val + '">' + label + "</option>"; 131 }); 132 if (!any) opts += '<option value="tbc|TBC">An upcoming home matchday (weâll confirm)</option>'; 133 sel.innerHTML = opts; 134 }).catch(function () { sel.innerHTML = '<option value="tbc|TBC">An upcoming home matchday (weâll confirm)</option>'; }); 135 136 // submit 137 container.querySelector("#mh-form").addEventListener("submit", function (e) { 138 e.preventDefault(); 139 var msg = container.querySelector("#mh-msg"); 140 var go = container.querySelector(".go"); 141 if (!selected) { msg.style.color = "#C8102E"; msg.textContent = "Please choose a package above first."; return; } 142 var name = container.querySelector("#mh-name").value.trim(); 143 var email = container.querySelector("#mh-email").value.trim(); 144 if (!name || !email) { msg.style.color = "#C8102E"; msg.textContent = "Please add your name and email."; return; } 145 var fxVal = sel.value || "tbc|an upcoming home matchday"; 146 var parts = fxVal.split("|"); var dateStr = parts[0]; var opp = parts[1] || ""; 147 var bookingDate = (dateStr && dateStr !== "tbc") ? dateStr : "2027-06-30"; 148 var c = client(); if (!c) { msg.textContent = "Sorry, requests are unavailable right now â please call the club."; return; } 149 go.disabled = true; msg.style.color = "#5a5a62"; msg.textContent = "Sendingâ¦"; 150 var purpose = "MATCHDAY: " + selected.name + " vs " + opp + (dateStr === "tbc" ? " (date TBC)" : "") + 151 " â " + (container.querySelector("#mh-notes").value.trim() || "no notes"); 152 c.from("bookings").insert({ 153 facility: (FAC_MAP[selected.id] || "hospitality"), date: bookingDate, start_hour: 12, duration: 5, status: "pending", 154 name: name, email: email, phone: container.querySelector("#mh-phone").value.trim(), purpose: purpose 155 }).then(function (r) { 156 if (r.error) { go.disabled = false; msg.style.color = "#C8102E"; msg.textContent = "Couldnât send â please try again or call the club."; return; } 157 msg.style.color = "#1E7A32"; container.querySelector("#mh-form").querySelector(".go").style.display = "none"; 158 msg.innerHTML = "<strong>Request sent.</strong> Weâll email " + email + " to confirm availability and pricing. Thank you!"; 159 }).catch(function () { go.disabled = false; msg.textContent = "Something went wrong â please try again."; }); 160 }); 161 } 162 163 // Add the named suites to the Hire tab's facility chooser (#facility-tabs). 164 // The page's existing handler is delegated, so appended .fac-tab buttons work natively. 165 var SUITE_META = { 166 "highway-suite": { ic: "HA", sub: "Premium hospitality · Matchday & hire", logo: "assets/sponsors/highway-assurance.png" }, 167 "ms-suite": { ic: "M&S", sub: "Hospitality lounge · Matchday & hire", logo: "https://mandscontractors.co.uk/wp-content/themes/mandscontractors/assets/images/logo.png" }, 168 "vip-suite": { ic: "VIP", sub: "Largest suite · Groups & corporate" } 169 }; 170 function suiteIcon(m) { 171 if (!m.logo) return m.ic; 172 return '<img src="' + m.logo + '" alt="" style="max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain" ' + 173 'onerror="this.parentNode.textContent=\'' + m.ic + '\'">'; 174 } 175 function injectSuiteFacilities() { 176 var tabs = document.getElementById("facility-tabs"); 177 if (!tabs) return; 178 Object.keys(SUITE_META).forEach(function (key) { 179 if (tabs.querySelector('[data-fac="' + key + '"]')) return; 180 var f = (window.NMFC_FACILITIES && window.NMFC_FACILITIES[key]) || {}; 181 var name = f.name || key; 182 var price = (f.rate && f.rate > 0) ? ("£" + f.rate) : "POA"; 183 var unit = (f.unit === "hour") ? "Per Hour" : "Per Session"; 184 var b = document.createElement("button"); 185 b.className = "fac-tab"; b.setAttribute("data-fac", key); 186 b.innerHTML = 187 '<div class="fac-tab-icon">' + suiteIcon(SUITE_META[key]) + "</div>" + 188 '<div class="fac-tab-name">' + name + "</div>" + 189 '<div class="fac-tab-sub">' + SUITE_META[key].sub + "</div>" + 190 '<div class="fac-tab-price"><strong>' + price + "</strong><span>" + unit + "</span></div>"; 191 tabs.appendChild(b); 192 }); 193 // keep the hero "Facilities" stat in sync with the real count 194 [].forEach.call(document.querySelectorAll(".hero-meta-cell"), function (c) { 195 var s = c.querySelector("span"), n = c.querySelector("strong"); 196 if (s && n && /facilities/i.test(s.textContent)) n.textContent = String(tabs.querySelectorAll(".fac-tab").length); 197 }); 198 } 199 200 // In the Hire tab, show "POA" instead of "£0" for the price-
200on-application suites. 201 function relabelPOA(root) { 202 var scope = root || document.querySelector("section.facilities") || document; 203 var walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT, null); 204 var n, hits = []; 205 while ((n = walker.nextNode())) { if (/£\s*0(?!\d)/.test(n.nodeValue)) hits.push(n); } 206 hits.forEach(function (t) { t.nodeValue = t.nodeValue.replace(/£\s*0(?!\d)(\s*(?:\/|per)?\s*\w*)?/i, "POA"); }); 207 } 208 function watchPOA() { 209 var sec = document.querySelector("section.facilities"); 210 if (!sec) return; 211 relabelPOA(sec); 212 var mo = new MutationObserver(function () { relabelPOA(sec); }); 213 mo.observe(sec, { childList: true, subtree: true, characterData: true }); 214 [400, 1200, 2500].forEach(function (ms) { setTimeout(function () { relabelPOA(sec); }, ms); }); 215 } 216 217 ready(function () { 218 var facilities = document.querySelector("section.facilities"); 219 var calendar = document.querySelector("section.calendar-section"); 220 if (!facilities || !calendar) return; 221 var hireGroup = [facilities, calendar, 222 document.querySelector("section.my-bookings"), document.querySelector("section.help")].filter(Boolean); 223 224 injectCSS(); 225 226 var tabs = document.createElement("div"); tabs.className = "bk-tabs"; 227 tabs.innerHTML = '<button class="bk-tab on" data-t="hire">Hire a facility</button>' + 228 '<button class="bk-tab" data-t="match">Matchday Hospitality & Sponsorship</button>'; 229 facilities.parentNode.insertBefore(tabs, facilities); 230 231 var mh = document.createElement("section"); mh.className = "mh"; mh.style.display = "none"; 232 hireGroup[hireGroup.length - 1].insertAdjacentElement("afterend", mh); 233 buildMatchday(mh); 234 235 function show(which) { 236 var hire = which === "hire"; 237 hireGroup.forEach(function (s) { s.style.display = hire ? "" : "none"; }); 238 mh.style.display = hire ? "none" : "block"; 239 [].forEach.call(tabs.querySelectorAll(".bk-tab"), function (b) { b.classList.toggle("on", b.dataset.t === which); }); 240 } 241 [].forEach.call(tabs.querySelectorAll(".bk-tab"), function (b) { 242 b.addEventListener("click", function () { show(b.dataset.t); }); 243 }); 244 injectSuiteFacilities(); 245 watchPOA(); 246 // deep link: /bookings?tab=match 247 if (/[?&]tab=match/.test(location.search)) show("match"); 248 }); 249})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.