From 75d8cbc7f78a04d89dfd0253868e6e503c0294c4 Mon Sep 17 00:00:00 2001 From: Alejandro Veras Date: Wed, 14 Oct 2026 13:43:35 -0400 Subject: [PATCH] Embed the GoHighLevel lead form and booking calendars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /contact/ now uses the GHL "Dylan – Lead Form", and a new /contact/book/ page lets visitors pick the free intro call or the paid 1:1 and book it in the matching GHL calendar. Both calendars load up front so switching tabs is instant. Co-Authored-By: Claude Opus 5.5 --- README.md | 4 ++-- build.mjs | 4 ++-- src/layout.mjs | 19 ++++++++++++--- src/pages/book.mjs | 52 ++++++++++++++++++++++++++++++++++++++++ src/pages/contact.mjs | 26 ++++---------------- static/assets/book.js | 21 ++++++++++++++++ static/assets/styles.css | 27 +++++++++++++++++++++ 7 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 src/pages/book.mjs create mode 100644 static/assets/book.js diff --git a/README.md b/README.md index 1879ed5..9220c41 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ build.mjs Genera dist/ + sitemap.xml + robots.txt + 404.html | `/consulting/education/` | `src/pages/education.mjs` | | `/consulting/fractional/` | `src/pages/fractional.mjs` | | `/contact/` | `src/pages/contact.mjs` | +| `/contact/book/` | `src/pages/book.mjs` | | `/press/` | `src/pages/press.mjs` | ## Desarrollo @@ -44,5 +45,4 @@ Importa el repo en Vercel. `vercel.json` ya define el build (`npm run build`), l - **Press:** falta el link del artículo de Gulf Insider y confirmar su titular exacto (`FEATURE` en `src/pages/press.mjs`). - **Korrith:** falta el cargo de Dylan (el tercer campo, vacío, en `src/pages/portfolio.mjs`). - **Páginas fuera del alcance actual:** Playbook y Poetry no forman parte del sitio. Sus secciones se muestran sin botón; si se crean esas páginas, se añade el botón con su texto original del documento. -- **Calendario:** poner el link real de agenda en `SITE.calendar`. -- **Formulario de contacto:** hoy abre el cliente de correo con el mensaje prellenado. Para recibirlo directo hay que conectar un servicio (Formspree, Resend, etc.). +- **Formulario y agenda (GoHighLevel):** los IDs están en `SITE.ghl` (`src/layout.mjs`). En GoHighLevel, el formulario "Dylan – Lead Form" debe redirigir a `https://dylanmazzei.com/contact/book/` al enviarse. diff --git a/build.mjs b/build.mjs index 815054a..6640221 100644 --- a/build.mjs +++ b/build.mjs @@ -7,7 +7,7 @@ import { page, eyebrow, btn } from './src/layout.mjs'; const root = dirname(fileURLToPath(import.meta.url)); const out = join(root, 'dist'); -const pages = ['home', 'about', 'portfolio', 'consulting', 'marketing', 'business-setup', 'business-optimization', 'supply-chain', 'cybersecurity', 'education', 'fractional', 'contact', 'press']; +const pages = ['home', 'about', 'portfolio', 'consulting', 'marketing', 'business-setup', 'business-optimization', 'supply-chain', 'cybersecurity', 'education', 'fractional', 'contact', 'book', 'press']; await rm(out, { recursive: true, force: true }); await mkdir(out, { recursive: true }); @@ -15,7 +15,7 @@ await cp(join(root, 'static'), out, { recursive: true }); // Fingerprint CSS/JS so a new deploy never reuses a stale cached copy. const hashOf = async (f) => createHash('sha256').update(await readFile(join(root, 'static', f))).digest('hex').slice(0, 10); -const assetVersions = Object.fromEntries(await Promise.all(['styles.css', 'site.js', 'home.css', 'home.js', 'fonts.css'].map(async (f) => ['/assets/' + f, await hashOf('assets/' + f)]))); +const assetVersions = Object.fromEntries(await Promise.all(['styles.css', 'site.js', 'home.css', 'home.js', 'fonts.css', 'book.js'].map(async (f) => ['/assets/' + f, await hashOf('assets/' + f)]))); const fingerprint = (html) => Object.entries(assetVersions).reduce((h, [u, v]) => h.replaceAll(`"${u}"`, `"${u}?v=${v}"`), html); const urls = []; diff --git a/src/layout.mjs b/src/layout.mjs index 076a9cf..bf354ad 100644 --- a/src/layout.mjs +++ b/src/layout.mjs @@ -7,7 +7,13 @@ export const SITE = { linkedin: 'https://www.linkedin.com/in/dylanmazzei/', instagram: 'https://www.instagram.com/thedylanmazzei', // Replace with the real scheduling link (Calendly, Cal.com, etc.). - calendar: 'mailto:dylan@dylanmazzei.com?subject=Scheduling%20a%20conversation', + calendar: '/contact/book/', + // GoHighLevel (LeadConnector) widgets. Contacts land in Dylan's GHL account. + ghl: { + form: 'xm10A7alZrxeKDtkMkD2', // "Dylan – Lead Form" + freeCalendar: '81JqpCRit6APOwavApzh', // Free 15-min intro call with Dylan's team + vipCalendar: 'lK9IYtCRGIuA7W93WjMF', // 15-min 1:1 with Dylan ($100) + }, }; const ICONS = { @@ -194,7 +200,7 @@ function footer() { `; } -export function page({ path, title, description, body, schema = [], head = '', scripts = '' }) { +export function page({ path, title, description, body, schema = [], head = '', scripts = '', noindex = false }) { const canonical = SITE.url + path; const person = { '@context': 'https://schema.org', @@ -215,7 +221,7 @@ export function page({ path, title, description, body, schema = [], head = '', s ${title} - +${noindex ? '\n' : ''} @@ -253,3 +259,10 @@ ${scripts} `; } + +// GoHighLevel embeds. form_embed.js resizes the iframes to their content. +export const GHL_SCRIPT = ''; +export const ghlForm = (id, name) => + ``; +export const ghlCalendar = (id, title) => + ``; diff --git a/src/pages/book.mjs b/src/pages/book.mjs new file mode 100644 index 0000000..e291f0d --- /dev/null +++ b/src/pages/book.mjs @@ -0,0 +1,52 @@ +import { icon, eyebrow, SITE, ghlCalendar, GHL_SCRIPT } from '../layout.mjs'; + +// Step 2 of the GoHighLevel booking flow. The lead form redirects here after submit. +// Copy mirrors the "Book Your Call" step Gabriela built in GoHighLevel. +const calls = [ + { id: 'free', tag: 'FREE', h: 'Free 15-min intro call with Dylan’s team', p: 'Discover how we can support your business goals and see if we are the right match for your journey.', cta: 'Book free call', cal: SITE.ghl.freeCalendar, icon: 'users' }, + { id: 'vip', tag: 'VIP', h: '15-min 1:1 with Dylan ($100)', p: 'Get direct strategic direction, tactical feedback on your bottlenecks, and bespoke 1:1 guidance.', cta: 'Book with Dylan', cal: SITE.ghl.vipCalendar, icon: 'spark', featured: true }, +]; + +export default { + path: '/contact/book/', + title: 'Book Your Call | Dylan Mazzei', + description: 'Book a free 15-minute intro call with Dylan Mazzei’s team or a 15-minute 1:1 session with Dylan.', + scripts: GHL_SCRIPT + '\n', + body: ` +
+
+
+ + ${eyebrow('BOOK YOUR CALL')} +

