diff --git a/404.html b/404.html index cdf4c13..f7f0585 100644 --- a/404.html +++ b/404.html @@ -3,9 +3,30 @@ + + + 404 — EventBuzz Live + + + + + + + + + + + + + -

Never bolted on. Always built in.

+

Answered instantly, with what's true right now.

The concierge reads your live event data — no separate app, no second login, no lag between what changes and what attendees are told.

@@ -251,7 +286,7 @@
-
Built for event management
event management

Run the operation, not a stack of spreadsheets

-

From sales-optimised registration and ticketing to the industry's best on-site solutions, EventBuzz Live simplifies event planning, drives event ROI, and delivers flawless experiences.

+

From a branded registration page live in minutes, to instant QR check-in, to real-time reporting the moment your event ends, EventBuzz replaces the spreadsheets and disconnected tools with one system — so planning takes less time, not more coordination.

+ +
+
+ + + + + + +
+

A branded event page, fully customisable to your event's look — live in under 5 minutes.

+
+Event Website - EventBuzz Live +Agenda Builder and Speaker Profiles - EventBuzz Live +Registration and Ticketing - EventBuzz Live +Badge Printing and QR Check-in - EventBuzz Live +Real-time Analytics - EventBuzz Live +Event Marketing and Automation - EventBuzz Live
@@ -371,37 +445,37 @@
-Event page and registration
+Event page and registration - EventBuzz Live

Networking & matchmaking

Interest-based matches and 1-1 meeting scheduling, built on the same attendee data as check-in.

-Live polls & Q&A
+Live polls & Q&A - EventBuzz Live

Live polls & Q&A

Real-time audience input during sessions, feeding straight back into your session analytics.

-Gamification & leaderboards
+Gamification & leaderboards - EventBuzz Live

Gamification & leaderboards

Points, badges, and leaderboards that drive booth visits and session attendance, not just clicks.

-Community & social feed
+Community & social feed - EventBuzz Live

Community & social feed

A private feed for photos, updates, and discussion — scoped to your event, not the open web.

-Push & reminders
+Push & reminders - EventBuzz Live

Push & reminders

Session start alerts and schedule changes, delivered the moment they happen.

-Engagement analytics
+Engagement analytics - EventBuzz Live

Engagement analytics

See which sessions, booths, and content actually held attention — not just who checked in.

@@ -409,14 +483,14 @@
-Attendee engagement scores
+Attendee engagement scores - EventBuzz Live
Attendee engagement scores

Know exactly who was in the conversation — not just in the room.

Every attendee earns an engagement score based on polls answered, questions submitted, and session time — connected to who they are, and synced automatically to HubSpot or Salesforce.

-AI post-session summary
+AI post-session summary - EventBuzz Live
AI post-session summary

Your leadership report, written the moment your session ends.

EventBuzz generates a structured summary automatically — top questions, poll insights, sentiment arc, engagement highlights. Forward it in one click. No slides to build, no exports to stitch together.

@@ -461,7 +535,7 @@ } -

Real numbers. No sales call required to see them.

+

Transparent pricing. A quick call to get you set up.

