PageSourceSearch

https://exquisite-kitten-9ab8bf.netlify.app/hospitality.js

js exquisite-kitten-9ab8bf.netlify.app collected 2026-10-03 10:43:13 UTC 16,738 bytes, 249 lines download raw bytes

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 &amp; 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 &amp; 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 &amp; 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.