Thanks! Now choose your call.

+

Pick the option that fits you best.

+
+
+ ${calls + .map( + (c, i) => `` + ) + .join('')} +
+
+
+ +
+
+ ${calls + .map( + (c, i) => `
+
${c.h}
+
${ghlCalendar(c.cal, c.h)}
+
` + ) + .join('')} +
+
+`, +}; diff --git a/src/pages/contact.mjs b/src/pages/contact.mjs index aa829ee..962cd68 100644 --- a/src/pages/contact.mjs +++ b/src/pages/contact.mjs @@ -1,8 +1,9 @@ -import { icon, eyebrow, btn, SITE } from '../layout.mjs'; +import { icon, eyebrow, btn, SITE, ghlForm, GHL_SCRIPT } from '../layout.mjs'; export default { path: '/contact/', title: 'Contact Dylan Mazzei | Business & Partnerships', + scripts: GHL_SCRIPT, description: 'Contact Dylan Mazzei about business opportunities, strategic partnerships, consulting, media inquiries, or other conversations worth having.', body: `
@@ -28,31 +29,14 @@ export default {
-
+
${SITE.email}

Send an Inquiry

Give me enough context to understand what you’re working on, where things stand, and what you’d like to discuss.

-
-
-
-
-
- -
-
-
- -

- +
${ghlForm(SITE.ghl.form, 'Dylan – Lead Form')}
+

Rather Put It on the Calendar?

If you already know a conversation makes sense, skip the back-and-forth and find a time that works.

diff --git a/static/assets/book.js b/static/assets/book.js new file mode 100644 index 0000000..36e0bdc --- /dev/null +++ b/static/assets/book.js @@ -0,0 +1,21 @@ +(function () { + var tabs = document.querySelectorAll('[data-call]'); + var select = function (id, scroll) { + tabs.forEach(function (t) { + var on = t.getAttribute('data-call') === id; + t.setAttribute('aria-selected', String(on)); + var panel = document.getElementById('cal-' + t.getAttribute('data-call')); + if (panel) { if (on) panel.removeAttribute('data-off'); else panel.setAttribute('data-off', ''); } + }); + if (scroll) document.getElementById('schedule').scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + tabs.forEach(function (t) { + t.addEventListener('click', function () { + var id = t.getAttribute('data-call'); + select(id, true); + try { history.replaceState(null, '', '#' + id); } catch (e) {} + }); + }); + var hash = location.hash.replace('#', ''); + if (hash === 'free' || hash === 'vip') select(hash, false); +})(); diff --git a/static/assets/styles.css b/static/assets/styles.css index 8c280f7..ef775ee 100644 --- a/static/assets/styles.css +++ b/static/assets/styles.css @@ -1019,3 +1019,30 @@ h1, h2, h3, h4 { font-weight: 600; } @media (max-width: 900px) { .gallery { grid-template-columns: repeat(2, 1fr); } } .feature-press { position: relative; } .fp-link { position: absolute; inset: 0; z-index: 3; border-radius: inherit; } + +/* GoHighLevel embeds (lead form + booking calendars) */ +.ghl-wrap { border-radius: 14px; overflow: hidden; background: #ffffff; } +.ghl-frame { display: block; width: 100%; border: 0; background: transparent; } +.ghl-wrap-form .ghl-frame { min-height: 460px; } +.ghl-wrap-cal { padding: 8px; } +.ghl-wrap-cal .ghl-frame { min-height: 720px; } +.ghl-card { gap: 20px; } +.calls { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; max-width: 980px; margin: 56px auto 0; } +.call { + all: unset; box-sizing: border-box; cursor: pointer; display: flex; flex-direction: column; gap: 14px; + padding: 32px; border-radius: 28px; border: 1px solid var(--bd); + background: linear-gradient(160deg, var(--glass), transparent 120%), var(--cloud); color: var(--ink); + transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease; +} +.call:hover { transform: translateY(-2px); border-color: rgba(31, 183, 255, .45); } +.call:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } +.call[aria-selected="true"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 50px rgba(26, 127, 255, .18); } +.call-top { display: flex; justify-content: space-between; align-items: center; } +.call-top .ic { width: 44px; height: 44px; border-radius: 50%; } +.call-h { font-size: var(--text-heading); font-weight: 500; letter-spacing: -0.03em; line-height: 1.15; } +.call-p { color: var(--slate); } +.call .btn { align-self: flex-start; margin-top: 6px; pointer-events: none; } +.cal-panel { max-width: 980px; margin: 0 auto; border-radius: 24px; } +@media (max-width: 760px) { .calls { grid-template-columns: 1fr; } .call { padding: 24px; } } +#schedule .wrap { position: relative; } +.cal-panel[data-off] { position: absolute; left: -10000px; top: 0; width: min(980px, 100%); visibility: hidden; pointer-events: none; }