(function () {
const THEME_KEY = "corner-cafe-theme";
const SERVICE_KEY = "corner-cafe-order-service";
const MODE_KEY = "corner-cafe-order-mode";
const CART_KEY = "corner-cafe-cart";
const serviceLabels = {
delivery: "Delivery",
collection: "Collection",
"sitting-in": "Sitting in",
};
const modeLabels = {
menu: "Browse menu",
know: "I know what I want",
};
const root = document.documentElement;
const metaTheme = document.getElementById("meta-theme-color");
const toggle = document.querySelector("[data-theme-toggle]");
const applyTheme = (theme) => {
const next = theme === "light" ? "light" : "dark";
root.setAttribute("data-theme", next);
if (metaTheme) metaTheme.setAttribute("content", next === "light" ? "#f6f3ee" : "#070707");
if (toggle) {
toggle.setAttribute(
"aria-label",
next === "light" ? "Switch to dark theme" : "Switch to light theme"
);
}
};
try {
applyTheme(localStorage.getItem(THEME_KEY) || "dark");
} catch (e) {
applyTheme("dark");
}
toggle?.addEventListener("click", () => {
const next = root.getAttribute("data-theme") === "light" ? "dark" : "light";
applyTheme(next);
try {
localStorage.setItem(THEME_KEY, next);
} catch (e) {
/* ignore */
}
});
const params = new URLSearchParams(window.location.search);
const money = (n) => `£${Number(n).toFixed(2)}`;
const priceText = (item) => item.priceDisplay || money(item.price);
const readCart = () => {
try {
const raw = sessionStorage.getItem(CART_KEY);
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
return [];
}
};
const writeCart = (next) => {
try {
sessionStorage.setItem(CART_KEY, JSON.stringify(next));
} catch (e) {
/* ignore */
}
};
let cart = readCart();
let menuRenderList = null;
const cartCount = () => cart.reduce((n, line) => n + line.qty, 0);
const cartTotal = () => cart.reduce((n, line) => n + line.qty * line.price, 0);
const qtyOf = (id) => cart.find((line) => line.id === id)?.qty || 0;
const setQty = (item, qty) => {
const next = Math.max(0, Math.floor(qty));
const idx = cart.findIndex((line) => line.id === item.id);
if (next <= 0) {
if (idx >= 0) cart.splice(idx, 1);
} else if (idx >= 0) {
cart[idx].qty = next;
} else {
cart.push({ id: item.id, name: item.name, price: item.price, qty: next });
}
writeCart(cart);
renderCartChrome();
if (typeof menuRenderList === "function") menuRenderList();
};
const addToCart = (item) => setQty(item, qtyOf(item.id) + 1);
const removeOne = (item) => setQty(item, qtyOf(item.id) - 1);
const clearCart = () => {
cart = [];
writeCart(cart);
renderCartChrome();
if (typeof menuRenderList === "function") menuRenderList();
};
const qtyControlsHtml = (item, qty) => {
if (qty <= 0) {
return ``;
}
return `
${qty}
`;
};
const bindQtyControls = (rootEl, findItem) => {
rootEl.querySelectorAll("[data-add]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const item = findItem(btn.getAttribute("data-add"));
if (item) addToCart(item);
});
});
rootEl.querySelectorAll("[data-inc]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const item = findItem(btn.getAttribute("data-inc"));
if (item) addToCart(item);
});
});
rootEl.querySelectorAll("[data-dec]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const item = findItem(btn.getAttribute("data-dec"));
if (item) removeOne(item);
});
});
};
const renderCartChrome = () => {
const bar = document.querySelector("[data-cart-bar]");
const countEl = document.querySelector("[data-cart-count]");
const totalEl = document.querySelector("[data-cart-total]");
const linesEl = document.querySelector("[data-cart-lines]");
const dialogTotal = document.querySelector("[data-cart-dialog-total]");
if (!bar) return;
const count = cartCount();
if (countEl) countEl.textContent = count === 1 ? "1 item" : `${count} items`;
if (totalEl) totalEl.textContent = money(cartTotal());
if (dialogTotal) dialogTotal.textContent = money(cartTotal());
bar.hidden = count === 0;
if (linesEl) {
linesEl.innerHTML = "";
const menu = window.CornerCafeMenu || [];
cart.forEach((line) => {
const item = menu.find((m) => m.id === line.id) || line;
const li = document.createElement("li");
li.className = "cart-line cart-line--edit";
li.innerHTML = `
${line.name}
${money(line.qty * line.price)}
${qtyControlsHtml(item, line.qty)}
`;
bindQtyControls(li, (id) => menu.find((m) => m.id === id) || (id === line.id ? line : null));
linesEl.appendChild(li);
});
}
};
document.querySelector("[data-cart-review]")?.addEventListener("click", () => {
document.querySelector("[data-cart-dialog]")?.showModal();
});
document.querySelector("[data-cart-clear]")?.addEventListener("click", () => {
clearCart();
document.querySelector("[data-cart-dialog]")?.close();
});
const goCheckout = () => {
if (cartCount() <= 0) return;
const service = params.get("service") || sessionStorage.getItem(SERVICE_KEY) || "";
const mode = params.get("mode") || sessionStorage.getItem(MODE_KEY) || "menu";
if (!serviceLabels[service]) {
window.location.replace("./");
return;
}
document.querySelector("[data-cart-dialog]")?.close();
window.location.href = `checkout.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`;
};
document.querySelectorAll("[data-cart-checkout]").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
goCheckout();
});
});
const pathLinks = document.querySelectorAll("[data-path-link]");
if (pathLinks.length) {
const service = params.get("service") || "";
if (!serviceLabels[service]) {
window.location.replace("./");
return;
}
try {
sessionStorage.setItem(SERVICE_KEY, service);
} catch (e) {
/* ignore */
}
const serviceEl = document.querySelector("[data-order-service]");
if (serviceEl) serviceEl.textContent = serviceLabels[service];
pathLinks.forEach((link) => {
const mode = link.getAttribute("data-mode");
link.href = `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`;
});
}
const serviceEl = document.querySelector("[data-order-service]");
const modeEl = document.querySelector("[data-order-mode]");
const panelMenu = document.querySelector("[data-panel-menu]");
const panelKnow = document.querySelector("[data-panel-know]");
if (serviceEl && (panelMenu || panelKnow)) {
const service = params.get("service") || "";
const mode = params.get("mode") || "";
if (!serviceLabels[service]) {
window.location.replace("./");
return;
}
if (!modeLabels[mode]) {
window.location.replace(`path.html?service=${encodeURIComponent(service)}`);
return;
}
try {
sessionStorage.setItem(SERVICE_KEY, service);
sessionStorage.setItem(MODE_KEY, mode);
} catch (e) {
/* ignore */
}
serviceEl.textContent = serviceLabels[service];
if (modeEl) modeEl.textContent = modeLabels[mode];
const back = document.querySelector("[data-back-path]");
if (back) back.href = `path.html?service=${encodeURIComponent(service)}`;
const heading = document.querySelector("[data-order-heading]");
const lead = document.querySelector("[data-order-lead]");
if (mode === "menu") {
if (heading) heading.textContent = "Menu";
if (lead) lead.textContent = "Add dishes to your basket. Change quantities anytime.";
if (panelMenu) panelMenu.hidden = false;
if (panelKnow) panelKnow.hidden = true;
initMenuPanel();
} else {
if (heading) heading.textContent = "What would you like?";
if (lead) lead.textContent = "Describe your order — we’ll match dishes from the menu.";
if (panelMenu) panelMenu.hidden = true;
if (panelKnow) panelKnow.hidden = false;
initKnowPanel();
}
renderCartChrome();
}
function initMenuPanel() {
const menu = window.CornerCafeMenu || [];
const filters = document.querySelector("[data-menu-filters]");
const list = document.querySelector("[data-menu-list]");
if (!filters || !list) return;
const sections = ["Breakfast", "Lunch", "Tea", "Drinks", "Indian", "Kebab", "Pizza"].filter((s) =>
menu.some((item) => item.section === s)
);
let active = sections[0] || "Breakfast";
const findItem = (id) => menu.find((m) => m.id === id);
const renderFilters = () => {
filters.innerHTML = "";
sections.forEach((section) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "menu-filter" + (section === active ? " is-active" : "");
btn.textContent = section;
btn.setAttribute("role", "tab");
btn.setAttribute("aria-selected", section === active ? "true" : "false");
btn.addEventListener("click", () => {
active = section;
renderFilters();
renderList();
});
filters.appendChild(btn);
});
};
const renderList = () => {
list.innerHTML = "";
const items = menu.filter((item) => item.section === active);
let lastGroup = "";
items.forEach((item) => {
const group = item.group || "";
if (group && group !== lastGroup) {
const h = document.createElement("h3");
h.className = "menu-group";
h.textContent = group;
list.appendChild(h);
lastGroup = group;
}
const row = document.createElement("div");
row.className = "menu-item" + (qtyOf(item.id) > 0 ? " is-in-basket" : "");
const qty = qtyOf(item.id);
row.innerHTML = `
`;
bindQtyControls(row, findItem);
list.appendChild(row);
});
};
menuRenderList = renderList;
renderFilters();
renderList();
}
function tokenize(text) {
return String(text || "")
.toLowerCase()
.replace(/[^a-z0-9\s&']/g, " ")
.split(/\s+/)
.filter((t) => t.length > 1 && !["and", "the", "with", "for", "please", "want", "like", "some", "a", "an", "of", "my", "me"].includes(t));
}
function scoreItem(item, tokens) {
const hay = `${item.name} ${item.desc} ${(item.tags || []).join(" ")}`.toLowerCase();
let score = 0;
tokens.forEach((token) => {
if (hay.includes(token)) score += token.length > 4 ? 3 : 2;
(item.tags || []).forEach((tag) => {
if (tag === token) score += 4;
else if (tag.startsWith(token) || token.startsWith(tag)) score += 2;
});
if (item.name.toLowerCase().includes(token)) score += 2;
});
return score;
}
function matchOrder(query) {
const menu = window.CornerCafeMenu || [];
const tokens = tokenize(query);
if (!tokens.length) return [];
const chunks = String(query)
.split(/,| and |&|\+| then | also /i)
.map((c) => c.trim())
.filter(Boolean);
const results = [];
const used = new Set();
const pushBest = (chunkTokens) => {
let best = null;
let bestScore = 0;
menu.forEach((item) => {
if (used.has(item.id)) return;
const s = scoreItem(item, chunkTokens);
if (s > bestScore) {
bestScore = s;
best = item;
}
});
if (best && bestScore >= 3) {
used.add(best.id);
results.push({ item: best, score: bestScore });
}
};
if (chunks.length > 1) {
chunks.forEach((chunk) => pushBest(tokenize(chunk)));
}
if (!results.length) {
menu
.map((item) => ({ item, score: scoreItem(item, tokens) }))
.filter((r) => r.score >= 3)
.sort((a, b) => b.score - a.score)
.slice(0, 8)
.forEach((r) => results.push(r));
}
return results;
}
function initKnowPanel() {
const form = document.querySelector("[data-know-form]");
const results = document.querySelector("[data-know-results]");
if (!form || !results) return;
const findItem = (id) => (window.CornerCafeMenu || []).find((m) => m.id === id);
const renderMatches = (matches) => {
results.hidden = false;
results.innerHTML = "";
const status = document.createElement("p");
status.className = "know-status";
if (!matches.length) {
status.textContent = "No clear match — try a dish name from the menu.";
results.appendChild(status);
return;
}
status.textContent = `${matches.length} match${matches.length === 1 ? "" : "es"}`;
results.appendChild(status);
const list = document.createElement("div");
list.className = "know-match-list";
matches.forEach(({ item }) => {
const row = document.createElement("div");
row.className = "know-match";
const qty = qtyOf(item.id);
row.innerHTML = `
${item.name}
${item.section}${item.desc ? ` · ${item.desc}` : ""} · ${priceText(item)}
${qtyControlsHtml(item, qty)}
`;
bindQtyControls(row, findItem);
list.appendChild(row);
});
results.appendChild(list);
};
form.addEventListener("submit", (event) => {
event.preventDefault();
const input = form.querySelector("[name=want]");
const query = (input?.value || "").trim();
renderMatches(matchOrder(query));
});
}
function orderReference() {
const cfg = window.SmOS_CC_Payments || {};
const prefix = cfg.referencePrefix || "CC";
let ref = "";
try {
ref = sessionStorage.getItem("corner-cafe-pay-ref") || "";
} catch (e) {
/* ignore */
}
if (!ref) {
const stamp = Date.now().toString(36).toUpperCase().slice(-6);
const rand = Math.floor(Math.random() * 90 + 10);
ref = `${prefix}-${stamp}${rand}`;
try {
sessionStorage.setItem("corner-cafe-pay-ref", ref);
} catch (e) {
/* ignore */
}
}
return ref;
}
function initCheckout() {
const rootEl = document.querySelector("[data-checkout]");
if (!rootEl) return;
const service = params.get("service") || "";
const mode = params.get("mode") || "menu";
if (!serviceLabels[service] || cartCount() <= 0) {
window.location.replace(serviceLabels[service] ? `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}` : "./");
return;
}
try {
sessionStorage.setItem(SERVICE_KEY, service);
sessionStorage.setItem(MODE_KEY, mode);
} catch (e) {
/* ignore */
}
const serviceEl = document.querySelector("[data-order-service]");
if (serviceEl) serviceEl.textContent = serviceLabels[service];
const back = document.querySelector("[data-back-order]");
if (back) back.href = `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`;
const addressField = document.querySelector("[data-field-address]");
if (addressField) {
addressField.hidden = service !== "delivery";
const ta = addressField.querySelector("textarea");
if (ta) ta.required = service === "delivery";
}
const linesEl = document.querySelector("[data-checkout-lines]");
const totalEl = document.querySelector("[data-checkout-total]");
const refEl = document.querySelector("[data-checkout-ref]");
const ref = orderReference();
if (refEl) refEl.textContent = ref;
if (totalEl) totalEl.textContent = money(cartTotal());
if (linesEl) {
linesEl.innerHTML = "";
cart.forEach((line) => {
const li = document.createElement("li");
li.className = "checkout-line";
li.innerHTML = `${line.qty}× ${line.name}${money(line.qty * line.price)}`;
linesEl.appendChild(li);
});
}
const cfg = window.SmOS_CC_Payments || { options: [] };
const optionsRoot = document.querySelector("[data-pay-options]");
const detailEl = document.querySelector("[data-pay-detail]");
const placeBtn = document.querySelector("[data-place-order]");
let selectedPay = null;
const available = (cfg.options || []).filter((opt) =>
(opt.availableFor || []).includes(service)
);
const bankBlock = (amount, payRef) => `
- Payee
- ${cfg.payeeName || cfg.businessName || "The Corner Cafe"}
- Sort code
- ${cfg.sortCode || "—"}
- Account number
- ${cfg.accountNumber || "—"}
- Amount
- ${money(amount)}
- Reference
- ${payRef}
Transfer the exact total and use this reference so the cafe can match your payment.
`;
const showDetail = (opt) => {
if (!detailEl) return;
detailEl.hidden = false;
if (opt.id === "bank-transfer") {
detailEl.innerHTML = `${opt.summary}
${bankBlock(cartTotal(), ref)}`;
} else {
detailEl.innerHTML = `${opt.summary}
`;
}
};
if (optionsRoot) {
optionsRoot.innerHTML = "";
available.forEach((opt, idx) => {
const label = document.createElement("label");
label.className = "pay-option";
label.innerHTML = `
${opt.name}
${opt.feeNote}
`;
const input = label.querySelector("input");
input.addEventListener("change", () => {
if (!input.checked) return;
selectedPay = opt;
showDetail(opt);
if (placeBtn) placeBtn.disabled = false;
});
optionsRoot.appendChild(label);
});
if (available[0]) {
selectedPay = available[0];
showDetail(available[0]);
if (placeBtn) placeBtn.disabled = false;
}
}
document.querySelector("[data-checkout-form]")?.addEventListener("submit", (event) => {
event.preventDefault();
if (!selectedPay || cartCount() <= 0) return;
const form = event.target;
const data = new FormData(form);
const order = {
ref,
service,
mode,
payment: selectedPay.id,
paymentName: selectedPay.name,
total: cartTotal(),
items: cart.slice(),
name: String(data.get("name") || "").trim(),
phone: String(data.get("phone") || "").trim(),
address: String(data.get("address") || "").trim(),
notes: String(data.get("notes") || "").trim(),
placedAt: new Date().toISOString(),
};
try {
const history = JSON.parse(sessionStorage.getItem("corner-cafe-orders") || "[]");
history.unshift(order);
sessionStorage.setItem("corner-cafe-orders", JSON.stringify(history.slice(0, 20)));
sessionStorage.removeItem("corner-cafe-pay-ref");
} catch (e) {
/* ignore */
}
clearCart();
rootEl.querySelectorAll("[data-checkout-hide-on-done]").forEach((el) => {
el.hidden = true;
});
const done = document.querySelector("[data-checkout-done]");
const lead = document.querySelector("[data-done-lead]");
const donePay = document.querySelector("[data-done-pay]");
if (done) done.hidden = false;
if (lead) {
lead.textContent = `Thanks ${order.name}. Order ${order.ref} · ${serviceLabels[service]} · ${money(order.total)} · ${selectedPay.name}.`;
}
if (donePay) {
if (selectedPay.id === "bank-transfer") {
donePay.innerHTML = `Complete your bank transfer now so the kitchen can prepare your order.
${bankBlock(order.total, order.ref)}`;
} else if (selectedPay.id === "cash-collection") {
donePay.innerHTML = `Bring ${money(order.total)} in cash when you collect. Quote reference ${order.ref}.
`;
} else {
donePay.innerHTML = `Pay ${money(order.total)} at the cafe. Quote reference ${order.ref}.
`;
}
}
const heroLead = document.querySelector(".app-hero__lead");
if (heroLead) heroLead.textContent = "Use the payment reference below when you pay.";
});
}
initCheckout();
})();