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:
+234
@@ -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 & 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 & 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 & 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>
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user