Build dylanmazzei.com redesign as a static multi-page site

Eight pages (home, about, portfolio, consulting, marketing, business setup,
contact, press) with copy taken verbatim from the content documents, a shared
layout, and a zero-dependency Node build ready for Vercel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Veras
2026-10-01 11:01:00 -04:00
commit ee8e0ab417
19 changed files with 2586 additions and 0 deletions
+234
View File
@@ -0,0 +1,234 @@
// Shared layout, icons and small HTML helpers for every page.
export const SITE = {
url: 'https://dylanmazzei.com',
email: 'dylan@dylanmazzei.com',
pressEmail: 'press@dylanmazzei.com',
linkedin: 'https://www.linkedin.com/in/dylanmazzei/',
instagram: 'https://www.instagram.com/caposins',
// Replace with the real scheduling link (Calendly, Cal.com, etc.).
calendar: 'mailto:dylan@dylanmazzei.com?subject=Scheduling%20a%20conversation',
// Pages that are not built yet. Their CTAs point here until they exist.
pending: '/contact/',
};
const ICONS = {
arrow: '<path d="M3 8h10M9 4l4 4-4 4" stroke-width="1.75"/>',
'arrow-ur': '<path d="M5 11 11 5M6 5h5v5" stroke-width="1.75"/>',
check: '<path d="m3.5 8.5 3 3 6-6.5" stroke-width="1.75"/>',
plus: '<path d="M8 3v10M3 8h10" stroke-width="1.75"/>',
chev: '<path d="m4 6 4 4 4-4" stroke-width="1.75"/>',
menu: '<path d="M2 5h14M2 9h14M2 13h14" stroke-width="1.75"/>',
close: '<path d="M4 4l10 10M14 4 4 14" stroke-width="1.75"/>',
brace: '<path d="M6 2.5H5a1.5 1.5 0 0 0-1.5 1.5v2.3c0 .8-.6 1.7-1.5 1.7.9 0 1.5.9 1.5 1.7V12A1.5 1.5 0 0 0 5 13.5h1M10 2.5h1A1.5 1.5 0 0 1 12.5 4v2.3c0 .8.6 1.7 1.5 1.7-.9 0-1.5.9-1.5 1.7V12a1.5 1.5 0 0 1-1.5 1.5h-1" stroke-width="1.5"/>',
search: '<circle cx="10.5" cy="10.5" r="6.5"/><path d="m20 20-4.8-4.8M10.5 7.5v6M7.5 10.5h6"/>',
chip: '<rect x="6" y="6" width="12" height="12" rx="2.5"/><path d="M9.5 9.5h5v5h-5zM9 3v3M15 3v3M9 18v3M15 18v3M3 9h3M3 15h3M18 9h3M18 15h3"/>',
bottle: '<path d="M10 2.5h4M10.5 2.5v4.2L8.6 9.4a3 3 0 0 0-.6 1.8V20a1.5 1.5 0 0 0 1.5 1.5h5A1.5 1.5 0 0 0 16 20v-8.8a3 3 0 0 0-.6-1.8l-1.9-2.7V2.5"/><path d="M8 13.5h8v4H8z"/>',
edu: '<path d="m2.5 9 9.5-4.5L21.5 9 12 13.5z"/><path d="M6.5 11v4.5c0 1.6 2.5 3 5.5 3s5.5-1.4 5.5-3V11M21.5 9v5"/>',
shield: '<path d="M12 2.8 19.5 6v5.4c0 4.6-3.1 8.2-7.5 9.8-4.4-1.6-7.5-5.2-7.5-9.8V6z"/><path d="m8.8 12 2.3 2.3 4.3-4.6"/>',
bag: '<path d="M4.5 8h15l-1.2 12.1a1.5 1.5 0 0 1-1.5 1.4H7.2a1.5 1.5 0 0 1-1.5-1.4z"/><path d="M8.5 10.5V7a3.5 3.5 0 0 1 7 0v3.5"/>',
umbrella: '<path d="M3 12a9 9 0 0 1 18 0z"/><path d="M12 12v6.5a2 2 0 0 1-4 0M12 3v-.5"/>',
megaphone: '<path d="M3.5 10v4a1 1 0 0 0 1 1H7l9 5V4L7 9H4.5a1 1 0 0 0-1 1z"/><path d="M7 15l1.5 5M19.5 9.5a3.5 3.5 0 0 1 0 5"/>',
heart: '<path d="M12 20.5s-8-4.6-8-10.7A4.3 4.3 0 0 1 12 7.4a4.3 4.3 0 0 1 8 2.4c0 6.1-8 10.7-8 10.7z"/><path d="M8.5 12h2l1-2 1.5 4 1-2h2"/>',
shirt: '<path d="M9 3.5 4 5.8 2.5 10.5l3.3 1.3V20.5h12.4v-8.7l3.3-1.3L20 5.8l-5-2.3c-.4 1.4-1.6 2.3-3 2.3s-2.6-.9-3-2.3z"/>',
building: '<path d="M4 21V5a1.5 1.5 0 0 1 1.5-1.5h8A1.5 1.5 0 0 1 15 5v16M15 9h3.5A1.5 1.5 0 0 1 20 10.5V21M2.5 21h19M7.5 7.5h4M7.5 11h4M7.5 14.5h4"/>',
gauge: '<path d="M4 17a8 8 0 1 1 16 0"/><path d="m12 17 4-5M4 17h2M18 17h2M12 9V7.5"/>',
truck: '<path d="M2.5 6.5h11v10h-11zM13.5 10h4l3 3.2v3.3h-7"/><circle cx="6.5" cy="17.5" r="1.8"/><circle cx="17" cy="17.5" r="1.8"/>',
compass: '<circle cx="12" cy="12" r="9"/><path d="m15.5 8.5-2 5-5 2 2-5z"/>',
target: '<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.2"/>',
users: '<circle cx="9" cy="8.5" r="3.5"/><path d="M2.5 20c.6-3.4 3.3-5.5 6.5-5.5s5.9 2.1 6.5 5.5"/><path d="M15.5 5.2a3.5 3.5 0 0 1 0 6.6M17.5 14.8c2.1.6 3.6 2.4 4 5.2"/>',
chart: '<path d="M3.5 20.5h17"/><path d="M6.5 16.5v-4M11 16.5V8M15.5 16.5v-6M20 16.5V5"/>',
layers: '<path d="m12 3 9 4.8-9 4.8-9-4.8z"/><path d="m3 12.2 9 4.8 9-4.8M3 16.4l9 4.8 9-4.8"/>',
mic: '<rect x="9" y="2.5" width="6" height="11" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5v4M8.5 21.5h7"/>',
rocket: '<path d="M12 15.5 8.5 12c1.5-4.6 4.6-8.3 11-8.5-.2 6.4-3.9 9.5-8.5 11z"/><path d="M8.5 12 5 11.5l2.5-3h4M12 15.5l.5 3.5 3-2.5v-4"/><path d="M6.5 16c-1.6.6-2.4 2.3-2.5 4 1.7-.1 3.4-.9 4-2.5"/>',
route: '<circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="6" r="2.5"/><path d="M8.5 18H16a3 3 0 0 0 0-6H8a3 3 0 0 1 0-6h7.5"/>',
mail: '<rect x="3" y="5" width="18" height="14" rx="2.5"/><path d="m3.5 6.5 8.5 6.5 8.5-6.5"/>',
calendar: '<rect x="3.5" y="5" width="17" height="15.5" rx="2.5"/><path d="M3.5 10h17M8 3v4M16 3v4"/>',
pin: '<path d="M12 21s7-6.2 7-11.5a7 7 0 0 0-14 0C5 14.8 12 21 12 21z"/><circle cx="12" cy="9.5" r="2.5"/>',
book: '<path d="M4 4.5A1.5 1.5 0 0 1 5.5 3H19v15H5.5A1.5 1.5 0 0 0 4 19.5z"/><path d="M4 19.5A1.5 1.5 0 0 0 5.5 21H19v-3"/>',
pen: '<path d="M4 20l1-4.5L15.5 5a2.1 2.1 0 0 1 3 3L8 18.5z"/><path d="m13.5 7 3 3"/>',
spark: '<path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M18.4 5.6l-2.8 2.8M8.4 15.6l-2.8 2.8"/>',
};
const SMALL = new Set(['arrow', 'arrow-ur', 'check', 'plus', 'chev', 'brace']);
export function icon(name, cls = '') {
const vb = SMALL.has(name) ? '0 0 16 16' : name === 'menu' || name === 'close' ? '0 0 18 18' : '0 0 24 24';
const sw = SMALL.has(name) || name === 'menu' || name === 'close' ? '' : ' stroke-width="1.75"';
return `<svg class="${cls}" viewBox="${vb}" fill="none" stroke="currentColor"${sw} stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICONS[name]}</svg>`;
}
export const mark = (cls = 'brand-mark') => `<svg class="${cls}" viewBox="0 0 34 34" aria-hidden="true"><rect width="34" height="34" rx="10" fill="#594ff4"/><path d="M8 9h7.2c4.4 0 7.3 3.1 7.3 7.6 0 1.5-.3 2.8-.9 4L16.9 25H8l1.8-3.2h5.6c2.6 0 4.1-2 4.1-4.9 0-2.6-1.5-4.6-4.3-4.6h-5.3z" fill="#fff"/><path d="M27 8.5 18.3 25.5h4.2L27 16.8z" fill="#fff" opacity=".55"/></svg>`;
export const eyebrow = (t) => `<span class="eyebrow">${t}</span>`;
export function btn(text, href, variant = 'primary', extra = '') {
const ext = /^https?:/.test(href) ? ' target="_blank" rel="noopener"' : '';
return `<a class="btn btn-${variant}${extra ? ' ' + extra : ''}" href="${href}"${ext}>${text} ${icon('arrow')}</a>`;
}
export function textLink(text, href) {
const ext = /^https?:/.test(href) ? ' target="_blank" rel="noopener"' : '';
return `<a class="text-link" href="${href}"${ext}>${text} ${icon('arrow')}</a>`;
}
export const paras = (arr, cls = '') => arr.map((t) => `<p${cls ? ` class="${cls}"` : ''}>${t}</p>`).join('');
export function faq(items) {
return `<div class="faq">${items
.map(
([q, a], i) => `<details${i === 0 ? ' open' : ''}><summary>${q}<span class="pm">${icon('plus')}</span></summary><div class="answer">${paras(a)}</div></details>`
)
.join('')}</div>`;
}
const NAV = [
{ label: 'Portfolio', href: '/portfolio/' },
{ label: 'About', href: '/about/' },
{
label: 'Consulting',
href: '/consulting/',
children: [
{ label: 'Consulting', desc: 'Find the Real Problem. Then Fix the Right One.', href: '/consulting/', icon: 'compass' },
{ label: 'Marketing & AI Search', desc: 'Get Found Where Attention Is Moving.', href: '/consulting/marketing/', icon: 'search' },
{ label: 'Business Setup', desc: 'Start With Fewer Expensive Mistakes.', href: '/consulting/business-setup/', icon: 'building' },
],
},
{ label: 'Press', href: '/press/' },
];
function nav(path) {
const isActive = (href) => (href === '/' ? path === '/' : path.startsWith(href));
const desktop = NAV.map((item) => {
if (!item.children) {
return `<li><a href="${item.href}"${isActive(item.href) ? ' aria-current="page"' : ''}>${item.label}</a></li>`;
}
return `<li class="has-drop${isActive(item.href) ? ' is-active' : ''}">
<button type="button" aria-haspopup="true">${item.label} ${icon('chev', 'chev')}</button>
<div class="dropdown">${item.children
.map((c) => `<a href="${c.href}"><span class="di">${icon(c.icon)}</span><span><strong>${c.label}</strong><small>${c.desc}</small></span></a>`)
.join('')}</div>
</li>`;
}).join('');
const mobile = NAV.map((item) => {
const top = `<li><a href="${item.href}">${item.label} ${icon('arrow', 'check')}</a></li>`;
if (!item.children) return top;
return top + item.children.slice(1).map((c) => `<li class="sub"><a href="${c.href}">${c.label}</a></li>`).join('');
}).join('');
return `<a class="skip" href="#main">Skip to content</a>
<header class="nav" id="nav">
<div class="wrap nav-inner">
<a class="brand" href="/" aria-label="Dylan Mazzei, home">${mark()}<span class="brand-word">Dylan Mazzei</span></a>
<nav aria-label="Primary"><ul class="nav-links">${desktop}</ul></nav>
<div class="nav-cta">
<a class="btn btn-primary btn-sm" href="/contact/">START A CONVERSATION</a>
<button class="menu-btn" id="menu-btn" type="button" aria-expanded="false" aria-controls="mobile-menu" aria-label="Open menu">${icon('menu')}</button>
</div>
</div>
<div class="wrap mobile-menu" id="mobile-menu" hidden>
<ul>${mobile}</ul>
<a class="btn btn-primary" href="/contact/">START A CONVERSATION ${icon('arrow')}</a>
</div>
</header>`;
}
function footer() {
return `<footer class="footer">
<div class="wrap">
<div class="foot-top">
<div class="foot-intro">
<a class="brand" href="/" aria-label="Dylan Mazzei, home">${mark()}<span class="brand-word">Dylan Mazzei</span></a>
<p>DYLAN MAZZEI | FOUNDER &amp; OPERATOR</p>
</div>
<div class="foot-col">
<h4>Explore</h4>
<ul>
<li><a href="/portfolio/">Portfolio</a></li>
<li><a href="/about/">About</a></li>
<li><a href="/press/">Press &amp; Media</a></li>
<li><a href="/contact/">Contact</a></li>
</ul>
</div>
<div class="foot-col">
<h4>Consulting</h4>
<ul>
<li><a href="/consulting/">Consulting</a></li>
<li><a href="/consulting/marketing/">Marketing &amp; AI Search</a></li>
<li><a href="/consulting/business-setup/">Business Setup</a></li>
</ul>
</div>
<div class="foot-col">
<h4>Direct Email</h4>
<ul>
<li><a href="mailto:${SITE.email}">${SITE.email}</a></li>
<li><a href="mailto:${SITE.pressEmail}">${SITE.pressEmail}</a></li>
<li><a href="${SITE.linkedin}" target="_blank" rel="noopener">LinkedIn</a></li>
<li><a href="${SITE.instagram}" target="_blank" rel="noopener">Instagram</a></li>
</ul>
</div>
</div>
<div class="foot-big">
<div class="stack-xs">
<span class="eyebrow">WHERE I WORK</span>
<p class="places">Texas<i>•</i>Florida<i>•</i>New York<i>•</i>Dubai</p>
</div>
<small>© ${new Date().getFullYear()} Dylan Mazzei</small>
</div>
</div>
<div class="foot-word" aria-hidden="true">DYLAN MAZZEI</div>
</footer>`;
}
export function page({ path, title, description, body, schema = [] }) {
const canonical = SITE.url + path;
const person = {
'@context': 'https://schema.org',
'@type': 'Person',
'@id': SITE.url + '/#dylan',
name: 'Dylan Mazzei',
url: SITE.url,
email: SITE.email,
image: SITE.url + '/assets/dylan-portrait.jpg',
jobTitle: 'Founder & Operator',
sameAs: [SITE.linkedin, SITE.instagram],
};
const ld = [person, ...schema].map((s) => `<script type="application/ld+json">${JSON.stringify(s)}</script>`).join('\n');
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>${title}</title>
<meta name="description" content="${description}">
<link rel="canonical" href="${canonical}">
<meta name="theme-color" content="#ffffff">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Dylan Mazzei">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
<meta property="og:url" content="${canonical}">
<meta property="og:image" content="${SITE.url}/assets/og.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
<meta name="twitter:image" content="${SITE.url}/assets/og.jpg">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@500;700&family=JetBrains+Mono:wght@500&display=swap">
<link rel="stylesheet" href="/assets/styles.css">
<script>document.documentElement.classList.add('js');setTimeout(function(){if(!window.__site)document.documentElement.classList.remove('js')},2500)</script>
${ld}
</head>
<body>
${nav(path)}
<main id="main">
${body}
</main>
${footer()}
<script src="/assets/site.js" defer></script>
</body>
</html>
`;
}