/* ============================================================
BARESPACE TEMPLATE ENGINE
Pours a config object into the page. Powers BOTH live customer
sites (window.BARESPACE_CONFIG) and the portal's live preview
(config received via postMessage). Don't edit per-customer.
============================================================ */
/* Apply a prebuilt theme: fonts, palette, hero style, corner shape */
function applyTheme(themeId) {
const themes = window.BARESPACE_THEMES || [];
const t = themes.find((x) => x.id === themeId) || themes[0];
if (!t) return null;
let link = document.getElementById("theme-fonts");
if (!link) { link = document.createElement("link"); link.id = "theme-fonts"; link.rel = "stylesheet"; document.head.appendChild(link); }
if (link.getAttribute("href") !== t.fontsUrl) link.setAttribute("href", t.fontsUrl);
const r = document.documentElement.style;
r.setProperty("--serif", t.heading);
r.setProperty("--sans", t.body);
r.setProperty("--accent", t.accent);
r.setProperty("--ink", t.ink);
r.setProperty("--warm", t.warm);
r.setProperty("--radius", t.radius);
/* surface tokens — let a theme go fully dark, default to light */
r.setProperty("--bg", t.bg || "#ffffff");
r.setProperty("--soft", t.soft || `color-mix(in srgb, ${t.accent} 7%, #ffffff)`);
r.setProperty("--card", t.card || "#ffffff");
r.setProperty("--muted", t.muted || "#6f6a66");
r.setProperty("--line", t.line || "#ece7e1");
document.body.classList.remove("hero-image", "hero-solid", "hero-light", "caps");
document.body.classList.add("hero-" + (t.hero || "image"));
if (t.caps) document.body.classList.add("caps");
return t;
}
var SVC_LIMIT = 8; // services shown before "View all" collapse
/* Prices on the service menu always read "from £X" */
function priceLabel(p) {
p = (p == null ? "" : String(p)).trim();
if (!p || /^free$/i.test(p) || /^from\b/i.test(p)) return p;
return "from " + p;
}
/* Load Instagram's embed script once, then (re)process blockquotes */
function loadInstagramEmbed() {
if (window.instgrm && window.instgrm.Embeds) { window.instgrm.Embeds.process(); return; }
if (!document.getElementById("ig-embed-js")) {
const s = document.createElement("script");
s.id = "ig-embed-js"; s.async = true; s.src = "https://www.instagram.com/embed.js";
document.body.appendChild(s);
}
}
/* Placeholder imagery — used whenever the customer hasn't supplied photos, so a
site is NEVER shown with empty or broken image areas. Real professional stock. */
var BARESPACE_STOCK = {
beauty: [
"https://images.unsplash.com/photo-1560066984-138dadb4c035?w=800&q=80",
"https://images.unsplash.com/photo-1522335789203-aabd1fc54bc9?w=800&q=80",
"https://images.unsplash.com/photo-1487412947147-5cebf100ffc2?w=800&q=80",
"https://images.unsplash.com/photo-1633681926035-ec1ac984418a?w=800&q=80",
],
barber: [
"https://images.unsplash.com/photo-1503951914875-452162b0f3f1?w=800&q=80",
"https://images.unsplash.com/photo-1599351431202-1e0f0137899a?w=800&q=80",
"https://images.unsplash.com/photo-1517832606299-7ae9b720a186?w=800&q=80",
"https://images.unsplash.com/photo-1521590832167-7bcbfaa6381f?w=800&q=80",
],
};
var BARESPACE_HERO = {
beauty: "https://images.unsplash.com/photo-1521590832167-7bcbfaa6381f?w=1600&q=80",
barber: "https://images.unsplash.com/photo-1503951914875-452162b0f3f1?w=1600&q=80",
};
function isBarber(c) { return c && (c.theme === "bold-barber" || c.schemaType === "BarberShop"); }
function fallbackGallery(c) { return isBarber(c) ? BARESPACE_STOCK.barber : BARESPACE_STOCK.beauty; }
function fallbackHero(c) { return isBarber(c) ? BARESPACE_HERO.barber : BARESPACE_HERO.beauty; }
var STOCK_ONE = BARESPACE_STOCK.beauty[0];
var TEAM_FALLBACK = [
"https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=500&q=80",
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=500&q=80",
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=500&q=80",
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&q=80",
];
function renderBarespace(c, opts) {
c = c || {};
opts = opts || {};
const bind = (name) => document.querySelectorAll(`[data-bind="${name}"]`);
const setText = (name, value) => bind(name).forEach((el) => (el.textContent = value || ""));
/* Theme first; then the salon's brand colour can override the accent */
const theme = applyTheme(c.theme);
if (c.brandColor) document.documentElement.style.setProperty("--accent", c.brandColor);
/* Text */
setText("businessName", c.businessName);
setText("tagline", c.tagline);
setText("about", c.about);
setText("phone", c.phone);
setText("email", c.email);
setText("address", c.address);
/* Hero background photo — with focal point + zoom */
const hero = document.getElementById("hero");
if (hero) {
const heroSrc = c.heroImage || fallbackHero(c); // always a photo behind the hero
hero.style.backgroundImage = heroSrc ? `url("${heroSrc}")` : "";
hero.style.backgroundPosition = c.heroPosition || "center";
hero.style.backgroundSize = c.heroZoom && +c.heroZoom !== 100 ? +c.heroZoom + "%" : "cover";
}
setText("heroLocation", c.heroLocation || (c.address || "").toUpperCase());
setText("heroHeadline", c.heroHeadline || c.businessName);
setText("heroAccent", c.heroAccent || "");
bind("heroRating").forEach((el) => {
el.innerHTML = c.googleRating
? `★★★★★ ${c.googleRating} on Google${c.googleReviewCount ? " · " + c.googleReviewCount + " reviews" : ""}`
: "";
});
/* Logo */
bind("logo").forEach((el) => {
el.innerHTML = c.logoImage
? ``
: (c.logoText || c.businessName || "");
});
/* Booking buttons */
bind("bookLink").forEach((el) => { if (c.bookingUrl) el.setAttribute("href", c.bookingUrl); });
/* Services */
bind("services").forEach((list) => {
const items = c.services || [];
list.innerHTML = items.map((s, i) => `
${m.role || ""}
${r.text ? '"' + String(r.text).slice(0, 280) + '"' : ""}
View on Instagram` ).join(""); if ((c.instagramPosts || []).length) loadInstagramEmbed(); }); /* Section visibility — never show an empty box */ const showHide = (sectionName, visible) => { const s = bind(sectionName)[0]; if (s) s.style.display = visible ? "" : "none"; }; bind("instagramWidget").forEach((slot) => (slot.innerHTML = c.instagramWidgetEmbed || "")); bind("reviewsWidget").forEach((slot) => (slot.innerHTML = c.googleReviewsWidgetEmbed || "")); showHide("teamSection", !!(c.team && c.team.length)); showHide("instagramSection", !!((c.instagramPosts && c.instagramPosts.length) || c.instagramWidgetEmbed)); const hasReviews = c.googleRating || (c.reviews && c.reviews.length) || c.googleReviewsWidgetEmbed; showHide("reviewsSection", c.showReviews !== false && !!hasReviews); // opt-out respected /* Reveal-on-scroll: in preview, just show everything */ if (opts.preview) document.querySelectorAll(".reveal").forEach((el) => el.classList.add("in")); } /* Marquee speed reacts to scroll velocity (speeds up / reverses as you scroll) */ function setupMarquee() { const tracks = [...document.querySelectorAll(".marquee-track")]; if (!tracks.length) return; let last = window.scrollY, vel = 0; window.addEventListener("scroll", () => { const y = window.scrollY; vel += y - last; last = y; }, { passive: true }); const state = tracks.map((t) => { t.style.animation = "none"; return { t, x: 0, w: (t.scrollWidth / 2) || 1 }; }); (function frame() { vel *= 0.9; state.forEach((s) => { s.x -= 0.6 + vel * 0.6; if (s.x <= -s.w) s.x += s.w; else if (s.x > 0) s.x -= s.w; s.t.style.transform = "translateX(" + s.x + "px)"; }); requestAnimationFrame(frame); })(); } /* A custom cursor that grows over interactive elements (fine-pointer devices only) */ function setupCursor() { const c = document.createElement("div"); c.id = "bs-cursor"; document.body.appendChild(c); document.body.classList.add("has-cursor"); let x = innerWidth / 2, y = innerHeight / 2, cx = x, cy = y; window.addEventListener("mousemove", (e) => { x = e.clientX; y = e.clientY; }, { passive: true }); (function loop() { cx += (x - cx) * 0.2; cy += (y - cy) * 0.2; c.style.transform = "translate(" + cx + "px," + cy + "px) translate(-50%,-50%)"; requestAnimationFrame(loop); })(); const sel = "a,button,.gal,.svc,.svc-toggle"; document.addEventListener("mouseover", (e) => { if (e.target.closest(sel)) c.classList.add("hover"); }); document.addEventListener("mouseout", (e) => { if (e.target.closest(sel)) c.classList.remove("hover"); }); } /* Per-section parallax — headings drift against the scroll */ function setupSectionParallax() { const els = [...document.querySelectorAll(".section-head h2, .section-head .section-eyebrow")]; if (!els.length) return; const upd = () => { const vh = window.innerHeight; els.forEach((el) => { const r = el.getBoundingClientRect(); const off = (r.top + r.height / 2 - vh / 2) / vh; el.style.transform = "translateY(" + (off * -26) + "px)"; }); }; window.addEventListener("scroll", upd, { passive: true }); upd(); } /* Scroll behaviours for the live site */ function setupScrollEffects() { const nav = document.getElementById("nav"); const onScroll = () => { if (nav) nav.classList.toggle("scrolled", window.scrollY > 40); }; window.addEventListener("scroll", onScroll); onScroll(); /* Hero parallax — the photo drifts slower than the scroll (skipped if reduced-motion) */ const hero = document.getElementById("hero"); const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (hero && !reduce) { window.addEventListener("scroll", () => { const y = window.scrollY; if (y < window.innerHeight) hero.style.backgroundPositionY = (50 + y * 0.04) + "%"; }, { passive: true }); } if (!reduce) { setupMarquee(); setupSectionParallax(); } if (new URLSearchParams(location.search).get("reveal") === "all") { document.querySelectorAll(".reveal").forEach((el) => el.classList.add("in")); const h = document.getElementById("hero"); if (h) h.style.minHeight = "600px"; // capture aid return; } if ("IntersectionObserver" in window) { const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12 }); document.querySelectorAll(".reveal").forEach((el) => io.observe(el)); } else { document.querySelectorAll(".reveal").forEach((el) => el.classList.add("in")); } } /* 1. Live site */ document.addEventListener("DOMContentLoaded", () => { renderBarespace(window.BARESPACE_CONFIG); setupScrollEffects(); }); /* 2. Portal live preview (+ click-to-edit mode) */ function parentEdit(p) { try { parent.postMessage(Object.assign({ type: "bs-edit" }, p), "*"); } catch (e) {} } function enableEditMode() { document.body.classList.add("bs-edit"); const wire = (el, onSave, multiline) => { if (!el || el.dataset.wired) return; el.dataset.wired = "1"; el.setAttribute("contenteditable", "true"); el.classList.add("bs-editable"); el.addEventListener("blur", () => onSave(el.textContent.trim())); el.addEventListener("keydown", (ev) => { if (ev.key === "Enter" && !multiline) { ev.preventDefault(); el.blur(); } }); }; ["heroHeadline", "heroAccent", "tagline", "heroLocation", "about", "phone", "email", "address"].forEach((f) => { document.querySelectorAll(`[data-bind="${f}"]`).forEach((el) => wire(el, (v) => parentEdit({ field: f, value: v }), f === "about")); }); document.querySelectorAll(".svc").forEach((row) => { const i = +row.dataset.i; wire(row.querySelector(".svc-n"), (v) => parentEdit({ field: "service", index: i, key: "name", value: v })); wire(row.querySelector(".svc-price"), (v) => parentEdit({ field: "service", index: i, key: "price", value: v })); }); document.querySelectorAll(".team-card").forEach((card) => { const i = +card.dataset.i; wire(card.querySelector("h3"), (v) => parentEdit({ field: "team", index: i, key: "name", value: v })); wire(card.querySelector("p"), (v) => parentEdit({ field: "team", index: i, key: "role", value: v })); }); } window.addEventListener("message", (e) => { if (e.data && e.data.type === "barespace-config") { renderBarespace(e.data.config, { preview: true }); if (e.data.edit) enableEditMode(); } });