2d37e3fcce
Recreate the DM monogram and wordmark as SVG and type so they stay sharp in both themes, move the accent from violet to the logo's blue, and add a small theme toggle in the nav that follows the system until the visitor picks one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
241 lines
13 KiB
JavaScript
241 lines
13 KiB
JavaScript
// 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',
|
|
};
|
|
|
|
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"/>',
|
|
moon: '<path d="M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z"/>',
|
|
sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2.5v2M12 19.5v2M2.5 12h2M19.5 12h2M5.3 5.3l1.4 1.4M17.3 17.3l1.4 1.4M18.7 5.3l-1.4 1.4M6.7 17.3l-1.4 1.4"/>',
|
|
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>`;
|
|
}
|
|
|
|
// Dylan Mazzei monogram: italic "D" (inherits text color) and the sky-blue blade.
|
|
export const mark = (cls = 'logo-mark') => `<svg class="${cls}" viewBox="0 0 104 60" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M9 4h55c10 0 13 7 9 14L58 46c-3 6-8 8-15 8.5L3 57zM21.5 15.5 18.3 45.8 40 44.5c4-.3 6-1.5 7.5-4.3l12-21.4c1.2-2.2.3-3.3-2-3.3z"/><path fill="#33b9ff" d="M24 59 102 1 91 52 72 54.5 79 33z"/><path fill="#8fdcff" d="M24 59 102 1 79 33z"/></svg>`;
|
|
|
|
export const logo = () => `${mark()}<span class="brand-word">Dylan <b>Mazzei</b></span>`;
|
|
|
|
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">${logo()}</a>
|
|
<nav aria-label="Primary"><ul class="nav-links">${desktop}</ul></nav>
|
|
<div class="nav-cta">
|
|
<button class="theme-btn" id="theme-btn" type="button" aria-label="Switch to dark mode">${icon('moon', 'moon')}${icon('sun', 'sun')}</button>
|
|
<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">${logo()}</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" media="(prefers-color-scheme: light)">
|
|
<meta name="theme-color" content="#0b0b0d" media="(prefers-color-scheme: dark)">
|
|
<meta name="color-scheme" content="light dark">
|
|
<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&family=Unbounded:wght@700&display=swap">
|
|
<link rel="stylesheet" href="/assets/styles.css">
|
|
<script>(function(){var r=document.documentElement;try{var t=localStorage.getItem('theme');if(t==='dark'||t==='light')r.setAttribute('data-theme',t)}catch(e){}if(!r.hasAttribute('data-theme')&&window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches)r.setAttribute('data-theme','dark')})();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>
|
|
`;
|
|
}
|