A reusable group-bookings and event-conversion landing page for restaurant and venue operators. One static page that turns "do you do private events?" into a structured inquiry, with packages, capacity, a gallery, and a smart inquiry form. Ships as plain HTML, CSS, and JavaScript. Deploys anywhere static. Zero build step.
Live demo: https://venue-page-builder.vercel.app
This repository is a clean-room demo built around a fictional venue, Northstar Demo Bistro in Demo City. Every name, number, photo, and contact is synthetic. Built from real venue landing-page work (clients not named).
Most restaurants and venues lose group-booking leads in a contact-form void or a slow email thread. This page is the opposite: a single scroll that answers the questions a host actually asks (How many fit? What does it cost in shape, not just price? When are you free? How do I lock it?) and ends in one structured inquiry that lands in your inbox.
It is intentionally a static site: fast, cheap to host, no backend to maintain, no database to breach. The only moving part is the inquiry form, which posts to a hosted form endpoint (Formspree by default).
- Hero. Full-viewport image with a scroll-scrubbed, word-by-word tagline reveal and a single clear call to action. Falls back gracefully when JavaScript or motion is unavailable.
- Gallery. A horizontal carousel of rooms and food that opens a full-screen lightbox on click.
- Packages. A four-tier responsive grid. The center tier carries a "MOST POPULAR" ribbon. Clicking any tier opens a modal with the full package detail, then offers to pre-select that package in the inquiry form.
- Capacity. A three-tab interface (Seated, Cocktail, Private Room) showing room-by-room numbers so a host can self-qualify before they ever write.
- Inquiry form. Date picker, guest-count slider, occasion and package selectors, quick-pick date chips, URL-parameter pre-fill, UTM passthrough, and an inline success and error state. An SLA note sets the reply expectation.
- Footer. Direct contact and a tasteful maker credit.
A sticky quick-facts bar appears after the hero scrolls away, and a language toggle switches the whole page between English and French (one language rendered at a time).
| Concern | Library | Loaded via |
|---|---|---|
| Hero scroll reveal | GSAP 3.12 + ScrollTrigger | CDN, deferred |
| Gallery carousel | Swiper 11 | CDN, deferred |
| Gallery lightbox | GLightbox | CDN, deferred |
| Date picker | Flatpickr | CDN, deferred |
| Everything else (tabs, modal, slider, language toggle, form submit, URL pre-fill) | Vanilla JavaScript (app.js) |
local |
No framework, no bundler. Below-the-fold vendor CSS is loaded asynchronously with a print-media swap so the hero paints fast.
- Semantic landmarks (
header,main,section,footer), labelled regions, alt text on every image, real form labels, a keyboard-accessible tab pattern (roving tabindex and arrow keys), and a focus-trapping modal. - Respects
prefers-reduced-motion. - Open Graph and Twitter card metadata, a canonical link, and Restaurant JSON-LD with demo venue data.
Rebranding for a real venue means swapping a known set of values, no code rewiring. The full map lives in CUSTOMIZE.md. The key ones:
- Venue identity: name, neighborhood, logo text, hero tagline lines.
- Brand colors: a handful of CSS variables in
:root(--brand,--accent,--cream, ...). - Packages: the four tier cards in
index.htmlplus the matchingPACKAGE_DETAILSobject inapp.js. - Capacity: the room numbers and descriptions in the three capacity panels.
- Imagery: the hero, gallery, and Open Graph image URLs.
- Contact: phone, email, and address in the footer and JSON-LD.
- Form endpoint: the Formspree action URL.
- Copy: the bilingual strings in the
I18Nobject inapp.js.
No build step. Serve the folder with any static server:
# Python
python3 -m http.server 8000
# or Node
npx serve .Then open http://localhost:8000.
- Create a free form at formspree.io and copy its endpoint, which looks like
https://formspree.io/f/<your-id>. - In
index.html, replace the formactionvaluehttps://formspree.io/f/YOUR_FORM_IDwith your endpoint. - Submit a test inquiry and confirm the email arrives. The page shows an inline success state on a successful post and an error state with a fallback email otherwise.
You can swap Formspree for any endpoint that accepts a POST of form data and returns JSON; the submit handler in app.js reads the response and is the only place to adjust.
This is a static site, so Vercel needs zero configuration.
- Push this repository to your Git host (GitHub, GitLab, or Bitbucket).
- In Vercel, choose Add New Project and import the repository.
- Framework preset: Other. Build command: none. Output directory: the repository root.
- Click Deploy. You get a live URL in under a minute.
- Optional: add a custom domain in the project settings.
A minimal vercel.json is included with sane caching headers, but it is not required; Vercel deploys the folder as-is without it.
The same folder deploys just as cleanly to Netlify, Cloudflare Pages, or GitHub Pages.
This repository is a public portfolio demo, so it carries two guardrails against leaking anything real:
- Local pre-commit hook. A blocklist hook runs before every commit and refuses content matching a list of private tokens. It lives in
.git/hooks/pre-commitand is not bypassed in normal use. - CI secret scan.
.github/workflows/secret-scan.ymlruns gitleaks on every push and pull request, with.gitleaks.tomlextending the default ruleset plus a rule that flags absolute local home-directory paths.
All content in this repo is synthetic demo data. No real venue, contact, key, or photo is committed.
MIT. See LICENSE.