AI concierge included from the Grow plan — not locked to the top tier
@@ -476,7 +550,7 @@
  • Email support
  • -Talk to sales +Talk to our team

    Grow

    @@ -493,7 +567,7 @@
  • Priority support
  • -Talk to sales +Talk to our team

    Scale

    @@ -510,7 +584,7 @@
  • Dedicated account manager
  • -Talk to sales +Talk to our team

    Enterprise

    @@ -524,7 +598,7 @@
  • SSO & custom DPA
  • -Talk to sales +Talk to our team
    @@ -567,7 +641,7 @@

    What event teams say after they switch

    -
    +
    -
    +
    “Our help desk queue basically disappeared. Attendees just ask the app now — and it actually knows the answer.”
    Event operations lead3-day medical congress · 1,200 attendees
    -
    +
    “We finally have one dashboard instead of three tools that never agreed with each other. Reporting after the event takes minutes, not days.”
    Head of eventsAnnual partner summit · 800 attendees
    -
    +
    “The AI session summary alone saved our team a full day of manual reporting per event. Leadership gets it the moment the session ends.”
    Program directorMulti-city training series
    -
    +
    “Engagement scores gave us real signal on who to follow up with — not just who scanned a badge.”
    Marketing leadB2B conference · 2,000 attendees
    -
    +
    “Room changes used to mean a flood of Slack messages. Now the concierge tells attendees before they even ask.”
    Onsite coordinatorPharma launch event · 900 attendees
    -
    +
    “We replaced two engagement apps and a registration tool. One login for the team, one experience for attendees.”
    VP EventsGlobal sales kickoff · 1,500 attendees
    -
    +
    +
    “QR check-in got 900 people through the door in under 20 minutes — attendance was updated live, no manual lookups, no chaos at the entrance.”
    +
    Event operations leadCorporate summit · 1,000 attendees
    +
    +
    “We projected the leaderboard after every quiz, and nobody wanted to miss the next round. Attendance held steady from the opening session to the closing keynote.”
    +
    Program directorMulti-track conference · 700 attendees
    +
    @@ -650,15 +730,18 @@
    +EventBuzz Logo in footer

    Where audiences come alive.

    Platform

    @@ -669,16 +752,16 @@
    © 2026 EventBuzz Live. Part of the Stratawiz family.Made for events that actually run on time.
    @@ -687,7 +770,7 @@ - - + + diff --git a/js/site.js b/js/site.js index 36bb887..b4346ae 100644 --- a/js/site.js +++ b/js/site.js @@ -15,13 +15,53 @@ const API_BASE = resolveApiBase(); + function getHashId(anchor) { + const raw = anchor.getAttribute("href") || ""; + if (!raw || raw === "#") return ""; + try { + const url = new URL(anchor.href, window.location.href); + if (!url.hash || url.hash === "#") return ""; + return decodeURIComponent(url.hash.slice(1)); + } catch { + return raw.charAt(0) === "#" ? raw.slice(1) : ""; + } + } + + let activeNavId = ""; + let navSpyLocked = false; + let navSpyUnlockTimer = 0; + + function setActiveNav(id) { + activeNavId = id || ""; + document + .querySelectorAll(".navlinks-desktop a, .nav-drawer-links a") + .forEach((a) => { + if (a.target === "_blank") return; + const linkId = getHashId(a); + if (!linkId) return; + const on = Boolean(activeNavId) && linkId === activeNavId; + a.classList.toggle("is-active", on); + if (on) a.setAttribute("aria-current", "location"); + else a.removeAttribute("aria-current"); + }); + } + + function lockActiveNav(id) { + navSpyLocked = true; + setActiveNav(id); + window.clearTimeout(navSpyUnlockTimer); + navSpyUnlockTimer = window.setTimeout(() => { + navSpyLocked = false; + }, 900); + } + /* ---------- Mobile nav drawer ---------- */ function initNav() { const nav = document.querySelector("nav"); const menuBtn = document.querySelector(".nav-menu-btn"); if (!nav || !menuBtn) return; - const mq = window.matchMedia("(max-width: 900px)"); + const mq = window.matchMedia("(max-width: 1024px)"); let drawerOpen = false; let drawerEls = null; @@ -91,6 +131,7 @@ function openDrawer() { const { overlay, aside } = ensureDrawer(); + setActiveNav(activeNavId); drawerOpen = true; menuBtn.setAttribute("aria-expanded", "true"); menuBtn.setAttribute("aria-label", "Close menu"); @@ -132,12 +173,171 @@ mq.addEventListener("change", syncDrawerMode); } + /* ---------- In-page section links (no #hash in the URL) ---------- */ + function initInPageNav() { + const reduceMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)" + ).matches; + const PENDING_KEY = "eb-scroll-to"; + + function normalizePath(path) { + return path.replace(/\/index\.html$/i, "/") || "/"; + } + + function scrollToId(id) { + const el = document.getElementById(id); + if (!el) return false; + lockActiveNav(id); + el.scrollIntoView({ + behavior: reduceMotion ? "auto" : "smooth", + block: "start", + }); + return true; + } + + function consumeHash() { + if (!window.location.hash || window.location.hash === "#") return ""; + const id = decodeURIComponent(window.location.hash.slice(1)); + history.replaceState( + null, + "", + window.location.pathname + window.location.search + ); + return id; + } + + let pending = ""; + try { + pending = sessionStorage.getItem(PENDING_KEY) || ""; + if (pending) sessionStorage.removeItem(PENDING_KEY); + } catch { + pending = ""; + } + + const fromUrl = consumeHash(); + const target = pending || fromUrl; + if (target) { + const go = () => scrollToId(target); + if (document.readyState === "complete") requestAnimationFrame(go); + else window.addEventListener("load", () => requestAnimationFrame(go), { once: true }); + } + + document.addEventListener("click", (e) => { + if ( + e.defaultPrevented || + e.button !== 0 || + e.metaKey || + e.ctrlKey || + e.shiftKey || + e.altKey + ) { + return; + } + + const a = e.target.closest("a[href]"); + if (!a || a.target === "_blank" || a.hasAttribute("download")) return; + if (a.hasAttribute("data-demo-open") || a.hasAttribute("data-talk-open")) { + return; + } + + const raw = a.getAttribute("href") || ""; + if (!raw || raw === "#") return; + + let url; + try { + url = new URL(a.href); + } catch { + return; + } + if (!url.hash || url.hash === "#") return; + + const id = decodeURIComponent(url.hash.slice(1)); + if (!id) return; + + if ( + url.origin === window.location.origin && + normalizePath(url.pathname) === normalizePath(window.location.pathname) + ) { + if (!document.getElementById(id)) return; + e.preventDefault(); + scrollToId(id); + return; + } + + if (url.origin !== window.location.origin) return; + try { + sessionStorage.setItem(PENDING_KEY, id); + } catch { + return; + } + e.preventDefault(); + window.location.assign(url.pathname + url.search); + }); + } + + /* ---------- Highlight nav link for the section in view ---------- */ + function initNavSpy() { + const links = Array.from( + document.querySelectorAll(".navlinks-desktop a") + ).filter((a) => a.target !== "_blank" && getHashId(a)); + const items = links + .map((a) => { + const id = getHashId(a); + const el = document.getElementById(id); + return el ? { id, el } : null; + }) + .filter(Boolean) + .sort((a, b) => { + const pos = a.el.compareDocumentPosition(b.el); + if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1; + if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1; + return 0; + }); + if (!items.length) return; + + function currentId() { + const nav = document.querySelector("nav"); + const marker = (nav ? nav.offsetHeight : 58) + 20; + let id = ""; + items.forEach((item) => { + if (item.el.getBoundingClientRect().top <= marker) id = item.id; + }); + return id; + } + + let ticking = false; + function update() { + ticking = false; + if (navSpyLocked) return; + setActiveNav(currentId()); + } + + function onScroll() { + if (ticking) return; + ticking = true; + requestAnimationFrame(update); + } + + window.addEventListener("scroll", onScroll, { passive: true }); + window.addEventListener("resize", onScroll); + update(); + } + /* ---------- Demo modal ---------- */ + const EVENT_TYPES = [ + "Webinar", + "Conference", + "Exhibition", + "Internal Sales Meet", + "Others", + ]; + function validateDemo(data) { const errors = {}; const name = (data.name || "").trim(); const mobile = (data.mobile || "").trim(); const email = (data.email || "").trim(); + const eventType = (data.eventType || "").trim(); const message = (data.message || "").trim(); const captchaAnswer = (data.captchaAnswer || "").trim(); @@ -153,6 +353,10 @@ else if (!EMAIL_RE.test(email)) { errors.email = "Enter a valid email address."; } + if (!eventType) errors.eventType = "Select an event type."; + else if (!EVENT_TYPES.includes(eventType)) { + errors.eventType = "Select a valid event type."; + } if (!message) errors.message = "Message is required."; if (!captchaAnswer) errors.captcha = "Please solve the captcha."; @@ -163,25 +367,25 @@ name, mobile: mobile.replace(/[\s\-().]/g, ""), email, + eventType, message, captchaAnswer, }, }; } - function initDemoModal() { - let overlay = null; - let open = false; - let success = false; + function bindDemoForm(root, options = {}) { + const idPrefix = options.idPrefix || "demo"; let captchaToken = ""; let captchaLoading = false; let submitting = false; + let success = false; async function loadCaptcha() { - if (!overlay) return; - const questionEl = overlay.querySelector("[data-demo-captcha-question]"); - const answerEl = overlay.querySelector('[name="captchaAnswer"]'); - const reloadBtn = overlay.querySelector("[data-demo-captcha-reload]"); + if (!root) return; + const questionEl = root.querySelector("[data-demo-captcha-question]"); + const answerEl = root.querySelector('[name="captchaAnswer"]'); + const reloadBtn = root.querySelector("[data-demo-captcha-reload]"); if (!questionEl) return; captchaLoading = true; @@ -210,8 +414,13 @@ function setFieldError(form, key, message) { const inputName = key === "captcha" ? "captchaAnswer" : key; - const errId = key === "captchaAnswer" || key === "captcha" ? "demo-captcha-err" : `demo-${key}-err`; - const field = form.querySelector(`[name="${inputName}"]`)?.closest(".demo-field"); + const errId = + key === "captchaAnswer" || key === "captcha" + ? `${idPrefix}-captcha-err` + : `${idPrefix}-${key}-err`; + const field = form + .querySelector(`[name="${inputName}"]`) + ?.closest(".demo-field"); const errEl = document.getElementById(errId); const input = form.querySelector(`[name="${inputName}"]`); if (message) { @@ -231,6 +440,169 @@ } } + function showSuccess() { + success = true; + const formView = root.querySelector(".demo-modal-form-view"); + const successView = root.querySelector(".demo-modal-success"); + if (formView) formView.hidden = true; + if (successView) successView.hidden = false; + } + + function reset() { + success = false; + captchaToken = ""; + const form = root.querySelector(".demo-modal-form"); + form?.reset(); + root.querySelectorAll(".demo-field-error").forEach((el) => { + el.hidden = true; + el.textContent = ""; + }); + root.querySelectorAll(".demo-field").forEach((el) => { + el.classList.remove("has-error"); + }); + const formErr = root.querySelector(".demo-form-error"); + if (formErr) { + formErr.hidden = true; + formErr.textContent = ""; + } + const formView = root.querySelector(".demo-modal-form-view"); + const successView = root.querySelector(".demo-modal-success"); + if (formView) formView.hidden = false; + if (successView) successView.hidden = true; + void loadCaptcha(); + } + + const form = root.querySelector(".demo-modal-form"); + if (!form) { + return { loadCaptcha, reset, isSuccess: () => success }; + } + + root + .querySelector("[data-demo-captcha-reload]") + ?.addEventListener("click", () => { + void loadCaptcha(); + }); + root.querySelector("[data-demo-reset]")?.addEventListener("click", reset); + + form.addEventListener("submit", async (e) => { + e.preventDefault(); + if (submitting || captchaLoading) return; + + const fd = new FormData(form); + const result = validateDemo({ + name: fd.get("name"), + mobile: fd.get("mobile"), + email: fd.get("email"), + eventType: fd.get("eventType"), + message: fd.get("message"), + captchaAnswer: fd.get("captchaAnswer"), + }); + + ["name", "mobile", "email", "eventType", "message", "captchaAnswer"].forEach( + (key) => { + const errKey = key === "captchaAnswer" ? "captcha" : key; + setFieldError(form, key, result.errors[errKey] || ""); + } + ); + + const formErr = root.querySelector(".demo-form-error"); + if (formErr) { + formErr.hidden = true; + formErr.textContent = ""; + } + + if (!result.ok) { + const first = + ["name", "mobile", "email", "eventType", "message", "captcha"].find( + (k) => result.errors[k] + ) || "name"; + const focusName = first === "captcha" ? "captchaAnswer" : first; + form.querySelector(`[name="${focusName}"]`)?.focus(); + return; + } + + if (!captchaToken) { + setFieldError( + form, + "captchaAnswer", + "Captcha is not ready. Please reload and try again." + ); + void loadCaptcha(); + return; + } + + const submitBtn = root.querySelector(".demo-submit-btn"); + submitting = true; + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.textContent = "Sending…"; + } + + try { + const res = await fetch(`${API_BASE}/public/demo-request`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: result.data.name, + mobile: result.data.mobile, + email: result.data.email, + eventType: result.data.eventType, + message: result.data.message, + captchaToken, + captchaAnswer: result.data.captchaAnswer, + }), + }); + let body = null; + try { + body = await res.json(); + } catch { + body = null; + } + + if (!res.ok || !body?.success) { + const msg = + body?.error?.message || + "Could not send your request. Please try again."; + if (/captcha/i.test(msg)) { + setFieldError(form, "captchaAnswer", msg); + void loadCaptcha(); + } else if (formErr) { + formErr.hidden = false; + formErr.textContent = msg; + } + return; + } + + showSuccess(); + } catch { + if (formErr) { + formErr.hidden = false; + formErr.textContent = + "Could not reach the server. Please try again in a moment."; + } + } finally { + submitting = false; + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.textContent = "Request demo"; + } + } + }); + + return { loadCaptcha, reset, isSuccess: () => success }; + } + + function initDemoPage() { + const root = document.querySelector("[data-demo-page]"); + if (!root) return; + bindDemoForm(root, { idPrefix: "help" }).reset(); + } + + function initDemoModal() { + let overlay = null; + let open = false; + let formApi = null; + function build() { if (overlay) return overlay; @@ -243,7 +615,7 @@

    Request a demo

    -

    Tell us a bit about your event and we’ll get back to you.

    +

    Tell us a bit about your event and we'll get back to you.

    @@ -261,6 +633,18 @@
    +
    + Event type + + +
    Message @@ -290,8 +674,8 @@