Initial commit: site + GA4 + Docker for Coolify
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.DS_Store
|
||||
.vercel
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN node build.mjs
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,48 @@
|
||||
# dylanmazzei.com
|
||||
|
||||
Sitio de Dylan Mazzei, Founder & Operator. HTML estático generado con un script de Node sin dependencias.
|
||||
|
||||
## Estructura
|
||||
|
||||
```
|
||||
src/layout.mjs Layout compartido: <head>, nav, footer, iconos y helpers
|
||||
src/pages/*.mjs Una página por archivo (texto literal del documento de contenido)
|
||||
src/service.mjs Bloques compartidos de las páginas de servicios de Consulting
|
||||
static/ Se copia tal cual a dist/ (CSS, JS, imágenes, favicon)
|
||||
build.mjs Genera dist/ + sitemap.xml + robots.txt + 404.html
|
||||
```
|
||||
|
||||
| Ruta | Archivo |
|
||||
|------|---------|
|
||||
| `/` | `src/pages/home.mjs` |
|
||||
| `/about/` | `src/pages/about.mjs` |
|
||||
| `/portfolio/` | `src/pages/portfolio.mjs` |
|
||||
| `/consulting/` | `src/pages/consulting.mjs` |
|
||||
| `/consulting/marketing/` | `src/pages/marketing.mjs` |
|
||||
| `/consulting/business-setup/` | `src/pages/business-setup.mjs` |
|
||||
| `/consulting/business-optimization/` | `src/pages/business-optimization.mjs` |
|
||||
| `/consulting/supply-chain/` | `src/pages/supply-chain.mjs` |
|
||||
| `/consulting/cybersecurity/` | `src/pages/cybersecurity.mjs` |
|
||||
| `/consulting/education/` | `src/pages/education.mjs` |
|
||||
| `/consulting/fractional/` | `src/pages/fractional.mjs` |
|
||||
| `/contact/` | `src/pages/contact.mjs` |
|
||||
| `/press/` | `src/pages/press.mjs` |
|
||||
|
||||
## Desarrollo
|
||||
|
||||
```bash
|
||||
npm run build # genera dist/
|
||||
npm run dev # build + servidor local
|
||||
```
|
||||
|
||||
## Deploy en Vercel
|
||||
|
||||
Importa el repo en Vercel. `vercel.json` ya define el build (`npm run build`), la salida (`dist`) y las URLs limpias con slash final. No hace falta configurar nada más.
|
||||
|
||||
## Pendientes de contenido
|
||||
|
||||
- **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.).
|
||||
@@ -0,0 +1,45 @@
|
||||
// Builds the static site into ./dist. No dependencies: `node build.mjs`.
|
||||
import { mkdir, rm, writeFile, cp, readFile } from 'node:fs/promises';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
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'];
|
||||
|
||||
await rm(out, { recursive: true, force: true });
|
||||
await mkdir(out, { recursive: true });
|
||||
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 fingerprint = (html) => Object.entries(assetVersions).reduce((h, [u, v]) => h.replaceAll(`"${u}"`, `"${u}?v=${v}"`), html);
|
||||
|
||||
const urls = [];
|
||||
for (const name of pages) {
|
||||
const def = (await import(`./src/pages/${name}.mjs`)).default;
|
||||
const file = join(out, def.path, 'index.html');
|
||||
await mkdir(dirname(file), { recursive: true });
|
||||
await writeFile(file, fingerprint(page(def)));
|
||||
urls.push(def.path);
|
||||
console.log('built', def.path);
|
||||
}
|
||||
|
||||
await writeFile(join(out, '404.html'), fingerprint(page({
|
||||
path: '/404/',
|
||||
title: 'Page Not Found | Dylan Mazzei',
|
||||
description: 'This page could not be found.',
|
||||
body: `<section class="page-hero"><div class="wrap-narrow stack center" style="align-items:center;text-align:center">${eyebrow('404')}<h1 class="h-display-lg">Page Not Found</h1><div class="btn-row">${btn('BACK TO HOME', '/')}</div></div></section>`,
|
||||
})));
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
await writeFile(join(out, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls.map((u) => ` <url><loc>https://dylanmazzei.com${u}</loc><lastmod>${today}</lastmod></url>`).join('\n')}
|
||||
</urlset>
|
||||
`);
|
||||
await writeFile(join(out, 'robots.txt'), 'User-agent: *\nAllow: /\nSitemap: https://dylanmazzei.com/sitemap.xml\n');
|
||||
console.log('done →', out);
|
||||
@@ -0,0 +1,17 @@
|
||||
server {
|
||||
listen 80;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
gzip on;
|
||||
gzip_types text/css application/javascript image/svg+xml application/xml;
|
||||
|
||||
location /assets/ {
|
||||
add_header Cache-Control "public, max-age=604800, stale-while-revalidate=86400";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/index.html =404;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "dylanmazzei-website",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "dylanmazzei.com — static site built with a zero-dependency Node script.",
|
||||
"scripts": {
|
||||
"build": "node build.mjs",
|
||||
"dev": "node build.mjs && npx --yes serve dist"
|
||||
},
|
||||
"engines": { "node": ">=18" }
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
// Shared layout, icons and small HTML helpers for every page.
|
||||
|
||||
export const SITE = {
|
||||
url: 'https://dylanmazzei.com',
|
||||
gaId: 'G-P9H24L6WM5',
|
||||
email: 'dylan@dylanmazzei.com',
|
||||
pressEmail: 'press@dylanmazzei.com',
|
||||
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',
|
||||
};
|
||||
|
||||
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 = () => `<img class="logo-img" src="/assets/o2/logo.png" alt="" width="640" height="121">${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}<span class="go">${icon('arrow')}</span></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: 'Business Optimization', desc: 'Find What Growth Made Messy.', href: '/consulting/business-optimization/', icon: 'gauge' },
|
||||
{ label: 'Supply Chain', desc: 'Know What Happens Before It Reaches the Customer.', href: '/consulting/supply-chain/', icon: 'truck' },
|
||||
{ label: 'Cybersecurity', desc: 'Find the Exposure Before Someone Else Does.', href: '/consulting/cybersecurity/', icon: 'shield' },
|
||||
{ label: 'Education', desc: 'Make the Strategy Survive the Classroom.', href: '/consulting/education/', icon: 'edu' },
|
||||
{ label: 'Fractional Leadership', desc: 'When the Business Needs an Owner, Not Another Recommendation.', href: '/consulting/fractional/', icon: 'users' },
|
||||
],
|
||||
},
|
||||
{ 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<span class="go">${icon('arrow')}</span></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<span class="go">${icon('arrow')}</span></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>
|
||||
<li><a href="/consulting/business-optimization/">Business Optimization</a></li>
|
||||
<li><a href="/consulting/supply-chain/">Supply Chain</a></li>
|
||||
<li><a href="/consulting/cybersecurity/">Cybersecurity</a></li>
|
||||
<li><a href="/consulting/education/">Education</a></li>
|
||||
<li><a href="/consulting/fractional/">Fractional Leadership</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 = [], head = '', scripts = '' }) {
|
||||
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">
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=${SITE.gaId}"></script>
|
||||
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','${SITE.gaId}');</script>
|
||||
<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="#04060c">
|
||||
<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=JetBrains+Mono:wght@500&family=Unbounded:wght@700&display=swap">
|
||||
<link rel="preload" as="font" type="font/woff2" crossorigin href="/assets/fonts/inter-tight-latin-500-normal.woff2">
|
||||
<link rel="stylesheet" href="/assets/fonts.css">
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
${head}
|
||||
<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'))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>
|
||||
<div class="bgfx" aria-hidden="true"></div>
|
||||
<div class="progress" id="progress" aria-hidden="true"></div>
|
||||
${nav(path)}
|
||||
<main id="main">
|
||||
${body}
|
||||
</main>
|
||||
${footer()}
|
||||
<script src="/assets/site.js" defer></script>
|
||||
${scripts}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import { icon, eyebrow, btn, paras } from '../layout.mjs';
|
||||
|
||||
const lines = (arr) => `<div class="lines">${arr.map((t, i) => `<p${i === 0 ? ' class="ink"' : ''}>${t}</p>`).join('')}</div>`;
|
||||
|
||||
const firsts = [
|
||||
['The First Yes', ['Before the team, the systems, and the growth plan, someone has to believe in what you’re selling.', 'That first customer teaches you something no projection can.']],
|
||||
['The First Wrong Call', ['Eventually, you make one.', 'Mine taught me something useful: being wrong is expensive only if your ego keeps you there.']],
|
||||
['The First Real Win', ['It changes your confidence.', 'Not because you suddenly know everything.', 'Because now you know you can figure something out.']],
|
||||
];
|
||||
|
||||
const noStraight = [
|
||||
['compass', 'New Industry. Blank Page.', ['Walking into a business you don’t completely understand is uncomfortable.', 'Good.', 'It makes you listen before speaking, find people smarter than you, and question things an insider might have stopped noticing.']],
|
||||
['layers', 'Different Business. Familiar Problem.', ['The terminology changes.', 'The fundamentals have a strange habit of showing up again: attention, trust, people, economics, execution, and whether the customer actually cares.']],
|
||||
['spark', 'Beginner Is a Useful Position.', ['I never want experience to make me too sophisticated to ask an obvious question.', 'Obvious questions have opened some very interesting doors.']],
|
||||
];
|
||||
|
||||
const people = [
|
||||
['mic', 'Speak Your Mind', ['I want people who bring their own judgment to the table. If there\'s a better approach, a concern I\'ve overlooked, or a decision worth questioning, I want to hear it.', 'A different perspective can change everything.']],
|
||||
['users', 'Experience Isn\'t Everything', ['Good ideas don\'t always come from the most senior person in the room.', 'I pay attention to people who understand the work firsthand. Sometimes the clearest insight comes from someone who has just joined the team.']],
|
||||
['target', 'Decide and Deliver', ['I believe in open discussions and honest disagreements. Every perspective deserves consideration, but eventually, a decision has to be made.', 'Once we agree on a direction, the focus shifts to execution. That\'s where a strong team proves itself.']],
|
||||
];
|
||||
|
||||
const exitsLater = [
|
||||
['Winning Doesn’t Make You Right Forever', ['Something worked.', 'Great.', 'The next company does not owe you the same answer.']],
|
||||
['Pattern Recognition Has a Catch', ['Experience helps you see what you’ve seen before.', 'It can also convince you that something new is familiar.', 'I try to know the difference.']],
|
||||
['I Still Want More', ['Not more for the sake of accumulation.', 'More problems worth thinking about. More people who raise my standard. More ideas that make ignoring them difficult.', 'My definition of ambition has become more selective, not smaller.']],
|
||||
];
|
||||
|
||||
const knowNow = [
|
||||
['I Know What I’m Good At.', ['And I’ve become much better at recognizing who is better than me at everything else.']],
|
||||
['I Know When I Don’t Know.', ['That sentence gets easier with experience.', 'It also gets more valuable.']],
|
||||
['I Know I’m Not Finished.', ['Not because I have a number of companies left to start or some finish line in mind.', 'I just haven’t stopped being interested yet.']],
|
||||
];
|
||||
|
||||
export default {
|
||||
path: '/about/',
|
||||
title: 'About Dylan Mazzei | Founder, Operator & Entrepreneur',
|
||||
description: 'Meet Dylan Mazzei, a founder and operator shaped by eight years of companies, four exits, hard decisions, changing industries, and what comes next.',
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="about-hero">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
${eyebrow('ABOUT DYLAN')}
|
||||
<h1 id="about-hero" class="h-display-lg">Founder in Title. Operator in Practice.</h1>
|
||||
<p class="statement">Anyone can have an opinion about a business.<br><em>I prefer being responsible for one.</em></p>
|
||||
<div class="prose">
|
||||
<p>For more than eight years, I’ve put ideas into the world, brought people around them, watched customers respond, made decisions with imperfect information, and learned what happens after the strategy meeting ends.</p>
|
||||
<p>Four companies have been sold. Others are still growing, changing, and giving me new things to figure out.</p>
|
||||
<p class="body-ink">That’s the part I like.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="visual portrait anim">
|
||||
<img src="/assets/photos/dylan-gallery-wall.jpg" alt="Dylan Mazzei" width="1100" height="1467" style="object-position:center 18%">
|
||||
<div class="flow" aria-hidden="true">
|
||||
<small>Four companies have been sold.</small>
|
||||
<ol><li>Insurance</li><li>Marketing</li><li>Home Healthcare</li><li>Apparel</li></ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="beginning">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('THE BEGINNING')}<h2 id="beginning" class="h-display">Business Made More Sense Once It Was Mine to Figure Out.</h2></div>
|
||||
<p class="body">I grew up in South Florida and studied business. Then I got close enough to the real thing to understand how much the classroom could never simulate.</p>
|
||||
</div>
|
||||
<div class="process reveal">
|
||||
${firsts.map(([h, p], i) => `<article class="process-step"><span class="num">${i + 1}</span><h3 class="h-md">${h}</h3>${lines(p)}</article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="straight">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('NO STRAIGHT LINES')}<h2 id="straight" class="h-display">My Career Would Be Easier to Explain If I Had Stayed in One Industry.</h2></div>
|
||||
<p class="statement">I didn’t.<br><em>And I wouldn’t rewrite it.</em></p>
|
||||
</div>
|
||||
<div class="cards cards-3 reveal">
|
||||
${noStraight.map(([ic, h, p]) => `<article class="card"><span class="ic">${icon(ic)}</span><h3 class="h-sm">${h}</h3>${lines(p)}</article>`).join('')}
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="justify-content:flex-end">${btn('EXPLORE MY JOURNEY', '/portfolio/')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section band" aria-labelledby="os">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('MY OPERATING SYSTEM')}<h2 id="os" class="h-display">I Don’t Have a Formula. I Have a Few Rules I Keep Coming Back To.</h2></div>
|
||||
<div class="prose"><p>Eight years has been long enough to develop instincts.</p><p>And long enough to know when not to trust them.</p></div>
|
||||
</div>
|
||||
<div class="rules">
|
||||
<article class="rule reveal"><span class="index">RULE_01</span><h3>Get Closer</h3><div class="prose">
|
||||
<p class="ink">When something doesn’t make sense, I move toward it.</p>
|
||||
<p>Talk to the customer. Sit with the team. Follow the process. Look at the numbers.</p>
|
||||
<p>Distance makes problems look cleaner than they are.</p></div></article>
|
||||
<article class="rule reveal"><span class="index">RULE_02</span><h3>Ask the Uncomfortable Question</h3><div class="prose">
|
||||
<div class="qlist"><p>Why are we doing this?</p><p>What if we’re wrong?</p><p>Would I buy this?</p><p>What are we pretending isn’t a problem?</p></div>
|
||||
<p>The question people least want to answer is occasionally the one worth asking first.</p></div></article>
|
||||
<article class="rule reveal"><span class="index">RULE_03</span><h3>Make the Call</h3><div class="prose">
|
||||
<p class="ink">Eventually, research becomes procrastination.</p>
|
||||
<p>Get enough information. Understand the risk. Decide.</p>
|
||||
<p>Then stay around for the consequences.</p></div></article>
|
||||
<article class="rule reveal"><span class="index">RULE_04</span><h3>Let Reality Win</h3><div class="prose">
|
||||
<p class="ink">I can love an idea.</p>
|
||||
<p>The customer is still allowed to hate it.</p>
|
||||
<p>Reality wins every argument eventually. I prefer finding out early.</p></div></article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="people">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('THE PEOPLE BEHIND THE BUSINESS')}<h2 id="people" class="h-display">The Right People Make the Business Better.</h2></div>
|
||||
<div class="prose">
|
||||
<p>I've learned that the people around me influence far more than the work getting done. They shape the decisions we make, the problems we notice, and how far an idea can go.</p>
|
||||
<p>I value people who think independently, take ownership, and care enough about the outcome to challenge my perspective.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="features reveal">
|
||||
${people.map(([ic, h, p]) => `<article class="feature"><span class="ic">${icon(ic)}</span><h3>${h}</h3><div class="prose">${paras(p)}</div></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="exits-later">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal" style="align-items:start">
|
||||
<div class="stack" style="position:sticky;top:calc(var(--nav-h) + 32px)">
|
||||
${eyebrow('FOUR EXITS LATER')}
|
||||
<h2 id="exits-later" class="h-display">Success Made Me More Confident. Experience Made Me Less Certain.</h2>
|
||||
<div class="prose"><p>Four exits look neat when reduced to a number.</p><p class="body-ink">The years behind them were anything but.</p></div>
|
||||
</div>
|
||||
<div class="stack-sm">
|
||||
${exitsLater.map(([h, p]) => `<article class="card card-white"><h3 class="h-sm">${h}</h3>${lines(p)}</article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="notice">
|
||||
<div class="wrap">
|
||||
<div class="sec-head-center reveal">
|
||||
${eyebrow('THINGS I NOTICE')}
|
||||
<h2 id="notice" class="h-display">The Business Usually Whispers Before It Shouts.</h2>
|
||||
</div>
|
||||
<div class="whisper reveal">
|
||||
<div class="stack">
|
||||
<div class="signals">
|
||||
<p class="signal"><span class="dot"></span>A customer asks the same question for the tenth time.</p>
|
||||
<p class="signal"><span class="dot"></span>Someone creates a spreadsheet to work around the software.</p>
|
||||
<p class="signal"><span class="dot"></span>A five-minute task somehow takes an afternoon.</p>
|
||||
</div>
|
||||
<div class="prose"><p>Nobody thinks any of it is urgent.</p><p class="statement">I notice those things.</p></div>
|
||||
</div>
|
||||
<div class="stack">
|
||||
<div class="shouts">
|
||||
<div class="shout"><q>“We’ve Always Done It This Way.”</q><span>Interesting.</span></div>
|
||||
<div class="shout"><q>“Nobody Complains About It.”</q><span>Even more interesting.</span></div>
|
||||
<div class="shout"><q>“It’s Not a Big Problem.”</q><span>Maybe.</span></div>
|
||||
</div>
|
||||
<div class="prose"><p>Or maybe everyone has simply become very good at living with it.</p><p class="body-ink">Small friction has introduced me to some very big questions.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="linkedin">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('THE PART THAT DOESN’T FIT ON LINKEDIN')}<h2 id="linkedin" class="h-display">Not Everything I Make Is Meant to Become a Company.</h2></div>
|
||||
<div class="prose">
|
||||
<p class="body-ink">This might be one of the more important things I’ve learned.</p>
|
||||
<p>Not every interest needs monetizing.<br>Not every thought needs optimizing.<br>Not everything meaningful needs a metric attached to it.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reveal">
|
||||
<article class="card card-lg card-dark">
|
||||
${eyebrow('A DIFFERENT OUTLET')}
|
||||
<h3 class="h-lg">Some Thoughts Need Space.</h3>
|
||||
<p class="poem"><span>That’s poetry for me.</span><span>No pitch deck. No growth plan. No conversion rate.</span></p>
|
||||
<p class="body">Just something I wanted to say and the patience to find the right words for it.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="know-now">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('WHAT I KNOW NOW')}<h2 id="know-now" class="h-display">Eight Years In, I Have Fewer Absolutes.</h2></div>
|
||||
</div>
|
||||
<div class="principles reveal">
|
||||
${knowNow.map(([h, p]) => `<article class="principle"><h3 class="h-md">${h}</h3><div class="lines">${paras(p)}</div></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="founders-call">
|
||||
<div class="wrap">
|
||||
<div class="showcase reverse reveal">
|
||||
<div class="visual stack-sm">
|
||||
<article class="card card-white"><span class="index">01</span><h3 class="h-sm">Diagnose First</h3><div class="prose"><p>The obvious problem and the real problem are not always the same.</p><p>Get close enough to know the difference.</p></div></article>
|
||||
<article class="card card-white"><span class="index">02</span><h3 class="h-sm">Make It Work in the Real Business</h3><div class="prose"><p>A strategy can look great on paper and fall apart on Monday morning.</p><p>I care about what can actually be executed, measured, and improved.</p><p class="body-ink">That is the same standard I use inside my own companies.</p></div></article>
|
||||
</div>
|
||||
<div class="stack">
|
||||
${eyebrow('WHEN FOUNDERS CALL')}
|
||||
<h2 id="founders-call" class="h-display">I Work With Founders the Way I Run My Own Companies.</h2>
|
||||
<div class="prose">
|
||||
<p>When I step into another founder’s business, I want to understand what is actually happening before deciding what needs to change.</p>
|
||||
<p class="body-ink">No assumptions. No solution looking for a problem.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('WORK WITH DYLAN', '/consulting/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="curious">
|
||||
<div class="wrap">
|
||||
<div class="closing closing-dark reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow('STILL CURIOUS')}
|
||||
<h2 id="curious" class="h-display-lg">I’m More Interested in What I Haven’t Seen Yet.</h2>
|
||||
<div class="prose">
|
||||
<p>Eight years ago, I couldn’t have mapped the route that brought me here. I’m glad I didn’t.</p>
|
||||
<p>There are still people I haven’t met, problems I haven’t seen, and ideas that will probably ruin a perfectly good night’s sleep.</p>
|
||||
<p style="color:var(--on-dark)">If the next eight years surprise me too, I’ll take it.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('START A CONVERSATION', '/contact/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import { icon } from '../layout.mjs';
|
||||
import { heroBlock, coversBlock, whoBlock, deliveryBlock, operatorBlock, faqBlock, closingBlock, faqSchema } from '../service.mjs';
|
||||
|
||||
const faqs = [
|
||||
['How is business optimisation different from hiring a fractional COO?', ['Business optimisation is a defined diagnostic and improvement engagement.', 'A fractional COO takes ongoing ownership of an operating function. If the diagnostic shows that the business needs continuing operational leadership rather than a focused optimisation sprint, we can discuss whether a fractional role makes more sense.']],
|
||||
['Will AI automation replace my staff?', ['That is not the assumption.', 'We look for work that is repetitive, manual, slow, or unnecessarily dependent on people before deciding whether AI or another form of automation belongs there.', 'Sometimes technology is the answer. Sometimes the process itself needs to change first.']],
|
||||
['My business is small. Is optimisation premature?', ['Not necessarily.', 'Optimisation becomes useful when operational friction is affecting margin, time, customer experience, decision-making, or the founder’s ability to focus.', 'Company size alone does not determine that.']],
|
||||
['What do you need from me to start?', ['Enough access to understand how the business actually operates.', 'Depending on the diagnostic, that can include financial information, software and vendor costs, team structure, existing processes, reporting, and conversations with the people closest to the work.']],
|
||||
];
|
||||
|
||||
const visual = `<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><span class="file">${icon('brace')}operations.log</span></div>
|
||||
<pre class="code-pre"><span class="ln"><span class="p">$</span> <span class="k">find</span> the friction</span><span class="ln"><span class="n">→</span> time</span><span class="ln"><span class="n">→</span> margin</span><span class="ln"><span class="n">→</span> visibility</span><span class="ln"><span class="n">→</span> unnecessary founder attention</span><span class="ln"><span class="p">$</span> <span class="k">prioritise</span> the fixes</span><span class="ln"><span class="s">✓</span> what deserves attention first</span><span class="ln"><span class="s">✓</span> what can wait</span><span class="ln"><span class="p">$</span> <span class="k">make</span> it lighter<span class="cursor"></span></span></pre>`;
|
||||
|
||||
export default {
|
||||
path: '/consulting/business-optimization/',
|
||||
title: 'Business Optimisation Consulting | Dylan Mazzei',
|
||||
description: 'Business optimisation consulting for founders focused on margins, processes, team structure, AI automation, reporting, and operational efficiency.',
|
||||
schema: [faqSchema(faqs)],
|
||||
body:
|
||||
heroBlock({
|
||||
id: 'bo-hero',
|
||||
crumb: 'Business Optimization',
|
||||
kicker: 'BUSINESS OPTIMIZATION CONSULTING',
|
||||
h1: 'Better Operations Create Better Businesses.',
|
||||
lead: 'Revenue can grow while margins tighten, processes multiply, teams get harder to manage, and the founder somehow ends up with more on their plate.',
|
||||
paras: [
|
||||
'*That doesn’t always mean the business needs more sales.',
|
||||
'Sometimes it needs a closer look at where the money, time, people, and decisions are going.',
|
||||
'My teams and I find the friction, determine what is actually creating it, and make the business easier to operate as it grows.',
|
||||
],
|
||||
visual,
|
||||
}).replace('<div class="panel anim" aria-hidden="true">', '<div class="panel panel-dark anim" aria-hidden="true">') +
|
||||
coversBlock({
|
||||
id: 'bo-covers',
|
||||
kicker: 'WHERE THE BUSINESS GETS HEAVY',
|
||||
h2: 'Find the Friction Growth Left Behind.',
|
||||
intro: ['Growth changes what a business asks of its systems, people, and processes.', '*We look at where the company has become more complicated than it needs to be and what can make it work better.'],
|
||||
items: [
|
||||
['chart', 'Margin & Cost', ['Operations audit to see where time and money actually go', 'Pricing, packaging, tiers, and unit economics review', 'Vendor, software, and recurring-spend analysis', 'Cost structure opportunities without cutting simply for the sake of cutting']],
|
||||
['users', 'Team & Process', ['Roles, ownership, and accountability', 'Team design for the company’s next stage', 'Process documentation and SOPs', 'Delegation architecture for founders carrying too many decisions']],
|
||||
['chip', 'Automation & Visibility', ['AI workflow automation across operations, content, research, reporting, and customer service', 'KPI and reporting design', 'Automated visibility into the numbers that deserve attention', 'Identification of repetitive work that technology can genuinely make easier']],
|
||||
],
|
||||
}) +
|
||||
whoBlock({
|
||||
id: 'bo-who',
|
||||
h2: 'When Growth Starts Creating Its Own Problems.',
|
||||
items: [
|
||||
['gauge', 'Revenue Grew. Margin Didn’t.', ['The top line is moving, but more revenue isn’t translating into the improvement you expected.', 'We look underneath the growth to find out where the economics are getting lost.']],
|
||||
['target', 'You’re Still in Too Many Decisions.', ['The company has a team, systems, and processes.', 'Somehow, too much still finds its way back to you.', 'The objective isn’t to make the founder less important. It’s to stop making the founder necessary for decisions that shouldn’t require them.']],
|
||||
['layers', 'The Team Got Bigger Faster Than the System Did.', ['Headcount can solve a capacity problem.', 'It can also hide a process problem.', 'Before adding another role, we look at what the existing team is being asked to work around.']],
|
||||
],
|
||||
}) +
|
||||
deliveryBlock({
|
||||
id: 'bo-delivery',
|
||||
h2: 'Fix What’s Creating the Drag.',
|
||||
intro: ['Business optimisation follows the same diagnostic-first approach behind my consulting work.'],
|
||||
steps: [
|
||||
['Find the Friction', ['The Business Diagnostic examines cost, process, team structure, reporting, technology, and AI leverage.', 'We look for where the business is losing time, margin, visibility, or unnecessary founder attention.']],
|
||||
['Prioritise the Fixes', ['You receive a scored roadmap showing what deserves attention first, what can wait, and what improvement should actually look like.', 'Not every inefficiency deserves a project.']],
|
||||
['Make It Lighter', ['When execution makes sense, the 90-Day Optimisation Sprint turns the roadmap into changes inside the business.', 'That can mean rebuilding processes, improving reporting, deploying useful automation, reviewing the vendor stack, clarifying ownership, or removing work that no longer needs to exist.', 'It’s the same operating discipline I apply across my current ventures.']],
|
||||
],
|
||||
}) +
|
||||
operatorBlock({
|
||||
id: 'bo-op',
|
||||
h2: 'Efficiency Isn’t About Cutting Everything.',
|
||||
paras: ['*A cheaper business is not automatically a better business.', 'The point is to know what deserves money, what deserves people, what deserves automation, and what probably shouldn’t be happening at all.', 'I’ve seen those decisions play out across businesses in very different industries.'],
|
||||
side: '<p class="statement">The details change.</p><article class="card card-dark"><h3 class="h-lg">The cost of unnecessary complexity doesn’t.</h3></article>',
|
||||
cta: 'EXPLORE MY PORTFOLIO',
|
||||
}) +
|
||||
faqBlock({ id: 'bo-faq', h2: 'Before We Start Taking Things Apart.', items: faqs }) +
|
||||
closingBlock({
|
||||
id: 'bo-close',
|
||||
kicker: 'MAKE GROWTH EASIER TO CARRY',
|
||||
h2: 'Keep the Growth. Lose the Unnecessary Weight.',
|
||||
paras: ['You don’t need to strip the business down just to call it efficient.', 'We’ll find where margin, time, and attention are getting lost, then identify the changes worth making.', '*The result should be a business that can do more without making everything harder to run.'],
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
import { icon, eyebrow, btn, paras, faq } from '../layout.mjs';
|
||||
|
||||
const covers = [
|
||||
['users', 'Entity & Ownership', ['LLC, S-Corp, or C-Corp considerations based on the business model, funding plans, and long-term goals', 'Co-founder and early-partner ownership structure', 'Existing entity and structure review']],
|
||||
['pin', 'State & Market', ['State selection based on industry, filing requirements, costs, licensing, and operating plans', 'Multi-state operations and foreign qualification considerations', 'US setup considerations for international founders', 'UAE free zone considerations for US founders']],
|
||||
['layers', 'Setup & Infrastructure', ['Licensing and compliance mapping', 'Banking and payment infrastructure', 'Registered agent setup', 'Bookkeeping and payroll sequencing', 'Insurance considerations', 'First-90-days administrative roadmap']],
|
||||
];
|
||||
|
||||
const who = [
|
||||
['rocket', 'You’re Starting Your First Company.', ['You have the idea and a plan to move.', 'Now you need to make the structural decisions without relying on a collection of contradictory search results and founder opinions.']],
|
||||
['building', 'The Business Already Exists, but the Setup Feels Wrong.', ['Maybe the company has outgrown its original structure.', 'Maybe you formed in a state because someone told you to.', 'Maybe revenue, hiring, ownership, or operations have changed enough to make another look worthwhile.', 'We examine what you have before deciding whether anything actually needs to change.']],
|
||||
['route', 'You’re Crossing a Border.', ['International founders entering the US and US founders exploring the UAE face another layer of structural and operational decisions.', 'The setup needs to make sense for where you’re going, not simply where you started.']],
|
||||
];
|
||||
|
||||
const steps = [
|
||||
['Diagnose', ['We review the planned or existing business structure against the industry, ownership, operating markets, revenue model, and growth plans.', 'If the company already exists, we look at what is working before suggesting that anything change.']],
|
||||
['Map the Setup', ['You receive a prioritized roadmap covering the relevant entity, state, licensing, banking, ownership, and administrative considerations in sequence.', 'You know what comes first and what can wait.']],
|
||||
['Set It Up', ['If you want help moving through the roadmap, a setup sprint takes the business through the practical steps with the appropriate specialists involved where needed.']],
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
['Is Delaware always the right answer?', ['No.', 'Delaware can make sense for certain companies, particularly depending on financing, ownership, and future plans, but it isn’t automatically the best choice for every founder.', 'Your operating location, business model, ownership, costs, compliance obligations, and plans should all be considered before deciding where to form.']],
|
||||
['Can you help if my business is already formed?', ['Yes.', 'The diagnostic can review an existing structure as well as a planned one.', 'If something looks inefficient or no longer fits the business, we identify it. That doesn’t mean restructuring automatically makes sense.', 'Sometimes the right recommendation is to leave what already works alone.']],
|
||||
['Do you handle the actual filings?', ['The setup sprint can help coordinate the formation and administrative process.', 'Where filings or decisions require licensed legal, tax, accounting, or other professional services, the appropriate qualified professional handles that portion.']],
|
||||
['Are you a lawyer or accountant?', ['No.', 'This is business and operational consulting, not legal, tax, or accounting advice.', 'Where formal professional advice is required, we work alongside your existing counsel or accounting professionals or help connect the process with appropriate specialists.']],
|
||||
];
|
||||
|
||||
const layers = ['Entity.', 'State.', 'Ownership.', 'Banking.', 'Licensing.'];
|
||||
const growth = ['You open another account.', 'Hire someone.', 'Add a partner.', 'Enter another market.', 'Raise money.', 'Sell something.'];
|
||||
|
||||
export default {
|
||||
path: '/consulting/business-setup/',
|
||||
title: 'Business Setup Consulting for Founders | Dylan Mazzei',
|
||||
description: 'Business setup consulting for founders covering entity and state selection, licensing, banking, ownership, US expansion, UAE formation, and more.',
|
||||
schema: [
|
||||
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqs.map(([q, a]) => ({ '@type': 'Question', name: q, acceptedAnswer: { '@type': 'Answer', text: a.join(' ') } })) },
|
||||
],
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="b-hero">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
<nav class="crumbs" aria-label="Breadcrumb"><a href="/consulting/">Consulting</a>${icon('chev')}<span>Business Setup</span></nav>
|
||||
${eyebrow('BUSINESS SETUP CONSULTING')}
|
||||
<h1 id="b-hero" class="h-display-lg">Make the First Decisions With the Future in Mind.</h1>
|
||||
<p class="lead">The first decisions in a company can be surprisingly persistent.</p>
|
||||
<div class="prose">
|
||||
<p>Entity. State. Ownership. Banking. Licensing. The administrative pieces aren’t the exciting part of starting a business, but getting them wrong can create expensive problems once the company starts moving.</p>
|
||||
<p class="body-ink">I’ve formed and operated companies across multiple US states and the UAE.</p>
|
||||
<p>The goal is to think through the structure before growth makes changing it harder.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('BOOK YOUR DIAGNOSTIC', '/contact/')}</div>
|
||||
</div>
|
||||
<div class="panel anim" aria-hidden="true">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><strong>SETTING THE FIRST LAYER</strong></div>
|
||||
<div style="padding:20px;display:grid;gap:8px">
|
||||
${layers.map((l, i) => `<div class="row" style="border:1px solid var(--mist);border-radius:12px;padding:14px 16px;margin-left:${i * 14}px;${i === 0 ? 'background:var(--accent);color:var(--on-accent);border-color:var(--accent)' : ''}"><span class="index" style="${i === 0 ? 'color:var(--on-accent)' : ''}">0${i + 1}</span><span class="name" style="font-weight:700;font-size:16px">${l}</span></div>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="layer">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('SETTING THE FIRST LAYER')}<h2 id="layer" class="h-display">Get the Foundation Right Before You Build on It.</h2></div>
|
||||
<div class="prose"><p class="body-ink">Business setup is more than filing an entity.</p><p>We look at how the decisions fit together and what the business may need next.</p></div>
|
||||
</div>
|
||||
<div class="covers reveal">
|
||||
${covers.map(([ic, h, items]) => `<article class="cover"><div class="cover-head"><span class="ic">${icon(ic)}</span><h3>${h}</h3></div><ul>${items.map((it) => `<li>${icon('check')}${it}</li>`).join('')}</ul></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="who-b">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('WHO THIS IS FOR')}<h2 id="who-b" class="h-display">Before “We’ll Figure It Out Later” Gets Expensive.</h2></div>
|
||||
</div>
|
||||
<div class="cards cards-3 reveal">
|
||||
${who.map(([ic, h, p]) => `<article class="card"><span class="ic">${icon(ic)}</span><h3 class="h-sm">${h}</h3><div class="prose">${paras(p)}</div></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section band" aria-labelledby="order">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('HOW DELIVERY WORKS')}<h2 id="order" class="h-display">Make the Decisions in the Right Order.</h2></div>
|
||||
<div class="prose">
|
||||
<p>You don’t need eight disconnected recommendations.</p>
|
||||
<p style="color:var(--on-dark)">You need to understand which decision affects the next one.</p>
|
||||
<p>That’s why this service follows the same diagnostic-first approach as my broader consulting work.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules">
|
||||
${steps.map(([h, p], i) => `<article class="rule reveal"><span class="index">STEP_0${i + 1}</span><h3>${i + 1}. ${h}</h3><div class="prose">${p.map((t, j) => `<p${j === 0 ? ' class="ink"' : ''}>${t}</p>`).join('')}</div></article>`).join('')}
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="border-top:0;margin-top:48px">
|
||||
<div class="card card-dark" style="background:rgba(255,255,255,.04);border:1px solid var(--dark-line);max-width:720px">
|
||||
<p class="statement">I’m an operator, not your lawyer or accountant.</p>
|
||||
<p>Where a decision requires formal legal, tax, or accounting advice, we coordinate with your existing professionals or help bring the right qualified advisor into the conversation.</p>
|
||||
</div>
|
||||
${btn('START WITH THE DIAGNOSTIC', '/contact/', 'light')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="live">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal">
|
||||
<div class="stack">
|
||||
${eyebrow('WHY THIS IS DIFFERENT')}
|
||||
<h2 id="live" class="h-display">I’ve Had to Live With These Decisions Too.</h2>
|
||||
<div class="prose">
|
||||
<p>I’ve started enough companies to know how tempting it is to treat formation like paperwork standing between you and the interesting part.</p>
|
||||
<p class="body-ink">Then the company grows.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack">
|
||||
<div class="panel" aria-hidden="true">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span></div>
|
||||
<ol style="padding:8px 0">${growth.map((g, i) => `<li class="row" style="padding:12px 20px${i === 0 ? ';border-top:0' : ''}"><span class="index">${String(i + 1).padStart(2, '0')}</span><span class="name">${g}</span></li>`).join('')}</ol>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<p class="sr-only">You open another account. Hire someone. Add a partner. Enter another market. Raise money. Sell something.</p>
|
||||
<p class="body-ink">Suddenly, a decision made in an afternoon is still sitting in the middle of the business years later.</p>
|
||||
<p>My perspective comes from operating companies across different industries and markets, not simply helping someone complete a filing.</p>
|
||||
<p class="body-ink">The beginning deserves more thought than it usually gets.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="faq-b">
|
||||
<div class="wrap faq-layout">
|
||||
<div class="stack-sm sticky reveal">${eyebrow('FREQUENTLY ASKED QUESTIONS')}<h2 id="faq-b" class="h-display">Before You File Anything.</h2></div>
|
||||
<div class="reveal">${faq(faqs)}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="structure">
|
||||
<div class="wrap">
|
||||
<div class="closing reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow('START WITH THE STRUCTURE')}
|
||||
<h2 id="structure" class="h-display-lg">Make the Early Decisions Easier to Live With Later.</h2>
|
||||
<div class="prose">
|
||||
<p>You don’t need every detail of the next five years figured out before starting.</p>
|
||||
<p>You do need a structure that makes sense for the business you’re actually planning to operate.</p>
|
||||
<p class="body-ink">We’ll map the decisions in the right order before you commit more time, money, and paperwork to them.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('BOOK YOUR DIAGNOSTIC', '/contact/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,224 @@
|
||||
import { icon, eyebrow, btn, textLink, paras, faq } from '../layout.mjs';
|
||||
|
||||
const steps = [
|
||||
['Business Diagnostic', ['We go inside the business to examine operations, financials, technology, marketing, and the places where growth is creating friction.', 'The goal is simple: find what deserves attention first.']],
|
||||
['Roadmap Delivery', ['You receive a documented, prioritised roadmap built around what we found.', 'What needs to change. What can wait. What your team can handle. And where outside execution may actually help.']],
|
||||
['Execution Sprint or Advisory', ['If it makes sense to keep working together, we move into a focused 90-day sprint or an ongoing advisory engagement.', 'If your team can take it from there, they do.']],
|
||||
];
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: 'Business Diagnostic', h: 'Know What You’re Solving.', p: 'The mandatory starting point for every engagement.', fee: 'Fixed fee, quoted at booking',
|
||||
items: ['Comprehensive operational audit', 'Marketing and visibility analysis', 'Cost structure review', 'Prioritised strategic roadmap', '45-minute strategy debrief'],
|
||||
cta: 'BOOK YOUR DIAGNOSTIC', featured: true,
|
||||
},
|
||||
{
|
||||
name: '90-Day Sprint', h: 'Turn the Roadmap Into Movement.', p: 'Hands-on execution around a defined problem, with a clear scope and enough time to make meaningful progress.', fee: 'Fixed scope, fixed fee',
|
||||
items: ['Everything in the Diagnostic', 'Execution by Dylan’s specialist teams', 'System setup and automation', 'Vendor and talent vetting', 'Weekly progress check-ins'],
|
||||
cta: 'APPLY FOR A 90-DAY SPRINT',
|
||||
},
|
||||
{
|
||||
name: 'Advisory Retainer', h: 'Keep an Operator in the Room.', p: 'Ongoing strategic involvement for founders and internal teams who need another experienced perspective as the business moves.', fee: 'Monthly engagement',
|
||||
items: ['Monthly strategic roadmapping', 'Bi-weekly advisory calls', 'Direct access for urgent blocks', 'Quarterly P&L reviews', 'Priority access to Dylan’s operating teams'],
|
||||
cta: 'REQUEST ADVISORY',
|
||||
},
|
||||
];
|
||||
|
||||
const services = [
|
||||
{ id: 'marketing', icon: 'search', name: 'Marketing & AI Search', h: 'Get Found Where Attention Is Moving.', p: ['SEO, AI search visibility, paid media, and funnel strategy grounded in what people actually discover, click, and choose.', 'Marketing has been part of my operating world for years, and it remains one of the areas where technology keeps changing the answer.'], cta: 'EXPLORE MARKETING & AI SEARCH', href: '/consulting/marketing/' },
|
||||
{ id: 'business-setup', icon: 'building', name: 'Business Setup', h: 'Start With Fewer Expensive Mistakes.', p: ['Entity and state selection, licensing, banking architecture, and the practical decisions that turn an idea into an operating business.', 'I’ve started enough companies to know that early decisions have a habit of following you.'], cta: 'EXPLORE BUSINESS SETUP', href: '/consulting/business-setup/' },
|
||||
{ id: 'optimization', icon: 'gauge', name: 'Business Optimization', h: 'Find What Growth Made Messy.', p: ['Growth creates opportunity.', 'It also creates workarounds, extra steps, unnecessary costs, unclear ownership, and processes nobody remembers choosing.', 'The job is to find what the business has outgrown and make it work for where the company is going next.'] , cta: 'EXPLORE BUSINESS OPTIMIZATION', href: '/consulting/business-optimization/' },
|
||||
{ id: 'supply-chain', icon: 'truck', name: 'Supply Chain', h: 'Know What Happens Before It Reaches the Customer.', p: ['Sourcing, supplier vetting, logistics planning, and landed-cost analysis for businesses where products have to move through the real world.', 'My work across consumer products and e-commerce has taught me that what happens before the sale can determine whether the sale makes sense.'] , cta: 'EXPLORE SUPPLY CHAIN', href: '/consulting/supply-chain/' },
|
||||
{ id: 'cybersecurity', icon: 'shield', name: 'Cybersecurity', h: 'Find the Exposure Before Someone Else Does.', p: ['Security posture assessments, vulnerability reviews, and compliance readiness grounded in how the business actually operates.', 'The goal isn’t security theatre.', 'It’s understanding where the business is exposed and what deserves attention first.'] , cta: 'EXPLORE CYBERSECURITY', href: '/consulting/cybersecurity/' },
|
||||
{ id: 'education', icon: 'edu', name: 'Education', h: 'Make the Strategy Survive the Classroom.', p: ['EdTech product strategy, institutional technology adoption, and market-entry thinking shaped around the realities of schools, educators, and implementation.', 'A product can make perfect sense in a meeting and still fail the people expected to use it.', 'The classroom gets the final vote.'] , cta: 'EXPLORE EDUCATION CONSULTING', href: '/consulting/education/' },
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
['What services does Dylan Mazzei offer?', ['My consulting work spans business diagnostics, 90-day execution sprints, ongoing advisory, fractional leadership, marketing and AI search, business setup and optimisation, supply chain, cybersecurity, and education strategy.', 'Every engagement starts by understanding the business before deciding which, if any, of those services make sense.']],
|
||||
['How do I book a consultation?', ['Start by booking the Business Diagnostic.', 'We’ll review the business, identify the areas that deserve attention, and use those findings to determine the most useful next step.']],
|
||||
['What is the pricing structure?', ['The Business Diagnostic is a fixed fee quoted at booking.', '90-day sprints are fixed-scope, fixed-fee engagements. Advisory and fractional leadership are structured around the level of ongoing involvement required.']],
|
||||
['What happens after the diagnostic?', ['You receive a prioritised roadmap outlining what needs attention, what should happen first, and where execution may be needed.', 'You can take that roadmap to your own team or continue working with mine.']],
|
||||
['Do you work with businesses outside the US?', ['Yes. I operate across the US and Dubai and can work with businesses in other markets when the engagement is a good fit.']],
|
||||
['Do I have to continue after the diagnostic?', ['No. The diagnostic is designed to be useful on its own.', 'If your team can execute the roadmap internally, you leave with a clearer direction and a place to start.']],
|
||||
];
|
||||
|
||||
export default {
|
||||
path: '/consulting/',
|
||||
title: 'Business Consulting for Founders | Dylan Mazzei',
|
||||
description: 'Work with Dylan Mazzei through diagnostic-first business consulting, execution sprints, advisory, and fractional leadership built for founders.',
|
||||
schema: [
|
||||
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqs.map(([q, a]) => ({ '@type': 'Question', name: q, acceptedAnswer: { '@type': 'Answer', text: a.join(' ') } })) },
|
||||
],
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="c-hero">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
${eyebrow('OPERATOR-LED CONSULTING')}
|
||||
<h1 id="c-hero" class="h-display-lg">Find the Real Problem. Then Fix the Right One.</h1>
|
||||
<p class="lead">Most founders know when something in the business isn’t working. The harder part is knowing why.</p>
|
||||
<div class="prose">
|
||||
<p>I start there. Before recommending a strategy, service, or solution, I look inside the business, find what is actually creating friction, and put the priorities in order.</p>
|
||||
<p>It’s the same approach I use across the companies I operate today. Understand what is happening first. Decide what deserves attention second.</p>
|
||||
<p class="statement">Then move.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('BOOK YOUR DIAGNOSTIC', '/contact/')}</div>
|
||||
</div>
|
||||
<div class="panel panel-dark anim" aria-hidden="true">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><span class="file">${icon('brace')}diagnostic.log</span></div>
|
||||
<pre class="code-pre"><span class="ln"><span class="p">$</span> <span class="k">look</span> inside the business</span><span class="ln"><span class="s">✓</span> operations</span><span class="ln"><span class="s">✓</span> financials</span><span class="ln"><span class="s">✓</span> technology</span><span class="ln"><span class="s">✓</span> marketing</span><span class="ln"><span class="p">$</span> <span class="k">find</span> friction</span><span class="ln"><span class="n">→</span> what deserves attention first</span><span class="ln"><span class="p">$</span> <span class="k">put</span> the priorities in order</span><span class="ln"><span class="p">$</span> <span class="k">move</span><span class="cursor"></span></span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="how">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('HOW IT WORKS')}<h2 id="how" class="h-display">Diagnosis Before Prescription.</h2></div>
|
||||
<div class="prose"><p>The loudest problem isn’t always the one holding the business back.</p><p class="body-ink">So we find the cause before we start fixing the symptoms.</p></div>
|
||||
</div>
|
||||
<div class="process reveal">
|
||||
${steps.map(([h, p], i) => `<article class="process-step"><span class="num">${i + 1}</span><h3 class="h-md">${h}</h3><div class="prose">${paras(p)}</div></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="engagements" aria-labelledby="eng">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('ENGAGEMENT OPTIONS')}<h2 id="eng" class="h-display">Different Problems Need Different Levels of Involvement.</h2></div>
|
||||
<div class="prose"><p class="body-ink">Start with the diagnostic.</p><p>What comes after depends on what the business actually needs.</p></div>
|
||||
</div>
|
||||
<div class="plans reveal">
|
||||
${plans
|
||||
.map(
|
||||
(pl) => `<article class="plan${pl.featured ? ' plan-featured' : ''}">
|
||||
<div class="plan-name"><span>${pl.name}</span>${pl.featured ? `<span class="dot"></span>` : ''}</div>
|
||||
<h3>${pl.h}</h3>
|
||||
<p>${pl.p}</p>
|
||||
<div class="fee">${pl.fee}</div>
|
||||
<ul>${pl.items.map((it) => `<li>${icon('check')}${it}</li>`).join('')}</ul>
|
||||
${btn(pl.cta, '/contact/', pl.featured ? 'primary' : 'ghost')}
|
||||
</article>`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section band" aria-labelledby="frac">
|
||||
<div class="wrap">
|
||||
<div class="showcase" style="background:transparent;padding:0">
|
||||
<div class="stack reveal">
|
||||
${eyebrow('FRACTIONAL LEADERSHIP')}
|
||||
<h2 id="frac" class="h-display">When the Business Needs an Owner, Not Another Recommendation.</h2>
|
||||
<div class="prose">
|
||||
<p>Some problems don’t need another project.</p>
|
||||
<p style="color:var(--on-dark)">They need someone accountable for the function.</p>
|
||||
<p>I can step into the business as a fractional CMO, COO, CISO, or Chief AI Officer, bringing the same operator mindset that shapes my current ventures into a defined leadership role.</p>
|
||||
</div>
|
||||
<p class="statement">Strategy gets more useful when someone owns what happens next.</p>
|
||||
<div class="btn-row">${btn('EXPLORE FRACTIONAL ROLES', '/consulting/fractional/', 'light')}</div>
|
||||
</div>
|
||||
<div class="roles reveal" aria-hidden="true">
|
||||
<div class="role"><span class="index">FRACTIONAL</span><b>CMO</b></div>
|
||||
<div class="role"><span class="index">FRACTIONAL</span><b>COO</b></div>
|
||||
<div class="role"><span class="index">FRACTIONAL</span><b>CISO</b></div>
|
||||
<div class="role"><span class="index">FRACTIONAL</span><b>Chief AI Officer</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="services" aria-labelledby="svc">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('SERVICE LINES')}<h2 id="svc" class="h-display">Where Operating Experience Becomes Useful.</h2></div>
|
||||
<div class="prose"><p>I stay close to areas where my teams and I are already doing the work.</p><p>Not because every business needs the same solution, but because experience is more useful when it comes from solving real versions of the problem.</p></div>
|
||||
</div>
|
||||
<div class="tabs reveal" data-tabs>
|
||||
<div class="tab-list" role="tablist" aria-label="Service lines">
|
||||
${services.map((s, i) => `<button class="tab-btn" role="tab" id="tab-${s.id}" aria-controls="${s.id}" aria-selected="${i === 0}"${i ? ' tabindex="-1"' : ''} type="button"><span class="ic">${icon(s.icon)}</span><span>${s.name}</span>${icon('arrow', 'arr')}</button>`).join('')}
|
||||
</div>
|
||||
<div class="tab-panels">
|
||||
${services
|
||||
.map(
|
||||
(s) => `<article class="tab-panel" role="tabpanel" id="${s.id}" aria-labelledby="tab-${s.id}">
|
||||
<span class="tp-title">${s.name}</span>
|
||||
<h3 class="h-display">${s.h}</h3>
|
||||
<div class="prose">${paras(s.p)}</div>
|
||||
${s.cta ? `<div class="btn-row">${btn(s.cta, s.href)}</div>` : ''}
|
||||
</article>`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="why">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('WHY DYLAN')}<h2 id="why" class="h-display">I’ve Been on the Other Side of the Invoice.</h2></div>
|
||||
<div class="prose">
|
||||
<p>I’ve spent more than eight years operating businesses across different industries.</p>
|
||||
<p>Along the way, I’ve hired consultants, agencies, specialists, vendors, and teams. I’ve made investments that worked and others that looked considerably better in the proposal.</p>
|
||||
<p class="body-ink">I’ve also taken four companies through successful exits.</p>
|
||||
<p>That changes how I look at someone else’s time and money.</p>
|
||||
<p>I know the difference between a recommendation that sounds impressive and one that survives contact with the actual business.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="principles reveal">
|
||||
<article class="principle"><span class="index">01</span><h3 class="h-md">Understand Before Recommending</h3><div class="prose"><p>I don’t need to arrive with the answer.</p><p>I need to ask enough of the right questions to find it.</p></div></article>
|
||||
<article class="principle"><span class="index">02</span><h3 class="h-md">Make the Tradeoffs Visible</h3><div class="prose"><p>Every meaningful decision costs something.</p><p class="ink">Money. Time. Focus. Speed. Flexibility.</p><p>A useful strategy tells you what you’re choosing and what you’re giving up.</p></div></article>
|
||||
<article class="principle"><span class="index">03</span><h3 class="h-md">Stay for the Result</h3><div class="prose"><p>A strategy deck is not the finish line.</p><p>If we move into execution, I care about what changed because we did.</p></div></article>
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="justify-content:flex-end;margin-top:40px">${textLink('GET TO KNOW DYLAN', '/about/')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="behind">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal">
|
||||
<div class="stack">
|
||||
${eyebrow('THE OPERATOR BEHIND THE WORK')}
|
||||
<h2 id="behind" class="h-display">The Consulting Came After the Companies.</h2>
|
||||
<div class="prose">
|
||||
<p>I didn’t start in consulting and eventually decide to operate businesses.</p>
|
||||
<p class="body-ink">It happened the other way around.</p>
|
||||
<p>Consulting grew out of the decisions, mistakes, patterns, and results of running companies across different industries.</p>
|
||||
<p>You can read the full story or explore the businesses and exits that shaped it.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('EXPLORE DYLAN’S VENTURES', '/portfolio/')}</div>
|
||||
</div>
|
||||
<div class="visual portrait">
|
||||
<img src="/assets/photos/dylan-bull.jpg" alt="Dylan Mazzei" width="1100" height="1467" loading="lazy" style="object-position:center 30%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="faq-h">
|
||||
<div class="wrap faq-layout">
|
||||
<div class="stack-sm sticky reveal">${eyebrow('FREQUENTLY ASKED QUESTIONS')}<h2 id="faq-h" class="h-display">Questions Founders Usually Ask Before We Start.</h2></div>
|
||||
<div class="reveal">${faq(faqs)}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="clarity">
|
||||
<div class="wrap">
|
||||
<div class="closing closing-dark reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow('START WITH CLARITY')}
|
||||
<h2 id="clarity" class="h-display-lg">Stop Spending Money on the Wrong Problem.</h2>
|
||||
<div class="prose">
|
||||
<p>You don’t need to know exactly what is wrong before we talk.</p>
|
||||
<p>Bring me the bottleneck, the stalled growth, the messy operation, or simply the feeling that the business should be working better than it is.</p>
|
||||
<p style="color:var(--on-dark)">We’ll figure out what deserves your attention before you spend more time or money trying to fix it.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('BOOK YOUR DIAGNOSTIC', '/contact/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { icon, eyebrow, btn, SITE } from '../layout.mjs';
|
||||
|
||||
export default {
|
||||
path: '/contact/',
|
||||
title: 'Contact Dylan Mazzei | Business & Partnerships',
|
||||
description: 'Contact Dylan Mazzei about business opportunities, strategic partnerships, consulting, media inquiries, or other conversations worth having.',
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="ct-hero">
|
||||
<div class="wrap contact-grid">
|
||||
<div class="stack anim">
|
||||
${eyebrow('CONTACT')}
|
||||
<h1 id="ct-hero" class="h-display-lg">Interesting Things Usually Start With Hello.</h1>
|
||||
<p class="lead">A business at a crossroads. An idea taking shape. A partnership worth exploring. Or something I haven’t thought of yet.</p>
|
||||
<div class="prose">
|
||||
<p class="body-ink">You don’t need a polished pitch.</p>
|
||||
<p>Tell me what you’re working on and why you think we should talk.</p>
|
||||
</div>
|
||||
<div class="direct">
|
||||
<div class="direct-row">
|
||||
<small>DIRECT EMAIL</small>
|
||||
<span class="val"><a href="mailto:${SITE.email}">${SITE.email}</a><button class="copy-btn" type="button" data-copy="${SITE.email}">Copy</button></span>
|
||||
</div>
|
||||
<div class="direct-row">
|
||||
<small>WHERE I WORK</small>
|
||||
<span class="val">Texas • Florida • New York • Dubai</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack anim">
|
||||
<form class="panel form-card" id="inquiry" data-to="${SITE.email}" novalidate>
|
||||
<div class="panel-bar"><span class="lights" aria-hidden="true"><i></i><i></i><i></i></span><span class="panel-url">${SITE.email}</span></div>
|
||||
<div class="stack-xs">
|
||||
<h2 class="h-md">Send an Inquiry</h2>
|
||||
<p class="body">Give me enough context to understand what you’re working on, where things stand, and what you’d like to discuss.</p>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label for="f-name">Name</label><input id="f-name" name="name" type="text" autocomplete="name" required></div>
|
||||
<div class="field"><label for="f-email">Email</label><input id="f-email" name="email" type="email" autocomplete="email" required></div>
|
||||
<div class="field"><label for="f-company">Company</label><input id="f-company" name="company" type="text" autocomplete="organization"></div>
|
||||
<div class="field"><label for="f-topic">Topic</label>
|
||||
<select id="f-topic" name="topic">
|
||||
<option value="">Select one</option>
|
||||
<option>Business opportunity</option>
|
||||
<option>Strategic partnership</option>
|
||||
<option>Consulting</option>
|
||||
<option>Media inquiry</option>
|
||||
<option>Something else</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field field-full"><label for="f-message">Message</label><textarea id="f-message" name="message" required></textarea></div>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-block" type="submit">SEND AN INQUIRY<span class="go">${icon('arrow')}</span></button>
|
||||
<p class="form-status" id="form-status" role="status" aria-live="polite"></p>
|
||||
</form>
|
||||
|
||||
<a class="cal-card" href="${SITE.calendar}">
|
||||
<div><h3>Rather Put It on the Calendar?</h3><p>If you already know a conversation makes sense, skip the back-and-forth and find a time that works.</p></div>
|
||||
<span class="go">${icon('calendar')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="where">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal">
|
||||
<div class="stack">
|
||||
${eyebrow('NOT SURE WHERE TO START?')}
|
||||
<h2 id="where" class="h-display">See What I’m Working On First.</h2>
|
||||
<div class="prose">
|
||||
<p>If you want a little context before reaching out, explore the companies I operate today, see the businesses and exits that shaped my journey, or learn how I work with founders.</p>
|
||||
<p class="body-ink">Then, if something clicks, come back and say hello.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('EXPLORE MY VENTURES', '/portfolio/')}</div>
|
||||
</div>
|
||||
<div class="stack-sm">
|
||||
<a class="card card-white" href="/portfolio/"><div class="card-top"><span class="ic">${icon('layers')}</span>${icon('arrow-ur', 'check')}</div><h3 class="h-sm">explore the companies I operate today</h3></a>
|
||||
<a class="card card-white" href="/about/"><div class="card-top"><span class="ic">${icon('route')}</span>${icon('arrow-ur', 'check')}</div><h3 class="h-sm">see the businesses and exits that shaped my journey</h3></a>
|
||||
<a class="card card-white" href="/consulting/"><div class="card-top"><span class="ic">${icon('compass')}</span>${icon('arrow-ur', 'check')}</div><h3 class="h-sm">learn how I work with founders</h3></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { icon } from '../layout.mjs';
|
||||
import { heroBlock, coversBlock, whoBlock, deliveryBlock, operatorBlock, faqBlock, closingBlock, faqSchema, panelBar } from '../service.mjs';
|
||||
|
||||
const faqs = [
|
||||
['We are a small business. Are we really a target?', ['Business size alone does not determine cyber risk.', 'Smaller companies still use email, payment systems, cloud platforms, customer data, employee accounts, and third-party vendors. Many common forms of cybercrime are automated or opportunistic, not aimed only at large enterprises.', 'The useful question is what exposure your particular business has.']],
|
||||
['Is this a penetration test?', ['Not automatically.', 'The engagement begins with a security-posture diagnostic. If deeper technical testing is appropriate, we define the scope and authorisation before any assessment activity begins.', 'A penetration test should only be performed against systems the client owns or has explicit authorisation to test.']],
|
||||
['Will the assessment disrupt our systems?', ['The initial diagnostic is designed to understand security posture without unnecessarily interfering with normal operations.', 'If deeper testing is recommended, we agree on the scope, method, authorisation, and potential operational considerations before that work begins.']],
|
||||
['We already have an IT provider. Why would we need this?', ['Your IT provider may already be doing valuable work.', 'The diagnostic helps establish what is currently covered, what responsibilities sit elsewhere, and whether any meaningful gaps remain.', 'The goal is not to replace a capable provider. It is to make sure assumptions about coverage match reality.']],
|
||||
];
|
||||
|
||||
const gaps = [
|
||||
['users', 'An old account keeps access.'],
|
||||
['shield', 'MFA never gets switched on somewhere important.'],
|
||||
['layers', 'A vendor covers less than everyone assumes.'],
|
||||
['mail', 'An employee receives a convincing payment request at exactly the wrong moment.'],
|
||||
];
|
||||
|
||||
const visual =
|
||||
panelBar('Most security gaps are quiet.') +
|
||||
`<div style="padding:20px;display:grid;gap:10px">${gaps
|
||||
.map(([ic, t], i) => `<div class="row" style="border:1px solid var(--mist);border-radius:12px;padding:12px 14px"><span class="ico">${icon(ic)}</span><span class="name" style="white-space:normal">${t}</span><span class="status"><span class="dot" style="background:${i === 3 ? '#f5a524' : '#e5484d'};box-shadow:none"></span></span></div>`)
|
||||
.join('')}</div>`;
|
||||
|
||||
export default {
|
||||
path: '/consulting/cybersecurity/',
|
||||
title: 'Cybersecurity Consulting for Businesses | Dylan Mazzei',
|
||||
description: 'Cybersecurity consulting for businesses covering security posture, vulnerabilities, fraud defences, access controls, compliance, vendors, and incident readiness.',
|
||||
schema: [faqSchema(faqs)],
|
||||
body:
|
||||
heroBlock({
|
||||
id: 'cy-hero',
|
||||
crumb: 'Cybersecurity',
|
||||
kicker: 'CYBERSECURITY CONSULTING',
|
||||
h1: 'Good Security Starts With Knowing Where You Stand.',
|
||||
lead: 'Most security gaps are quiet.',
|
||||
paras: [
|
||||
'An old account keeps access. MFA never gets switched on somewhere important. A vendor covers less than everyone assumes. An employee receives a convincing payment request at exactly the wrong moment.',
|
||||
'*My security team and I look at the business from the outside in and the inside out, so you can see where the exposure actually is and decide what deserves attention first.',
|
||||
],
|
||||
visual,
|
||||
}) +
|
||||
coversBlock({
|
||||
id: 'cy-covers',
|
||||
kicker: 'WHERE EXPOSURE HIDES',
|
||||
h2: 'Security Is Usually More Than One Big Vulnerability.',
|
||||
intro: ['A company’s security posture is shaped by dozens of smaller decisions across accounts, people, vendors, systems, and processes.', '*We bring those pieces into one view.'],
|
||||
items: [
|
||||
['shield', 'Systems & Access', ['Security posture assessment across domains, email, cloud accounts, and access controls', 'Review of externally visible vulnerabilities', 'MFA coverage and password practices', 'Administrator access and privilege review', 'Employee onboarding and offboarding gaps']],
|
||||
['mail', 'People & Payments', ['Email and payment-fraud defences', 'Wire and invoice-fraud controls', 'Phishing exposure', 'Practical employee security awareness', 'Internal processes around sensitive requests and approvals']],
|
||||
['layers', 'Compliance & Readiness', ['Gap review against relevant client, insurer, or industry requirements', 'SOC 2 and ISO 27001 readiness considerations', 'IT provider, MSP, and security-tool review', 'Basic incident-response planning', 'Prioritised remediation roadmap']],
|
||||
],
|
||||
}) +
|
||||
whoBlock({
|
||||
id: 'cy-who',
|
||||
h2: 'When “We Have IT” Isn’t the Same as “We Know Our Risk.”',
|
||||
items: [
|
||||
['target', 'You’ve Never Established a Security Baseline.', ['The business has software, email, cloud accounts, employees, and vendors, but nobody has stepped back and looked at the exposure as a whole.', 'The diagnostic gives you that baseline.']],
|
||||
['users', 'Clients Are Starting to Ask Harder Questions.', ['Enterprise customers, partners, and insurers increasingly want to know how you manage access, data, vendors, and security controls.', 'We identify the gaps before the questionnaire becomes the project.']],
|
||||
['spark', 'Something Happened Close Enough to Get Your Attention.', ['A suspicious login. A fraudulent payment request. A compromised account. An incident at a vendor or another company in your industry.', '*You don’t need panic.', 'You need a clear picture of what your own business looks like.']],
|
||||
],
|
||||
}) +
|
||||
deliveryBlock({
|
||||
id: 'cy-delivery',
|
||||
h2: 'See the Risk. Rank the Risk. Address the Risk.',
|
||||
intro: ['Cybersecurity follows the same diagnostic-first approach behind my consulting work.'],
|
||||
steps: [
|
||||
['Establish the Baseline', ['The Business Diagnostic includes an initial review of the company’s security posture.', 'We look at the systems, access, controls, vendors, and processes that shape the business’s exposure.']],
|
||||
['Go Deeper Where It Matters', ['When the baseline identifies areas that warrant deeper assessment, my cybersecurity team conducts the appropriate authorised review using the assessment capabilities we use in our own security business.', 'All assessment activity is limited to systems the business owns or is authorised to test, with written permission defining the scope.']],
|
||||
['Prioritise the Fixes', ['You receive a scored report that ranks findings by risk, business impact, and the effort or cost involved in addressing them.', 'The objective isn’t to produce the longest vulnerability list.', '*It’s to make clear what deserves attention first.']],
|
||||
],
|
||||
}) +
|
||||
operatorBlock({
|
||||
id: 'cy-op',
|
||||
h2: 'Security Has to Work on a Normal Tuesday.',
|
||||
paras: ['*A policy nobody follows isn’t much protection.', 'Neither is a security tool everyone assumes somebody else configured.', 'That practical view comes from operating businesses where technology and security are part of the day-to-day responsibility.'],
|
||||
side: '<article class="card card-dark"><h3 class="h-lg">The strongest controls are the ones that still work while invoices move, employees join and leave, vendors need access, and everyone tries to get through a normal workday.</h3></article>',
|
||||
cta: 'EXPLORE MY VENTURES',
|
||||
}) +
|
||||
faqBlock({ id: 'cy-faq', h2: 'Before We Look Under the Hood.', items: faqs }) +
|
||||
closingBlock({
|
||||
id: 'cy-close',
|
||||
kicker: 'START WITH VISIBILITY',
|
||||
h2: 'Know What You’re Protecting and Where the Gaps Are.',
|
||||
paras: ['You don’t need to treat every security concern like an emergency.', 'You do need a clear view of your systems, access, vendors, people, and existing controls.', '*We’ll establish the baseline, rank what matters, and give you a practical place to start.'],
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import { heroBlock, coversBlock, whoBlock, deliveryBlock, operatorBlock, faqBlock, closingBlock, faqSchema, panelBar, panelRow, panelRowAccent } from '../service.mjs';
|
||||
|
||||
const faqs = [
|
||||
['Do you work with both EdTech companies and schools?', ['Yes.', 'I work with companies developing or bringing education products to market as well as schools and educational organizations evaluating or implementing technology.', 'The diagnostic is structured around which side of that equation you are on.']],
|
||||
['Can you help with education hardware, not just software?', ['Yes.', 'My education work includes hardware as well as software, including product strategy, sourcing and manufacturing considerations, R&D partnerships, device management, and implementation at classroom scale.']],
|
||||
['We are pre-product. Is it too early?', ['Not necessarily.', 'The period before significant product investment can be a valuable time to test assumptions around users, market fit, procurement, implementation, manufacturing, and adoption.', 'The objective is not to make the product more complicated. It is to understand the environment it will eventually enter.']],
|
||||
['Do you cover higher education and corporate training, or just K-12?', ['The work is not limited to K-12.', 'The appropriate strategy depends on the institution, learner, product, procurement environment, and market. Higher education, training organizations, and other learning environments can also fit the engagement.']],
|
||||
];
|
||||
|
||||
const sides = [
|
||||
['building', 'Schools have procurement cycles.'],
|
||||
['users', 'Teachers have classrooms to run.'],
|
||||
['edu', 'Students have different needs.'],
|
||||
['chip', 'Technology teams have infrastructure to manage.'],
|
||||
];
|
||||
|
||||
export default {
|
||||
path: '/consulting/education/',
|
||||
title: 'Education & EdTech Consulting | Dylan Mazzei',
|
||||
description: 'Education and EdTech consulting for product strategy, school procurement, technology adoption, hardware, AI, privacy, and education market entry.',
|
||||
schema: [faqSchema(faqs)],
|
||||
body:
|
||||
heroBlock({
|
||||
id: 'ed-hero',
|
||||
crumb: 'Education',
|
||||
kicker: 'EDUCATION CONSULTING',
|
||||
h1: 'Better Learning Starts With Better Possibilities.',
|
||||
lead: 'A strong product is only part of the equation in education.',
|
||||
paras: [
|
||||
'Schools have procurement cycles. Teachers have classrooms to run. Students have different needs. Technology teams have infrastructure to manage. And every new tool eventually has to prove that it belongs in the day.',
|
||||
'Through my work with READTYCH and education partnerships, I’ve worked across product development, market strategy, technology adoption, and the realities of bringing new ideas into education.',
|
||||
'*My teams and I help connect those sides.',
|
||||
],
|
||||
visual:
|
||||
panelBar('FROM PRODUCT TO CLASSROOM') +
|
||||
`<div style="padding:20px;display:grid;gap:10px">${sides.map(([ic, t]) => panelRow(ic, t)).join('')}${panelRowAccent('And every new tool eventually has to prove that it belongs in the day.')}</div>`,
|
||||
}) +
|
||||
coversBlock({
|
||||
id: 'ed-covers',
|
||||
kicker: 'FROM PRODUCT TO CLASSROOM',
|
||||
h2: 'Make the Strategy Work Where Learning Actually Happens.',
|
||||
intro: ['Education has more than one decision-maker and more than one definition of success.', '*We look at the product, institution, technology, and people together.'],
|
||||
items: [
|
||||
['chip', 'Product & Technology', ['EdTech hardware and software product strategy', 'Pilot-to-product planning', 'Education hardware sourcing and manufacturing', 'R&D partnership considerations', 'Device management at classroom scale', 'AI applications across learning and operations']],
|
||||
['route', 'Market & Procurement', ['Education market-entry strategy', 'School and institutional procurement cycles', 'Decision-maker and stakeholder mapping', 'Sales-cycle planning for EdTech companies', 'US and international education market considerations']],
|
||||
['shield', 'Adoption & Readiness', ['Institutional technology evaluation', 'Vendor-selection frameworks', 'Implementation and adoption planning', 'Exam integrity and proctoring strategy', 'Student data privacy considerations', 'FERPA and COPPA readiness']],
|
||||
],
|
||||
}) +
|
||||
whoBlock({
|
||||
id: 'ed-who',
|
||||
h2: 'Different Sides of Education. One Common Challenge.',
|
||||
items: [
|
||||
['rocket', 'You’re Building for Education.', ['EdTech founders have to think beyond whether the product works.', 'The product also has to fit procurement, implementation, classroom workflows, privacy expectations, and the people responsible for adopting it.']],
|
||||
['building', 'You’re Choosing Technology for an Institution.', ['A strong feature list doesn’t guarantee a strong rollout.', 'We help schools and educational organizations evaluate technology around what they need, how it will be implemented, and whether the people using it can realistically adopt it.']],
|
||||
['compass', 'You’re Entering Education From Another Industry.', ['Education has its own timelines, stakeholders, requirements, and expectations.', 'We help companies understand those differences before bringing assumptions from another market into the classroom.']],
|
||||
],
|
||||
}) +
|
||||
deliveryBlock({
|
||||
id: 'ed-delivery',
|
||||
h2: 'Start With the People Who Have to Use It.',
|
||||
intro: ['Education consulting follows the same diagnostic-first approach behind my consulting work.'],
|
||||
steps: [
|
||||
['Understand the Environment', ['The Business Diagnostic evaluates the product, institution, market, or procurement strategy against the environment where it actually has to work.', 'For an EdTech company, that may mean product readiness, sales cycles, manufacturing, or market entry.', 'For an institution, it may mean technology requirements, vendor evaluation, implementation, and adoption.']],
|
||||
['Map the Priorities', ['We identify what deserves attention first and build a roadmap around the real stakeholders involved.', 'The buyer matters.', '*So does the person expected to use what they bought.']],
|
||||
['Move From Plan to Adoption', ['When execution makes sense, the 90-Day Education Sprint moves the roadmap forward.', 'For EdTech companies, that can include product strategy, market positioning, sales-cycle alignment, or manufacturing.', 'For institutions, it can include vendor evaluation, implementation planning, and technology adoption.']],
|
||||
],
|
||||
}) +
|
||||
operatorBlock({
|
||||
id: 'ed-op',
|
||||
h2: 'The Classroom Gets the Final Vote.',
|
||||
paras: ['A product can look excellent in a demonstration.', '*The real test comes later.', 'That perspective comes from working inside education technology, including my involvement with READTYCH and other ventures.', 'The closer the strategy gets to the classroom, the more useful it becomes.'],
|
||||
side: '<div class="qlist card card-dark" style="gap:10px"><p>Can a teacher use it without creating more work?</p><p>Can students understand it?</p><p>Can the institution support it?</p><p>Does it fit into the day instead of fighting against it?</p></div>',
|
||||
cta: 'EXPLORE MY VENTURES',
|
||||
}) +
|
||||
faqBlock({ id: 'ed-faq', h2: 'Before We Put the Plan Into Practice.', items: faqs }) +
|
||||
closingBlock({
|
||||
id: 'ed-close',
|
||||
kicker: 'FROM IDEA TO ADOPTION',
|
||||
h2: 'Give Good Education Ideas a Better Chance to Work.',
|
||||
paras: ['A product still has to fit the institution. A purchase still has to work for the people using it. And a strategy still has to survive the realities of implementation.', '*We’ll bring those pieces together, identify what deserves attention, and create a clearer path from the idea to actual use.'],
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
import { icon, eyebrow, btn } from '../layout.mjs';
|
||||
import { heroBlock, whoBlock, deliveryBlock, operatorBlock, faqBlock, closingBlock, faqSchema } from '../service.mjs';
|
||||
|
||||
const seats = [
|
||||
{
|
||||
name: 'Fractional CMO', icon: 'megaphone', h: 'Marketing leadership from strategy through execution.',
|
||||
items: ['Go-to-market strategy and execution oversight', 'Paid media and SEO budget allocation', 'Marketing team leadership', 'Vendor management', 'Funnel and conversion improvement', 'Social and content strategy', 'SEO and AI search strategy'],
|
||||
backed: 'Backed by specialist teams across SEO, AI search, paid media, content, and marketing execution.',
|
||||
cta: ['EXPLORE MARKETING CONSULTING', '/consulting/marketing/'],
|
||||
},
|
||||
{
|
||||
name: 'Fractional COO', icon: 'gauge', h: 'Operational leadership for the next stage of the company.',
|
||||
items: ['Operational priorities and KPI management', 'Process documentation and SOP development', 'Team structure and hiring plans', 'Performance management', 'Cost structure review', 'Vendor negotiation', 'Systems and workflow improvement'],
|
||||
backed: 'Backed by operations and automation capabilities that can help put the systems into practice.',
|
||||
cta: ['EXPLORE BUSINESS OPTIMIZATION', '/consulting/business-optimization/'],
|
||||
},
|
||||
{
|
||||
name: 'Fractional CISO', icon: 'shield', h: 'Security leadership connected to the business itself.',
|
||||
items: ['Security posture oversight', 'Risk management', 'Compliance-readiness planning', 'IT vendor and access-control review', 'Incident-response planning', 'Employee security awareness', 'Security priorities and accountability'],
|
||||
backed: 'Backed by Dylan’s cybersecurity team and assessment capabilities for authorised security review and ongoing support.',
|
||||
cta: ['EXPLORE CYBERSECURITY CONSULTING', '/consulting/cybersecurity/'],
|
||||
},
|
||||
{
|
||||
name: 'Fractional Chief AI Officer', icon: 'chip', h: 'AI leadership with an operating mandate.',
|
||||
items: ['AI strategy and governance', 'Responsible AI policies', 'Workflow automation across departments', 'AI tool evaluation and integration', 'Software-stack rationalisation', 'Internal AI adoption and training', 'Identification of practical AI use cases'],
|
||||
backed: 'Backed by teams already implementing AI and automation across Dylan’s own operating portfolio.',
|
||||
cta: null,
|
||||
},
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
['What is a fractional executive?', ['A fractional executive is an experienced senior leader who takes responsibility for an executive function without joining the company as a traditional full-time employee.', 'The role can provide companies with senior leadership when they need the function owned but aren\'t yet ready to make a permanent executive hire.']],
|
||||
['How does fractional leadership work?', ['The engagement begins with a Business Diagnostic and defined 90-day objectives.', 'From there, I join the company’s operating cadence, work with the relevant teams, establish priorities and KPIs, and bring in specialist support where appropriate.']],
|
||||
['What fractional roles are available?', ['Current roles include Fractional CMO, Fractional COO, Fractional CISO, and Fractional Chief AI Officer.', 'The appropriate seat depends on the part of the business that needs senior ownership.']],
|
||||
['How do I get started?', ['Start with the Business Diagnostic.', 'It gives us enough context to determine what the business needs, whether fractional leadership is the right model, and which executive seat would create the most value.']],
|
||||
];
|
||||
|
||||
const APPLY = 'APPLY FOR A FRACTIONAL SEAT';
|
||||
|
||||
const visual = `<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><strong>FOUR EXECUTIVE SEATS</strong></div>
|
||||
<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
${['CMO', 'COO', 'CISO', 'Chief AI Officer'].map((r) => `<div class="kpi" style="display:grid;gap:14px;padding:18px"><span>FRACTIONAL</span><b style="font-size:24px;line-height:1.1">${r}</b></div>`).join('')}
|
||||
</div>`;
|
||||
|
||||
export default {
|
||||
path: '/consulting/fractional/',
|
||||
title: 'Fractional Executive Leadership | Dylan Mazzei',
|
||||
description: 'Fractional executive leadership from Dylan Mazzei across marketing, operations, cybersecurity, and AI, backed by specialist operating teams.',
|
||||
schema: [faqSchema(faqs)],
|
||||
body:
|
||||
heroBlock({
|
||||
id: 'fr-hero',
|
||||
crumb: 'Fractional Leadership',
|
||||
kicker: 'FRACTIONAL LEADERSHIP',
|
||||
h1: 'Bring Senior Leadership Into the Business Earlier.',
|
||||
lead: 'At some point, a business needs more than advice, but a full-time executive hire may not make sense yet.',
|
||||
paras: [
|
||||
'*That is where fractional leadership fits.',
|
||||
'I step into the executive seat, work directly with your existing team, own priorities and KPIs, and bring in specialist support from across my operating companies where the work calls for it.',
|
||||
'You get executive leadership now, with the flexibility to make the permanent hire when the business is ready.',
|
||||
],
|
||||
cta: APPLY,
|
||||
visual,
|
||||
}) +
|
||||
`
|
||||
<section class="section" aria-labelledby="fr-stage">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal">
|
||||
<div class="stack">
|
||||
${eyebrow('THE RIGHT STAGE FOR FRACTIONAL')}
|
||||
<h2 id="fr-stage" class="h-display">When the Business Needs Someone in the Seat.</h2>
|
||||
<div class="prose">
|
||||
<p class="body-ink">Consulting can tell you what should happen next.</p>
|
||||
<p>Fractional leadership goes further. I become part of the company's operating rhythm and take responsibility for moving that part of the business forward.</p>
|
||||
<p>That can mean leading the existing team, setting priorities, managing vendors, establishing KPIs, improving systems, and making sure strategy turns into actual work.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-sm">
|
||||
<p class="statement">The role is fractional.</p>
|
||||
<article class="card card-dark"><h3 class="h-lg">The responsibility isn’t.</h3></article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="seats" aria-labelledby="fr-seats">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('FOUR EXECUTIVE SEATS')}<h2 id="fr-seats" class="h-display">One Executive. A Wider Operating Bench.</h2></div>
|
||||
<div class="prose">
|
||||
<p class="body-ink">Every engagement puts me directly in the executive role.</p>
|
||||
<p>Behind me are specialist teams across the businesses I operate, giving the engagement access to execution capabilities beyond one person’s calendar.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="seats reveal">
|
||||
${seats
|
||||
.map(
|
||||
(s) => `<article class="plan seat">
|
||||
<div class="plan-name"><span>${s.name}</span><span class="ic">${icon(s.icon)}</span></div>
|
||||
<h3>${s.h}</h3>
|
||||
<ul>${s.items.map((it) => `<li>${icon('check')}${it}</li>`).join('')}</ul>
|
||||
<p class="backed">${s.backed}</p>
|
||||
${s.cta ? btn(s.cta[0], s.cta[1], 'ghost') : ''}
|
||||
</article>`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>` +
|
||||
deliveryBlock({
|
||||
id: 'fr-delivery',
|
||||
kicker: 'HOW THE ENGAGEMENT WORKS',
|
||||
h2: 'Step Into the Role. Stay Close to the Work.',
|
||||
intro: [],
|
||||
steps: [
|
||||
['Establish the Baseline', ['Every fractional engagement begins with the Business Diagnostic.', 'We establish the current position, identify the priorities, and define what the first 90 days need to accomplish.']],
|
||||
['Take the Seat', ['I step into the agreed executive role and work directly with the founder, leadership team, and relevant department.', 'The relationship is designed to function as part of the company, not as an outside report delivered once a month.']],
|
||||
['Set the Operating Rhythm', ['Engagements typically include weekly executive meetings, direct access to communication, and regular department reviews.', 'Priorities are translated into measurable objectives, ownership, and an operating cadence the team can follow.']],
|
||||
['Bring in the Bench', ['Where specialist execution is needed, the relevant capabilities across my operating teams can support the work.', 'This keeps strategy and execution connected.']],
|
||||
['Build Toward What Comes Next', ['Fractional leadership should help the business mature.', 'That may eventually mean developing an internal leader, strengthening the existing team, or hiring a permanent executive into a function that is now ready for one.']],
|
||||
],
|
||||
foot: '<div class="card card-dark" style="background:rgba(255,255,255,.04);border:1px solid var(--dark-line);max-width:720px"><p style="color:#fff">Engagements begin with a minimum three-month commitment and include a 30-day notice period.</p><p>Fractional seats are intentionally limited to three concurrent engagements across all roles.</p></div>',
|
||||
cta: APPLY,
|
||||
}) +
|
||||
whoBlock({
|
||||
id: 'fr-who',
|
||||
h2: 'The Company Has a Team. Now It Needs Leadership at the Next Level.',
|
||||
items: [
|
||||
['rocket', 'The Business Has Reached a New Stage.', ['You have revenue, customers, and people doing the work.', 'Now the decisions around marketing, operations, security, or AI require more experienced ownership.']],
|
||||
['users', 'The Founder Is Carrying an Executive Function.', ['You are still acting as the CMO, COO, CISO, or AI lead because nobody else owns the function yet.', 'Fractional leadership creates somewhere for that responsibility to go.']],
|
||||
['target', 'The Team Can Execute but Needs Direction.', ['There are capable people already inside the company.', 'What is missing is someone senior enough to set priorities, connect the work to the business, make decisions, and hold the function accountable.']],
|
||||
],
|
||||
}) +
|
||||
operatorBlock({
|
||||
id: 'fr-op',
|
||||
h2: 'I Don’t Advise From the Sidelines.',
|
||||
paras: ['*Fractional leadership only works if the person in the seat is willing to own decisions.', 'That is how I approach my own companies, and it is how I approach a fractional role.', 'I work with the team, review the numbers, make the calls that belong to the role, and stay close enough to see whether those decisions are working.'],
|
||||
link: ['You can see the companies I operate today and the businesses and exits that shaped that experience.', '/portfolio/'],
|
||||
side: '<div class="portrait"><img src="/assets/photos/dylan-livex.jpg" alt="Dylan Mazzei at LIVEX" width="1100" height="1467" loading="lazy" style="object-position:center 25%"></div>',
|
||||
}) +
|
||||
faqBlock({ id: 'fr-faq', h2: 'Before You Open a Seat.', items: faqs }) +
|
||||
closingBlock({
|
||||
id: 'fr-close',
|
||||
kicker: 'WHEN THE BUSINESS IS READY FOR MORE',
|
||||
h2: 'Put Experience in the Seat.',
|
||||
paras: ['You may already have the people who can do the work.', 'What they need next may be someone who can set the direction, own the decisions, and lead the function alongside them.', '*That is what the fractional seat is for.'],
|
||||
cta: APPLY,
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,251 @@
|
||||
import { icon, eyebrow, btn, textLink, mark } from '../layout.mjs';
|
||||
|
||||
const ventures = [
|
||||
['search', 'MARKETING & AI SEARCH'],
|
||||
['chip', 'AI SOFTWARE'],
|
||||
['bottle', 'SPIRITS'],
|
||||
['edu', 'EDUCATION TECHNOLOGY'],
|
||||
['shield', 'CYBERSECURITY'],
|
||||
['bag', 'E-COMMERCE'],
|
||||
];
|
||||
|
||||
const exits = [
|
||||
['Insurance', 'I grew an insurance business around strong client relationships, a clear understanding of the market, and service people could trust.', '<path d="M24 5 8 11v11c0 10 7 17 16 21 9-4 16-11 16-21V11z"/><path d="m17 24 5 5 9-10"/>'],
|
||||
['Marketing', 'Led an agency focused on helping businesses reach the right audiences, strengthen their presence, and turn attention into growth.', '<path d="M8 20v8h6l14 8V12l-14 8z"/><path d="M34 18c3 2 3 10 0 12M14 28l2 9h5l-2-9"/>'],
|
||||
['Home Healthcare', 'Grew a people-first business in an industry where trust, quality of care, and responsible leadership mattered every day.', '<path d="M6 22 24 7l18 15M10 20v21h28V20"/><path d="M24 35c-6-4-8-7-8-10a4.2 4.2 0 0 1 8-1.5A4.2 4.2 0 0 1 32 25c0 3-2 6-8 10z"/>'],
|
||||
['Apparel', 'Took a consumer concept into the market, shaping the product, brand, and customer experience from the ground up.', '<path d="M17 6 6 12l4 9 5-2v23h18V19l5 2 4-9-11-6c-1 3-4 5-7 5s-6-2-7-5z"/>'],
|
||||
];
|
||||
|
||||
const consult = [
|
||||
['<circle cx="11" cy="11" r="6.5"/><path d="m16 16 4.5 4.5"/>', 'We look closely at the business', ''],
|
||||
['<path d="M4 5h16v11H9l-5 4z"/>', 'Talk through what is getting in the way', ''],
|
||||
['<circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3.2"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/>', 'Figure out where your attention can make the biggest difference', ''],
|
||||
['<path d="M12 3 4 6v6c0 4.5 3.4 7.6 8 9 4.6-1.4 8-4.5 8-9V6z"/><path d="m8.5 12 2.5 2.5 4.5-5"/>', 'Every engagement starts with a diagnostic.', 'Not because I already know the answer, but because your business deserves one that actually fits.'],
|
||||
];
|
||||
|
||||
const ventureRows = ventures
|
||||
.map(([ic, name]) => `<div class="row"><span class="ico">${icon(ic)}</span><span class="name">${name}</span><span class="status"><span class="dot"></span>Active</span></div>`)
|
||||
.join('');
|
||||
const exitRows = exits
|
||||
.map(([name]) => `<div class="row">${icon('check', 'check')}<span class="name">${name}</span><span class="status">Exited</span></div>`)
|
||||
.join('');
|
||||
const exitWords = ['Insurance.', 'Marketing.', 'Home Healthcare.', 'Apparel.'];
|
||||
const arrowUR = '<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 10 10 4M5 4h5v5"/></svg>';
|
||||
|
||||
export default {
|
||||
path: '/',
|
||||
title: 'Dylan Mazzei | Founder, Operator & Entrepreneur',
|
||||
description: 'Meet Dylan Mazzei, a founder and operator leading ventures across AI, software, spirits, education, cybersecurity, e-commerce, and more.',
|
||||
head: '<link rel="stylesheet" href="/assets/home.css">',
|
||||
scripts: '<script src="/assets/o2/three.min.js" defer></script>\n<script src="/assets/home.js" defer></script>',
|
||||
body: `
|
||||
<div class="o2">
|
||||
|
||||
<section id="hero" aria-labelledby="hero-title">
|
||||
<div class="beam" aria-hidden="true"></div>
|
||||
<div class="floaters" id="floaters" aria-hidden="true"></div>
|
||||
<div class="hero-in">
|
||||
<div class="emblem" id="emblem"><img alt="Dylan Mazzei monogram" src="/assets/o2/dylan-monogram.webp" width="720" height="480"></div>
|
||||
${eyebrow('DYLAN MAZZEI | FOUNDER & OPERATOR')}
|
||||
<h1 id="hero-title"><span class="grad">One Good Idea Has Never</span> <span class="grad2">Been Enough for Me.</span></h1>
|
||||
<p class="lead">I’ve spent the last eight years turning ideas into companies across marketing, AI, software, spirits, education, cybersecurity, e-commerce, and more. Four have been sold. Others are growing. And I’m nowhere near finished building.</p>
|
||||
<div class="btn-row">${btn('MY STORY', '/about/')}${btn('EXPLORE MY VENTURES', '#ventures', 'ghost')}</div>
|
||||
<div class="icon-row" id="iconRow" aria-hidden="true"></div>
|
||||
</div>
|
||||
<div class="scroll-note" aria-hidden="true"><i></i></div>
|
||||
</section>
|
||||
|
||||
<section class="wsec" id="words" aria-label="Four exits">
|
||||
<div class="wsec-in">
|
||||
<div class="cube-glow" aria-hidden="true"></div>
|
||||
<div class="cube" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
<div class="w-top">${eyebrow('4 EXITS')}</div>
|
||||
<div class="wmask" aria-hidden="true"><div id="wTrack">${[...exitWords, ...exitWords].map((w) => `<span>${w}</span>`).join('')}</div></div>
|
||||
<p class="sr-only">Insurance. Marketing. Home Healthcare. Apparel.</p>
|
||||
<div class="w-side"><p>Four have been sold. Others are growing. And I’m nowhere near finished building.</p>${btn('FOLLOW THE JOURNEY', '#journey', 'ghost', 'btn-sm')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="building" aria-labelledby="building-title">
|
||||
<div class="wrap bgrid">
|
||||
<div class="bhead reveal">
|
||||
${eyebrow('CURRENT VENTURES')}
|
||||
<h2 id="building-title" class="h-display">The Companies I’m Leading Today</h2>
|
||||
<p class="lead">I like being close to the decisions that shape a company, especially when there is something new to figure out.</p>
|
||||
<div>${btn('EXPLORE MY VENTURES', '/portfolio/')}</div>
|
||||
</div>
|
||||
<div class="cluster-fit reveal">
|
||||
<div class="cluster" aria-hidden="true">
|
||||
<div class="panel dash">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><span>Portfolio</span><span class="panel-url">dylanmazzei.com/portfolio</span></div>
|
||||
<div class="dash-body">
|
||||
<div class="dash-head"><div><small>CURRENT VENTURES</small><strong>The Companies I’m Leading Today</strong></div></div>
|
||||
<div class="kpis">
|
||||
<div class="kpi"><span>EXITS</span><b>4</b></div>
|
||||
<div class="kpi"><span>INDUSTRIES</span><b>6</b></div>
|
||||
<div class="kpi"><span>YEARS OPERATING</span><b>8+</b></div>
|
||||
<div class="kpi"><span>WHERE I WORK</span><b>4</b></div>
|
||||
</div>
|
||||
<div class="dash-cols">
|
||||
<div class="list"><div class="list-head"><span>CURRENT VENTURES</span><span></span></div>${ventureRows}</div>
|
||||
<div class="list"><div class="list-head"><span>4 EXITS</span><span></span></div>${exitRows}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel profile">
|
||||
<img src="/assets/dylan-portrait.jpg" alt="" width="900" height="900">
|
||||
<div class="profile-meta">
|
||||
<strong>Dylan Mazzei</strong>
|
||||
<span class="prole">FOUNDER & OPERATOR</span>
|
||||
<div class="chips"><span class="tag">Texas</span><span class="tag">Florida</span><span class="tag">New York</span><span class="tag">Dubai</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-dark code">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><span class="file">${icon('brace')}dylan.json</span></div>
|
||||
<pre class="code-pre"><span class="ln"><span class="p">{</span></span><span class="ln"> <span class="k">"role"</span><span class="p">:</span> <span class="s">"Founder & Operator"</span><span class="p">,</span></span><span class="ln"> <span class="k">"exits"</span><span class="p">:</span> <span class="n">4</span><span class="p">,</span></span><span class="ln"> <span class="k">"industries"</span><span class="p">:</span> <span class="n">6</span><span class="p">,</span></span><span class="ln"> <span class="k">"yearsOperating"</span><span class="p">:</span> <span class="s">"8+"</span><span class="p">,</span></span><span class="ln"> <span class="k">"whereIWork"</span><span class="p">: [</span></span><span class="ln"> <span class="s">"Texas"</span><span class="p">,</span> <span class="s">"Florida"</span><span class="p">,</span></span><span class="ln"> <span class="s">"New York"</span><span class="p">,</span> <span class="s">"Dubai"</span></span><span class="ln"> <span class="p">],</span></span><span class="ln"> <span class="k">"finishedBuilding"</span><span class="p">:</span> <span class="n">false</span><span class="cursor"></span></span><span class="ln"><span class="p">}</span></span></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="ventures" aria-labelledby="ventures-title">
|
||||
<div class="wrap">
|
||||
<div class="vhead reveal">
|
||||
${eyebrow('CURRENT VENTURES')}
|
||||
<h2 id="ventures-title" class="sr-only">Current ventures</h2>
|
||||
<p class="lead">Today, I lead businesses across AI, marketing, software, spirits, education technology, cybersecurity, and e-commerce. They operate in very different worlds, which keeps me curious.</p>
|
||||
</div>
|
||||
<ul class="sr-only">
|
||||
<li>MARKETING & AI SEARCH Leading teams through a constantly changing search landscape.</li>
|
||||
<li>AI SOFTWARE Building around new opportunities created by AI.</li>
|
||||
<li>SPIRITS Taking a consumer brand from idea to market.</li>
|
||||
<li>EDUCATION TECHNOLOGY Creating technology designed for real classrooms.</li>
|
||||
<li>CYBERSECURITY Solving security challenges for growing businesses.</li>
|
||||
<li>E-COMMERCE Turning product ideas into businesses built around real customers.</li>
|
||||
</ul>
|
||||
<div class="vtabs reveal" id="vtabs" role="tablist" aria-label="Ventures"></div>
|
||||
<div class="vpanel reveal" id="vpanel">
|
||||
<div class="vinfo">
|
||||
<div><span class="vnum" id="vnum">01 / 06</span></div>
|
||||
<div class="vt" id="vt" aria-live="polite"><h3 id="vname">MARKETING & AI SEARCH</h3><p id="vdesc">Leading teams through a constantly changing search landscape.</p></div>
|
||||
<div class="btn-row">${btn('EXPLORE MY VENTURES', '/portfolio/', 'primary', 'btn-sm')}</div>
|
||||
</div>
|
||||
<canvas id="vcanvas" aria-hidden="true"></canvas>
|
||||
<div class="varrows">
|
||||
<button id="vprev" type="button" aria-label="Previous venture"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13 8H3M7 4 3 8l4 4"/></svg></button>
|
||||
<button id="vnext" type="button" aria-label="Next venture"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="journey" aria-labelledby="journey-title">
|
||||
<div class="wrap">
|
||||
<div class="jgrid">
|
||||
<div class="jhead reveal">
|
||||
${eyebrow('THE JOURNEY SO FAR')}
|
||||
<h2 id="journey-title" class="h-display">Good Opportunities Rarely Look the Same.</h2>
|
||||
<p class="lead">I’ve never looked at opportunity through the lens of one industry. Over the years, that curiosity has taken me into very different businesses, including four that grew into successful exits.</p>
|
||||
</div>
|
||||
<div class="jstage" id="jstage">
|
||||
<div class="floor" aria-hidden="true"></div>
|
||||
<div class="pins" aria-hidden="true">
|
||||
${['Insurance', 'Marketing', 'Home Healthcare', 'Apparel', 'Today'].map((p) => `<div class="pn"><i></i><b></b><em>${p}</em></div>`).join('')}
|
||||
</div>
|
||||
<canvas id="fw" aria-hidden="true"></canvas>
|
||||
<p class="jfinal" id="jfinal">Others are growing. And I’m nowhere near finished building.</p>
|
||||
<button class="btn btn-ghost btn-sm jreplay" id="jreplay" type="button">Replay</button>
|
||||
</div>
|
||||
<div class="jbody">
|
||||
<ul class="jlist">
|
||||
${exits.map(([h, p, path]) => `<li class="jx"><div class="ji"><svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${path}</svg></div><div><h3>${h}</h3><p>${p}</p></div></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><span>EXITS</span><b id="cEx">4</b></div>
|
||||
<div class="stat"><span>INDUSTRIES</span><b id="cInd">6</b></div>
|
||||
<div class="stat"><span>YEARS OPERATING</span><b id="cYr">8+</b></div>
|
||||
<div class="stat stat-note">
|
||||
<p>Different businesses have called for different decisions, teams, and ways of thinking. That range still shapes how I approach every opportunity today.</p>
|
||||
${textLink('FOLLOW THE JOURNEY', '/about/')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="about" aria-labelledby="about-title">
|
||||
<div class="wrap agrid">
|
||||
<div class="stack">
|
||||
${eyebrow('ABOUT DYLAN')}
|
||||
<h2 id="about-title" class="awords"><span class="grad">Curiosity</span><span class="grad">Got Me Started.</span><span class="neon">Ambition</span><span class="neon n2">Kept Me Moving.</span></h2>
|
||||
</div>
|
||||
<div class="atext reveal">
|
||||
<p class="q">I have always been more interested in what could be possible than what already exists.</p>
|
||||
<p class="lead">That curiosity has taken me into industries I never expected to enter, across markets, teams, and ideas that looked nothing alike from the outside. What connects them is the part I enjoy most: seeing potential early, bringing the right people around it, and giving an idea the chance to become something real.</p>
|
||||
<p class="lead">Business is a big part of my story, but it is not the whole story.</p>
|
||||
${btn('GET TO KNOW DYLAN', '/about/')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="consult" aria-labelledby="consult-title">
|
||||
<div class="wrap">
|
||||
<div class="chead">
|
||||
<div class="stack-sm reveal">${eyebrow('CONSULT DYLAN')}<h2 id="consult-title" class="h-display">Sometimes You Need Another Founder in the Room.</h2></div>
|
||||
<div class="stack-sm reveal">
|
||||
<p class="lead" style="color:var(--ink)">I know what it is like to make decisions when the answer isn't obvious, and the outcome belongs to you.</p>
|
||||
<p class="lead">That is the perspective I bring when I work with founders. We look closely at the business, talk through what is getting in the way, and figure out where your attention can make the biggest difference.</p>
|
||||
<div>${btn('WORK WITH DYLAN', '/consulting/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cgrid">
|
||||
${consult.map(([path, h, p]) => `<article class="cc reveal"><div class="cic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${path}</svg></div><h3>${h}</h3>${p ? `<p>${p}</p>` : ''}</article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="press" aria-labelledby="press-title">
|
||||
<div class="wrap">
|
||||
<div class="phead">
|
||||
<div class="stack-sm reveal">${eyebrow('PRESS & MEDIA')}<h2 id="press-title" class="h-display">The Work Spoke. Then I Did.</h2></div>
|
||||
<div class="stack-sm reveal" style="max-width:48ch">
|
||||
<p class="lead">About the businesses. The exits. The ideas. What worked. What didn’t. And what I’m thinking about next.</p>
|
||||
<p class="lead">Some of those conversations made it into interviews, profiles, podcasts, and features.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pgrid">
|
||||
${['Interviews', 'Profiles', 'Podcasts', 'Features'].map((t) => `<a class="pt reveal" href="/press/"><h3>${t}</h3><span class="arrow">${arrowUR}</span></a>`).join('')}
|
||||
</div>
|
||||
<div class="btn-row reveal" style="margin-top:2rem">${btn('SEE THE FEATURES', '/press/', 'ghost')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="poetry" aria-labelledby="poetry-title">
|
||||
<div class="arc" aria-hidden="true"></div>
|
||||
<div class="in-wrap reveal">
|
||||
${eyebrow('A DIFFERENT SIDE')}
|
||||
<h2 id="poetry-title" class="h-display">And Then There’s Poetry.</h2>
|
||||
<p class="poem-line">Not every idea of mine becomes a company.<br>Some become words.</p>
|
||||
<p class="lead">Poetry gives me somewhere to put thoughts that don't belong in a business plan, a strategy session, or a boardroom.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="connect" aria-labelledby="connect-title">
|
||||
<div class="talk" aria-hidden="true"><img class="ribbon" alt="" src="/assets/o2/ribbon-hd.webp" width="3200" height="1800" loading="lazy"><h2>Let’s talk</h2></div>
|
||||
<div class="cbody">
|
||||
${eyebrow('LET’S CONNECT')}
|
||||
<h3 id="connect-title" class="grad">Interesting Things Usually Start With a Conversation.</h3>
|
||||
<div class="prose">
|
||||
<p>Maybe you have an idea taking shape. A business at an interesting crossroads. A partnership worth exploring. Or simply a question you think I might have a perspective on.</p>
|
||||
<p>I’ve learned to pay attention when an interesting conversation begins. Some of the best opportunities in my life have started exactly that way.</p>
|
||||
<p style="color:var(--ink)">If there’s something worth talking about, I’m listening.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('START A CONVERSATION', '/contact/')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
import { icon, eyebrow, btn, textLink, paras, faq } from '../layout.mjs';
|
||||
|
||||
const covers = [
|
||||
['search', 'Search & AI Visibility', ['SEO strategy', 'AI search visibility, including LLMO, GEO, and AEO', 'Schema and entity strategy', 'Local and multi-location visibility']],
|
||||
['route', 'Acquisition & Conversion', ['Paid media', 'Social media management', 'Funnel strategy', 'Conversion opportunities']],
|
||||
['chart', 'Measurement & Marketing Operations', ['Analytics setup', 'Channel and attribution review', 'Marketing team evaluation', 'Agency and vendor evaluation']],
|
||||
];
|
||||
|
||||
const who = [
|
||||
['gauge', 'You’re Spending, but the ROI Is Blurry.', ['Budget is moving across channels, but nobody can clearly connect enough of that activity to revenue.', 'Before spending more, we figure out what the current spend is actually doing.']],
|
||||
['search', 'Organic Growth Has Stopped Behaving Like It Used To.', ['Rankings may still matter, but traditional SEO now shares the discovery journey with AI Overviews, answer engines, LLMs, local results, and other surfaces.', 'If the strategy hasn’t changed, the market already has.']],
|
||||
['rocket', 'Paid Media Works Until You Try to Scale It.', ['A channel can perform well and still have a ceiling.', 'When acquisition starts getting more expensive, we look beyond simply increasing the budget and examine the offer, funnel, search presence, channel mix, and what happens after the click.']],
|
||||
];
|
||||
|
||||
const steps = [
|
||||
['Diagnose', ['We audit analytics, search visibility, paid accounts, funnels, current marketing activity, and the teams or vendors responsible for it.', 'We find the leaks before prescribing the fix.']],
|
||||
['Prioritise', ['You get a clear roadmap showing what deserves attention now, what can wait, and where the strongest opportunities sit.', 'Not twenty priorities.', 'The ones that matter.']],
|
||||
['Execute', ['When execution makes sense, we move into a focused 90-day sprint with specialist support from the same operating ecosystem behind my current ventures.', 'Your existing team can stay involved. So can your existing vendors if they’re doing good work.']],
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
['What is AI search visibility, LLMO, GEO, and AEO?', ['These terms describe different approaches to improving how a business, brand, or content appears across AI-generated answers and modern search experiences.', 'The terminology is still evolving. What matters is making your business understandable, credible, and discoverable across the systems people increasingly use to find answers.']],
|
||||
['Do you execute or advise?', ['Both.', 'Some businesses need a roadmap their existing team can execute. Others need hands-on implementation through a 90-day sprint or ongoing support.', 'The diagnostic determines the right approach.']],
|
||||
['Do you work with existing marketing teams?', ['Yes.', 'I’m not interested in replacing a capable team simply to create more work for mine.', 'If your internal team or existing agency is strong, we can design the strategy around making them more effective.']],
|
||||
['What budget size is needed?', ['There isn’t one universal marketing budget that makes an engagement worthwhile.', 'The more important question is whether there is enough opportunity, complexity, or existing spend for the diagnostic to uncover meaningful improvements.']],
|
||||
];
|
||||
|
||||
const surfaces = ['a search result', 'an AI answer', 'an ad', 'a social platform', 'a local listing'];
|
||||
|
||||
export default {
|
||||
path: '/consulting/marketing/',
|
||||
title: 'Marketing Consulting for Founders | Dylan Mazzei',
|
||||
description: 'Marketing consulting for founders covering SEO, AI search, paid media, funnels, analytics, and digital visibility, starting with a diagnostic.',
|
||||
schema: [
|
||||
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqs.map(([q, a]) => ({ '@type': 'Question', name: q, acceptedAnswer: { '@type': 'Answer', text: a.join(' ') } })) },
|
||||
],
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="m-hero">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
<nav class="crumbs" aria-label="Breadcrumb"><a href="/consulting/">Consulting</a>${icon('chev')}<span>Marketing</span></nav>
|
||||
${eyebrow('MARKETING CONSULTING')}
|
||||
<h1 id="m-hero" class="h-display-lg">Marketing Consulting for a Search World That Won’t Sit Still.</h1>
|
||||
<p class="lead">Search changed. Discovery changed. The customer journey changed with it.</p>
|
||||
<div class="prose">
|
||||
<p>I run marketing businesses, so I’m not watching those changes from the sidelines. My teams work inside SEO, AI search, paid media, content, and conversion every day.</p>
|
||||
<p class="body-ink">The goal isn’t to add another marketing tactic.</p>
|
||||
<p>It’s to figure out what is actually earning attention, what is wasting money, and where your next customer is increasingly likely to find you.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('BOOK YOUR DIAGNOSTIC', '/contact/')}</div>
|
||||
</div>
|
||||
<div class="panel anim" aria-hidden="true">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><strong>Visibility Is Bigger Than Google Rankings Now.</strong></div>
|
||||
<div style="padding:20px;display:grid;gap:10px">
|
||||
${surfaces.map((s, i) => `<div class="row" style="border:1px solid var(--mist);border-radius:12px;padding:12px 14px"><span class="ico">${icon(['search', 'spark', 'target', 'users', 'pin'][i])}</span><span class="name">${s}</span><span class="status"><span class="dot"></span></span></div>`).join('')}
|
||||
<div class="row" style="border-radius:12px;padding:12px 14px;background:var(--accent);color:var(--on-accent);border:0"><span class="name">your website</span>${icon('arrow', 'check')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="covers">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('WHAT THIS COVERS')}<h2 id="covers" class="h-display">Visibility Is Bigger Than Google Rankings Now.</h2></div>
|
||||
<div class="prose">
|
||||
<p>People can discover your business through a search result, an AI answer, an ad, a social platform, or a local listing before they ever reach your website.</p>
|
||||
<p class="body-ink">The strategy has to connect those moments.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="covers reveal">
|
||||
${covers.map(([ic, h, items]) => `<article class="cover"><div class="cover-head"><span class="ic">${icon(ic)}</span><h3>${h}</h3></div><ul>${items.map((it) => `<li>${icon('check')}${it}</li>`).join('')}</ul></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="who">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('WHO THIS IS FOR')}<h2 id="who" class="h-display">When More Marketing Isn’t Necessarily the Answer.</h2></div>
|
||||
</div>
|
||||
<div class="cards cards-3 reveal">
|
||||
${who.map(([ic, h, p]) => `<article class="card"><span class="ic">${icon(ic)}</span><h3 class="h-sm">${h}</h3><div class="prose">${paras(p)}</div></article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section band" aria-labelledby="delivery">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('HOW DELIVERY WORKS')}<h2 id="delivery" class="h-display">We Don’t Start With a Channel. We Start With the Business.</h2></div>
|
||||
<div class="prose">
|
||||
<p style="color:var(--on-dark)">Marketing problems have a habit of disguising themselves.</p>
|
||||
<p>Weak conversion can look like a traffic problem. Poor attribution can make a good channel look bad. A visibility problem can begin somewhere entirely different from SEO.</p>
|
||||
<p>That’s why every engagement follows the same diagnostic-first approach behind my consulting work.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules">
|
||||
${steps.map(([h, p], i) => `<article class="rule reveal"><span class="index">STEP_0${i + 1}</span><h3>${i + 1}. ${h}</h3><div class="prose">${p.map((t, j) => `<p${j === 0 ? ' class="ink"' : ''}>${t}</p>`).join('')}</div></article>`).join('')}
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="border-color:var(--dark-line);margin-top:48px;border-top:0">
|
||||
<div class="prose"><p>The objective is not to replace people unnecessarily.</p><p class="statement">It’s to make the marketing work better.</p></div>
|
||||
${btn('START WITH THE DIAGNOSTIC', '/contact/', 'light')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="why-op">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal">
|
||||
<div class="stack">
|
||||
${eyebrow('WHY OPERATOR-LED MARKETING')}
|
||||
<h2 id="why-op" class="h-display">I Have to Live With These Strategies Too.</h2>
|
||||
<div class="prose">
|
||||
<p class="body-ink">Marketing advice looks different when your own businesses depend on it.</p>
|
||||
<p>I’ve spent years operating in digital marketing while also being the person on the other side asking whether the leads are good, whether acquisition makes economic sense, and whether the strategy is producing anything the business can actually use.</p>
|
||||
</div>
|
||||
<p><a class="inline-link" href="/portfolio/">You can see the businesses behind that operating experience.</a></p>
|
||||
</div>
|
||||
<div class="stack-sm">
|
||||
<p class="statement">That tension matters.</p>
|
||||
<article class="card card-dark"><h3 class="h-lg">Marketing has to perform outside the marketing report.</h3></article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="faq-m">
|
||||
<div class="wrap faq-layout">
|
||||
<div class="stack-sm sticky reveal">${eyebrow('FREQUENTLY ASKED QUESTIONS')}<h2 id="faq-m" class="h-display">Before We Talk Marketing.</h2></div>
|
||||
<div class="reveal">${faq(faqs)}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="numbers">
|
||||
<div class="wrap">
|
||||
<div class="closing reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow('START WITH THE NUMBERS')}
|
||||
<h2 id="numbers" class="h-display-lg">Before You Spend More, Find Out What’s Working.</h2>
|
||||
<div class="prose">
|
||||
<p>Every engagement begins with the Business Diagnostic because another campaign won’t solve a problem we haven’t identified yet.</p>
|
||||
<p>We’ll look at where your visibility, traffic, leads, and marketing spend are going, then identify what deserves attention first.</p>
|
||||
<p class="body-ink">If there’s a stronger way forward, you’ll know what it is before committing more budget to it.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('BOOK YOUR DIAGNOSTIC', '/contact/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
import { icon, eyebrow, btn, textLink } from '../layout.mjs';
|
||||
|
||||
const URLS = {"Ai Growth Technologies": "https://aigrowthtechnologies.com", "READTYCH": "https://readtych.com", "Growth360Partners": "https://growth360partners.com", "Infinitus Texas": "https://www.infinitustx.com", "Skinny Spiritz": "https://skinnyspiritz.com", "meSEO": "https://meseoapp.com", "Korrith": "https://korrith.com"};
|
||||
|
||||
const DARK_LOGOS = ['meseo.png'];
|
||||
|
||||
const ventures = [
|
||||
['Ai Growth Technologies', 'AG', 'CEO & Board Member', 'AI-focused marketing and growth company working at the intersection of digital visibility, strategy, and performance.', ['AI Search', 'Digital Visibility', 'Growth Strategy'], null],
|
||||
['READTYCH', 'RT', 'Chief Marketing Officer, Co-Founder & Board Member', 'Education technology company developing a two-screen Digital Book and connected learning platform designed around how students and educators actually work.', ['EdTech', 'Product Strategy', 'Market Development'], 'readtych.png'],
|
||||
['Growth360Partners', 'G3', 'Chief Executive Officer & Founder', 'Full-service digital marketing agency bringing together search, websites, paid media, social, optimization, and growth strategy.', ['Marketing', 'Search', 'Digital Growth'], 'growth360partners.png'],
|
||||
['Infinitus Texas', 'IT', 'Partner & Strategic Advisor', 'Strategic partnership focused on brand positioning, marketing direction, and creating stronger foundations for scalable growth.', ['Strategy', 'Positioning', 'Growth'], 'infinitus-texas.png'],
|
||||
['Skinny Spiritz', 'SS', 'Chief Executive Officer & Board Member', 'Beverage brand where product, positioning, placement, partnerships, and customer attention all have to work together.', ['Spirits', 'Brand Growth', 'Partnerships'], 'skinny-spiritz.svg'],
|
||||
['AiGrowth360', 'A3', 'Chief Executive Officer & Founder', 'Performance-driven digital marketing company using AI, analytics, and search strategy to find better ways to create measurable growth.', ['AI', 'Analytics', 'Performance Marketing'], null],
|
||||
['meSEO', 'me', 'Founder & Strategic Growth Leader', 'Web application built around AI search, SEO, and the changing ways businesses are discovered online.', ['Software', 'AI Search', 'SEO'], 'meseo.png'],
|
||||
['Korrith', 'KO', '', 'Enterprise-grade managed infrastructure, AI-powered security, and quantum readiness.', ['Secure Infrastructure', 'AI Security', 'Quantum Readiness'], 'korrith.png'],
|
||||
];
|
||||
|
||||
const capabilities = [
|
||||
'SEO Strategy', 'LLMO / AI Search Visibility', 'GEO / Local Expansion', 'Website Development', 'Website Optimization',
|
||||
'Social Media Management', 'Paid Media', 'Brand Development', 'Strategic Partnerships', 'Investor Relations',
|
||||
'Venture Growth', 'Business Development', 'Product Development', 'Fashion Brand Building',
|
||||
];
|
||||
|
||||
const industries = [
|
||||
'Education', 'Technology', 'AI', 'Digital Marketing', 'Beverage', 'Fashion / Apparel', 'Consumer Brands',
|
||||
'Web3', 'Healthcare', 'Business Development', 'Partnerships', 'Investor Relations', 'Software / Web Apps',
|
||||
];
|
||||
|
||||
const half = Math.ceil(capabilities.length / 2);
|
||||
const row = (items) => items.map((c) => `<span class="cap"><i></i>${c}</span>`).join('');
|
||||
const marquee = (items, rev) => `<div class="marquee"><div class="marquee-track${rev ? ' rev' : ''}">${row(items)}<span aria-hidden="true" style="display:contents">${row(items)}</span></div></div>`;
|
||||
|
||||
export default {
|
||||
path: '/portfolio/',
|
||||
title: 'Venture Portfolio & Brands | Dylan Mazzei',
|
||||
description: 'Explore Dylan Mazzei’s portfolio of companies, brands, partnerships, and operating roles across AI, marketing, technology, education, and more.',
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="pf-hero">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
${eyebrow('PORTFOLIO')}
|
||||
<h1 id="pf-hero" class="h-display-lg">Different Companies. Different Roles. Same Responsibility.</h1>
|
||||
<p class="lead">I’ve worked across companies at very different stages, sometimes as founder, sometimes as operator, partner, advisor, or board member.</p>
|
||||
<div class="btn-row">${btn('EXPLORE MY CURRENT VENTURES', '#ventures')}${btn('WORK WITH DYLAN', '/consulting/', 'ghost')}</div>
|
||||
</div>
|
||||
<div class="panel anim" aria-hidden="true">
|
||||
<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><strong>The title changes. The expectation doesn’t.</strong></div>
|
||||
<ol class="steps-panel" style="padding:8px 0">
|
||||
<li class="sp-step"><span class="num">1</span><h4>Understand the business.</h4></li>
|
||||
<li class="sp-step"><span class="num">2</span><h4>Know where I can make a difference.</h4></li>
|
||||
<li class="sp-step"><span class="num">3</span><h4>Then be accountable for what happens next.</h4></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<p class="sr-only">The title changes. The expectation doesn’t. Understand the business. Know where I can make a difference. Then be accountable for what happens next.</p>
|
||||
</section>
|
||||
|
||||
<section class="section" id="ventures" aria-labelledby="vb">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('FOUNDER-LED. OPERATOR-DRIVEN. ALWAYS CLOSE TO THE WORK.')}<h2 id="vb" class="h-display">Ventures & Brands</h2></div>
|
||||
<p class="body">Different industries have asked different things of me. That range is part of the point.</p>
|
||||
</div>
|
||||
<div class="ventures reveal">
|
||||
${ventures
|
||||
.map(
|
||||
([name, mono, role, desc, focus, logo]) => `<article class="venture">${URLS[name] ? `<a class="venture-link" href="${URLS[name]}" target="_blank" rel="noopener" aria-label="Visit ${name}"></a>` : ''}
|
||||
<div class="vlogo${logo ? '' : ' vlogo-empty'}${DARK_LOGOS.includes(logo) ? ' vlogo-dark' : ''}">${logo ? `<img src="/assets/logos/${logo}" alt="${name} logo" loading="lazy">` : `<span class="mono" aria-hidden="true">${mono}</span>`}</div>
|
||||
<div class="stack-xs"><h3>${name}</h3>${role ? `<p class="vrole">${role}</p>` : ''}</div>
|
||||
<p>${desc}</p>
|
||||
<div class="focus"><small>FOCUS:</small><div class="chips">${focus.map((f) => `<span class="tag">${f}</span>`).join('')}</div></div>
|
||||
</article>`
|
||||
)
|
||||
.join('')}
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="justify-content:flex-end">${textLink('SEE HOW I THINK AS AN OPERATOR', '/about/')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section band" aria-labelledby="caps">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('CORE CAPABILITIES')}<h2 id="caps" class="h-display">What I Keep Getting Pulled Into.</h2></div>
|
||||
<p class="body">Different companies. Different problems. Certain kinds of work keep finding their way back to my desk.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-sm reveal">
|
||||
${marquee(capabilities.slice(0, half))}
|
||||
${marquee(capabilities.slice(half), true)}
|
||||
</div>
|
||||
<div class="wrap">
|
||||
<div class="sec-foot reveal" style="border-color:var(--dark-line);margin-top:48px">
|
||||
<p style="color:var(--ash)">The operating experience behind these capabilities also shapes my consulting work with founders.</p>
|
||||
${btn('WORK WITH DYLAN', '/consulting/', 'light')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="ind">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('INDUSTRIES & SECTORS')}<h2 id="ind" class="h-display">I Never Saw Much Value in Staying in One Lane.</h2></div>
|
||||
<p class="body">Moving between industries keeps me from assuming the last answer will work on the next problem.</p>
|
||||
</div>
|
||||
<div class="industries reveal">
|
||||
${industries.map((n, i) => `<div class="industry"><span class="index">${String(i + 1).padStart(2, '0')}</span><b>${n}</b></div>`).join('')}
|
||||
<a class="industry industry-note" href="/about/"><p>That range has also included businesses that went through successful exits. You can read more about the journey behind the portfolio.</p>${icon('arrow', 'check')}</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="next">
|
||||
<div class="wrap">
|
||||
<div class="closing reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow('WHAT’S NEXT')}
|
||||
<h2 id="next" class="h-display-lg">The Portfolio Is Still Moving.</h2>
|
||||
<div class="prose">
|
||||
<p>Some of these businesses are growing. Others are changing. New ideas are already competing for attention.</p>
|
||||
<p>If you want to see what I’m actively operating today, start with my current ventures.</p>
|
||||
<p class="body-ink">If you have a business, partnership, or opportunity worth putting on the table, I’m open to the conversation.</p>
|
||||
</div>
|
||||
<div class="btn-row">${btn('EXPLORE MY VENTURES', '#ventures', 'ghost')}${btn('START A CONVERSATION', '/contact/')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
import { icon, eyebrow, btn, textLink, SITE } from '../layout.mjs';
|
||||
|
||||
// TODO(content): add the Gulf Insider article link (FEATURE.url) and confirm the
|
||||
// exact published headline (FEATURE.headline).
|
||||
const FEATURE = {
|
||||
outlet: 'GULF INSIDER • FEATURE ARTICLE',
|
||||
headline: 'Inside Dylan Mazzei’s High-Stakes',
|
||||
url: null,
|
||||
};
|
||||
|
||||
const topics = [
|
||||
['rocket', 'ENTREPRENEURSHIP', 'Starting, operating, growing, and exiting companies.'],
|
||||
['search', 'AI & SEARCH', 'How AI is changing visibility, marketing, software, and discovery.'],
|
||||
['target', 'LEADERSHIP', 'Teams, decisions, accountability, and what happens after the strategy meeting.'],
|
||||
['layers', 'BUILDING ACROSS INDUSTRIES', 'What changes between markets, and what surprisingly doesn’t.'],
|
||||
];
|
||||
|
||||
export default {
|
||||
path: '/press/',
|
||||
title: 'Press, Media & Interviews | Dylan Mazzei',
|
||||
description: 'Explore press and media featuring Dylan Mazzei on entrepreneurship, business growth, AI, leadership, and operating companies across industries.',
|
||||
body: `
|
||||
<section class="page-hero" aria-labelledby="pr-hero">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
${eyebrow('PRESS & MEDIA')}
|
||||
<h1 id="pr-hero" class="h-display-lg">Some Questions Are Better Answered From Experience.</h1>
|
||||
<p class="lead">I spend most of my time inside businesses, not talking about them.</p>
|
||||
<div class="prose"><p>But when there’s something useful to add, I’m happy to talk about the decisions, industries, ideas, and changes I’m seeing from the operator’s side of the table.</p></div>
|
||||
<div class="btn-row">${btn('ABOUT DYLAN', '/about/', 'ghost')}</div>
|
||||
</div>
|
||||
<div class="panel profile anim" style="position:relative;top:auto;left:auto;max-width:360px;justify-self:end;width:100%">
|
||||
<img src="/assets/dylan-portrait.jpg" alt="Dylan Mazzei" width="900" height="900">
|
||||
<div class="profile-meta">
|
||||
<strong>Dylan Mazzei</strong>
|
||||
<span class="prole">FOUNDER & OPERATOR</span>
|
||||
<a class="btn btn-primary btn-sm btn-block" href="mailto:${SITE.pressEmail}" style="justify-content:space-between">${SITE.pressEmail}<span class="go">${icon("mail")}</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="featured">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('FEATURED PRESS')}<h2 id="featured" class="h-display">From the Other Side of the Interview.</h2></div>
|
||||
</div>
|
||||
<article class="feature-press reveal">
|
||||
${FEATURE.url ? `<a class="fp-link" href="${FEATURE.url}" target="_blank" rel="noopener" aria-label="Read the Gulf Insider feature"></a>` : ''}
|
||||
<div class="fp-visual" aria-hidden="true">
|
||||
<div class="magazine">
|
||||
<img src="/assets/photos/dylan-goodlife.jpg" alt="" width="1100" height="1467" loading="lazy">
|
||||
<span class="mast">GULF INSIDER</span>
|
||||
<span class="cover-line">${FEATURE.headline}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fp-copy">
|
||||
<span class="meta">${FEATURE.outlet}</span>
|
||||
<h3>${FEATURE.headline}</h3>
|
||||
${FEATURE.url ? `<div>${btn('READ THE FEATURE', FEATURE.url, 'light')}</div>` : ''}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-label="Photos of Dylan Mazzei">
|
||||
<div class="wrap">
|
||||
<div class="gallery reveal">
|
||||
${[['dylan-livex-smile','Dylan Mazzei at the LIVEX Liveability and Investment Exhibition'],['dylan-podcast','Dylan Mazzei in a podcast studio'],['dylan-gallery-wall','Dylan Mazzei at an exhibition'],['dylan-dinner','Dylan Mazzei at dinner']].map(([f,a]) => `<figure><img src="/assets/photos/${f}.jpg" alt="${a}" width="1100" height="1467" loading="lazy"></figure>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="speak">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow('WHAT I CAN SPEAK TO')}<h2 id="speak" class="h-display">Ask Me About Something I Actually Work On.</h2></div>
|
||||
</div>
|
||||
<div class="topics reveal">
|
||||
${topics.map(([ic, h, p]) => `<article class="topic"><span class="ic">${icon(ic)}</span><h3>${h}</h3><p>${p}</p></article>`).join('')}
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="justify-content:flex-end;border-top:0">${textLink('EXPLORE MY VENTURES', '/portfolio/')}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" aria-labelledby="media">
|
||||
<div class="wrap">
|
||||
<div class="closing closing-dark reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow('MEDIA INQUIRIES')}
|
||||
<h2 id="media" class="h-display-lg">Have a Question Worth Asking?</h2>
|
||||
<div class="prose">
|
||||
<p>For interviews, speaking engagements, podcasts, expert commentary, or press materials, contact the PR team.</p>
|
||||
<p>Share the topic, format, publication or platform, and deadline so we can get you what you need.</p>
|
||||
</div>
|
||||
<p class="statement" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;justify-content:center">${SITE.pressEmail}<button class="copy-btn" type="button" data-copy="${SITE.pressEmail}">Copy</button></p>
|
||||
<div class="btn-row">${btn('CONTACT THE PR TEAM', 'mailto:' + SITE.pressEmail)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import { heroBlock, coversBlock, whoBlock, deliveryBlock, operatorBlock, faqBlock, closingBlock, faqSchema, panelBar, panelRows, panelRow, panelRowAccent } from '../service.mjs';
|
||||
|
||||
const faqs = [
|
||||
['Do you only work with brands sourcing from China?', ['No.', 'The right sourcing market depends on the product, volumes, quality requirements, economics, lead times, and other operating considerations.', 'The diagnostic focuses on what makes sense for your business rather than starting with a predetermined country.']],
|
||||
['Can you introduce me to suppliers directly?', ['Where an appropriate supplier relationship exists, and the fit makes sense, introductions may be possible.', 'Supplier selection still requires proper vetting, sampling, commercial evaluation, and due diligence before placing an order.']],
|
||||
['My volumes are small. Will factories take me seriously?', ['Smaller volumes can affect pricing, MOQs, terms, and which manufacturers are realistic options, but they do not automatically rule out overseas sourcing.', 'Part of the work is finding a sourcing strategy appropriate for the stage of the business rather than negotiating as though you already have enterprise volume.']],
|
||||
['Do you handle ongoing supply chain management?', ['The 90-Day Supply Chain Sprint can support execution of the priorities identified in the diagnostic.', 'If the business needs ongoing operational ownership after that, we can determine whether an advisory or fractional engagement makes more sense.']],
|
||||
];
|
||||
|
||||
const costs = [['building', 'MOQs'], ['chart', 'payment terms'], ['truck', 'freight'], ['layers', 'duties'], ['target', 'defects'], ['bag', 'storage'], ['route', 'fulfilment'], ['calendar', 'delays']];
|
||||
|
||||
export default {
|
||||
path: '/consulting/supply-chain/',
|
||||
title: 'Supply Chain Consulting for Product Brands | Dylan Mazzei',
|
||||
description: 'Supply chain consulting for product brands covering sourcing, suppliers, landed costs, logistics, fulfilment, quality control, and supply risk.',
|
||||
schema: [faqSchema(faqs)],
|
||||
body:
|
||||
heroBlock({
|
||||
id: 'sc-hero',
|
||||
crumb: 'Supply Chain',
|
||||
kicker: 'SUPPLY CHAIN CONSULTING',
|
||||
h1: 'Your Margin Starts Long Before the Sale.',
|
||||
lead: 'The price on a supplier quote is only the beginning.',
|
||||
paras: [
|
||||
'MOQs, payment terms, freight, duties, defects, storage, fulfilment, and delays all factor into the real cost of a product.',
|
||||
'My teams and I source and manufacture for physical product businesses across multiple countries. We bring that operating experience to founders who want better suppliers, stronger terms, clearer costs, and a supply chain that can keep up with the business.',
|
||||
],
|
||||
visual:
|
||||
panelBar('FROM FACTORY TO CUSTOMER') +
|
||||
`<div style="padding:20px;display:grid;grid-template-columns:1fr 1fr;gap:10px">${costs.map(([ic, t]) => panelRow(ic, t)).join('')}</div>` +
|
||||
`<div style="padding:0 20px 20px">${panelRowAccent('the real cost of a product')}</div>`,
|
||||
}) +
|
||||
coversBlock({
|
||||
id: 'sc-covers',
|
||||
kicker: 'FROM FACTORY TO CUSTOMER',
|
||||
h2: 'Know What the Product Really Costs to Get There.',
|
||||
intro: ['A good supply chain connects sourcing, cost, quality, logistics, and fulfilment.', '*We look across the journey instead of optimising one piece while another quietly eats the margin.'],
|
||||
items: [
|
||||
['building', 'Sourcing & Suppliers', ['Manufacturing-market selection based on product, volume, and quality requirements', 'Supplier identification and vetting', 'Sample and trial-order strategy', 'Pricing, MOQ, and payment-term negotiation', 'Supplier diversification and backup planning']],
|
||||
['truck', 'Cost & Logistics', ['Landed-cost analysis, including freight, duties, defects, and other relevant costs', 'Air versus sea freight considerations', 'Freight-forwarding and customs planning', 'Logistics review based on volume, lead time, and cash flow', 'Tariff and supply-risk considerations']],
|
||||
['bag', 'Quality & Fulfillment', ['Quality-control and inspection protocols', 'Defect management', '3PL evaluation and selection', 'Warehouse strategy', 'Shipping and fulfilment cost review', 'Processes for managing supply as order volume grows']],
|
||||
],
|
||||
}) +
|
||||
whoBlock({
|
||||
id: 'sc-who',
|
||||
h2: 'When the Product Is Good, but the Supply Chain Needs to Catch Up.',
|
||||
items: [
|
||||
['route', 'You’re Sourcing Overseas for the First Time.', ['Finding a factory is only one part of the decision.', 'You need to understand who you’re dealing with, what to test before committing, what the terms actually mean, and what the product will cost by the time it reaches you.']],
|
||||
['chart', 'Your Margins Aren’t What They Used to Be.', ['Volumes changed. Freight changed. Supplier pricing changed.', 'But the agreements may not have.', 'We look at what you’re paying now, where the margin is going, and whether sourcing, terms, logistics, or fulfilment deserve another look.']],
|
||||
['users', 'The Founder Is Still Managing Every Shipment.', ['That can work when volumes are small.', 'As the business grows, supplier communication, freight, QC, inventory, and fulfilment need a system that doesn’t depend on the founder following every container.']],
|
||||
],
|
||||
}) +
|
||||
deliveryBlock({
|
||||
id: 'sc-delivery',
|
||||
h2: 'Start With the Cost Behind the Cost.',
|
||||
intro: ['Supply chain work follows the same diagnostic-first approach as my consulting work.'],
|
||||
steps: [
|
||||
['See the Real Cost', ['The Business Diagnostic reviews landed cost, supplier terms, logistics, fulfilment, quality issues, and supplier concentration.', 'The objective is to understand what you actually pay to get a sellable product where it needs to be.']],
|
||||
['Find the Leverage', ['We identify the strongest opportunities.', 'That could mean supplier terms, sourcing alternatives, freight decisions, QC, fulfilment, or reducing exposure to one supplier.', '*Not every lower quote is a better deal.']],
|
||||
['Strengthen the Chain', ['When execution makes sense, the 90-Day Supply Chain Sprint moves into supplier research and vetting, negotiations, logistics changes, QC protocols, and other priorities identified in the diagnostic.', 'It is grounded in the same practical experience my teams use while operating physical product businesses in my portfolio.']],
|
||||
],
|
||||
}) +
|
||||
operatorBlock({
|
||||
id: 'sc-op',
|
||||
h2: 'The Cheapest Unit Can Become the Most Expensive One.',
|
||||
paras: ['A lower factory price means very little if the MOQ traps cash, quality creates returns, freight destroys the savings, or unreliable lead times leave inventory sitting on the wrong side of the world.', 'That perspective comes from being involved in businesses where physical products have to move through the real world.'],
|
||||
side: '<p class="statement">I care about the number that matters after all of that.</p><article class="card card-dark"><h3 class="h-lg">The cost of getting a product the customer can actually buy.</h3></article>',
|
||||
cta: 'EXPLORE MY VENTURES',
|
||||
}) +
|
||||
faqBlock({ id: 'sc-faq', h2: 'Before the Next Order Goes In.', items: faqs }) +
|
||||
closingBlock({
|
||||
id: 'sc-close',
|
||||
kicker: 'BEFORE THE NEXT SHIPMENT',
|
||||
h2: 'Know the Real Cost Before You Commit.',
|
||||
paras: ['A supplier quote tells you what the factory charges. It doesn’t tell you what the product ultimately costs your business.', 'We’ll look at the suppliers, terms, freight, quality, fulfilment, and risk behind that number.', '*Then you can make the next sourcing decision with the full picture in front of you.'],
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
// Shared builder for the consulting service pages (same layout as Marketing and
|
||||
// Business Setup). Copy is passed in verbatim; a leading "*" marks a paragraph
|
||||
// that should read in the stronger ink color.
|
||||
import { icon, eyebrow, btn, faq } from './layout.mjs';
|
||||
|
||||
const P = (t, inkCls = 'body-ink') => (t.startsWith('*') ? `<p class="${inkCls}">${t.slice(1)}</p>` : `<p>${t}</p>`);
|
||||
const prose = (arr, inkCls) => `<div class="prose">${arr.map((t) => P(t, inkCls)).join('')}</div>`;
|
||||
|
||||
export const cover = ([ic, h, items]) =>
|
||||
`<article class="cover"><div class="cover-head"><span class="ic">${icon(ic)}</span><h3>${h}</h3></div><ul>${items.map((it) => `<li>${icon('check')}${it}</li>`).join('')}</ul></article>`;
|
||||
|
||||
export function heroBlock({ id, crumb, kicker, h1, lead, paras, cta = 'BOOK YOUR DIAGNOSTIC', ctaHref = '/contact/', visual }) {
|
||||
return `
|
||||
<section class="page-hero" aria-labelledby="${id}">
|
||||
<div class="wrap page-hero-grid">
|
||||
<div class="stack anim">
|
||||
<nav class="crumbs" aria-label="Breadcrumb"><a href="/consulting/">Consulting</a>${icon('chev')}<span>${crumb}</span></nav>
|
||||
${eyebrow(kicker)}
|
||||
<h1 id="${id}" class="h-display-lg">${h1}</h1>
|
||||
<p class="lead">${lead}</p>
|
||||
${prose(paras)}
|
||||
<div class="btn-row">${btn(cta, ctaHref)}</div>
|
||||
</div>
|
||||
<div class="panel anim" aria-hidden="true">${visual}</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export function coversBlock({ id, kicker, h2, intro, items }) {
|
||||
return `
|
||||
<section class="section" aria-labelledby="${id}">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow(kicker)}<h2 id="${id}" class="h-display">${h2}</h2></div>
|
||||
${prose(intro)}
|
||||
</div>
|
||||
<div class="covers reveal">${items.map(cover).join('')}</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export function whoBlock({ id, kicker = 'WHO THIS IS FOR', h2, items }) {
|
||||
return `
|
||||
<section class="section" aria-labelledby="${id}">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow(kicker)}<h2 id="${id}" class="h-display">${h2}</h2></div>
|
||||
</div>
|
||||
<div class="cards cards-3 reveal">
|
||||
${items.map(([ic, h, p]) => `<article class="card"><span class="ic">${icon(ic)}</span><h3 class="h-sm">${h}</h3>${prose(p)}</article>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export function deliveryBlock({ id, kicker = 'HOW DELIVERY WORKS', h2, intro, steps, foot = '', cta = 'START WITH THE DIAGNOSTIC', ctaHref = '/contact/' }) {
|
||||
return `
|
||||
<section class="section band" aria-labelledby="${id}">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<div class="stack-sm">${eyebrow(kicker)}<h2 id="${id}" class="h-display">${h2}</h2></div>
|
||||
<div class="prose">${intro.map((t) => (t.startsWith('*') ? `<p style="color:var(--on-dark)">${t.slice(1)}</p>` : `<p>${t}</p>`)).join('')}</div>
|
||||
</div>
|
||||
<div class="rules">
|
||||
${steps.map(([h, p], i) => `<article class="rule reveal"><span class="index">STEP_0${i + 1}</span><h3>${i + 1}. ${h}</h3><div class="prose">${p.map((t, j) => `<p${j === 0 || t.startsWith('*') ? ' class="ink"' : ''}>${t.replace(/^\*/, '')}</p>`).join('')}</div></article>`).join('')}
|
||||
</div>
|
||||
<div class="sec-foot reveal" style="border-top:0;margin-top:48px">
|
||||
${foot || '<span></span>'}
|
||||
${cta ? btn(cta, ctaHref, 'light') : ''}
|
||||
</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export function operatorBlock({ id, kicker = 'OPERATOR PERSPECTIVE', h2, paras, side, cta, ctaHref = '/portfolio/', link }) {
|
||||
return `
|
||||
<section class="section" aria-labelledby="${id}">
|
||||
<div class="wrap">
|
||||
<div class="showcase reveal">
|
||||
<div class="stack">
|
||||
${eyebrow(kicker)}
|
||||
<h2 id="${id}" class="h-display">${h2}</h2>
|
||||
${prose(paras)}
|
||||
${link ? `<p><a class="inline-link" href="${link[1]}">${link[0]}</a></p>` : ''}
|
||||
${cta ? `<div class="btn-row">${btn(cta, ctaHref)}</div>` : ''}
|
||||
</div>
|
||||
<div class="stack-sm">${side}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export function faqBlock({ id, h2, items }) {
|
||||
return `
|
||||
<section class="section" aria-labelledby="${id}">
|
||||
<div class="wrap faq-layout">
|
||||
<div class="stack-sm sticky reveal">${eyebrow('FREQUENTLY ASKED QUESTIONS')}<h2 id="${id}" class="h-display">${h2}</h2></div>
|
||||
<div class="reveal">${faq(items)}</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export function closingBlock({ id, kicker, h2, paras, cta = 'BOOK YOUR DIAGNOSTIC', ctaHref = '/contact/' }) {
|
||||
return `
|
||||
<section class="section" aria-labelledby="${id}">
|
||||
<div class="wrap">
|
||||
<div class="closing reveal">
|
||||
<div class="closing-inner">
|
||||
${eyebrow(kicker)}
|
||||
<h2 id="${id}" class="h-display-lg">${h2}</h2>
|
||||
${prose(paras)}
|
||||
<div class="btn-row">${btn(cta, ctaHref)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
export const faqSchema = (items) => ({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: items.map(([q, a]) => ({ '@type': 'Question', name: q, acceptedAnswer: { '@type': 'Answer', text: a.join(' ') } })),
|
||||
});
|
||||
|
||||
// Small mockup helpers for the hero panels
|
||||
export const panelBar = (title) => `<div class="panel-bar"><span class="lights"><i></i><i></i><i></i></span><strong>${title}</strong></div>`;
|
||||
export const panelRows = (rows) =>
|
||||
`<div style="padding:20px;display:grid;gap:10px">${rows.join('')}</div>`;
|
||||
export const panelRow = (ic, text, tail = '<span class="status"><span class="dot"></span></span>') =>
|
||||
`<div class="row" style="border:1px solid var(--mist);border-radius:12px;padding:12px 14px"><span class="ico">${icon(ic)}</span><span class="name" style="white-space:normal">${text}</span>${tail}</div>`;
|
||||
export const panelRowAccent = (text) =>
|
||||
`<div class="row row-accent" style="border-radius:12px;padding:12px 14px;background:var(--accent);color:var(--on-accent);border:0"><span class="name" style="white-space:normal">${text}</span>${icon('arrow', 'check')}</div>`;
|
||||
|
After Width: | Height: | Size: 134 KiB |
@@ -0,0 +1,65 @@
|
||||
/* Inter Tight (SIL Open Font License 1.1) - self-hosted, no Google Fonts request */
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300;
|
||||
src: url(fonts/inter-tight-latin-ext-300-normal.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 300;
|
||||
src: url(fonts/inter-tight-latin-300-normal.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(fonts/inter-tight-latin-ext-400-normal.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(fonts/inter-tight-latin-400-normal.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: url(fonts/inter-tight-latin-ext-500-normal.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: url(fonts/inter-tight-latin-500-normal.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url(fonts/inter-tight-latin-ext-600-normal.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url(fonts/inter-tight-latin-600-normal.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/* Homepage: option 2 sections (3D, neon, fireworks) scoped under .o2.
|
||||
Panels that act as "screens" (venture stage, fireworks stage) stay dark in
|
||||
both themes; everything else reads from the shared tokens. */
|
||||
.o2 {
|
||||
--o2-text: var(--ink);
|
||||
--o2-muted: var(--slate);
|
||||
--o2-faint: var(--smoke);
|
||||
--neon-off: #222c47;
|
||||
--neon-on: #e8f9ff;
|
||||
--neon-glow: 0 0 4px rgba(255,255,255,.9), 0 0 14px #1fb7ff, 0 0 34px #1a7fff, 0 0 72px rgba(26,127,255,.8);
|
||||
--neon-mid: #9fd8f2;
|
||||
--stroke-word: rgba(159, 230, 255, .14);
|
||||
--o2-tile-bd: rgba(159, 230, 255, .3);
|
||||
--o2-tile-bg: linear-gradient(145deg, rgba(159, 230, 255, .14), rgba(26, 127, 255, .04));
|
||||
--o2-beam: rgba(31, 183, 255, .30);
|
||||
}
|
||||
:root[data-theme="light"] .o2 {
|
||||
--neon-off: #c3cfe2;
|
||||
--neon-on: #0a5fd1;
|
||||
--neon-glow: 0 0 18px rgba(26, 127, 255, .35);
|
||||
--neon-mid: #6aa6e8;
|
||||
--stroke-word: rgba(10, 60, 140, .12);
|
||||
--o2-tile-bd: rgba(26, 127, 255, .25);
|
||||
--o2-tile-bg: linear-gradient(145deg, rgba(26, 127, 255, .10), rgba(255, 255, 255, .6));
|
||||
--o2-beam: rgba(31, 140, 255, .16);
|
||||
}
|
||||
.o2 > section { position: relative; overflow-x: clip; padding: clamp(5rem, 12vh, 8rem) 0; }
|
||||
.o2 .lead { color: var(--o2-muted); }
|
||||
.o2 .grad2 { background: linear-gradient(180deg, #a9dfff 0%, #5aa8ff 60%, #1a7fff); -webkit-background-clip: text; background-clip: text; color: transparent; }
|
||||
:root[data-theme="light"] .o2 .grad2 { background: linear-gradient(180deg, #1a6fe0, #0b4fb0); -webkit-background-clip: text; background-clip: text; }
|
||||
|
||||
/* ---------- hero ---------- */
|
||||
#hero { min-height: calc(100svh - var(--nav-h)); display: flex; align-items: center; justify-content: center; text-align: center; padding: 3rem 0 6rem; overflow: hidden; }
|
||||
.beam { position: absolute; left: 50%; top: -10%; width: min(1100px, 140vw); height: 90%; transform: translateX(-50%); pointer-events: none;
|
||||
background: conic-gradient(from 180deg at 50% 0%, transparent 160deg, transparent 168deg, var(--o2-beam) 180deg, transparent 192deg, transparent 200deg);
|
||||
filter: blur(26px); opacity: .9; }
|
||||
.beam::after { content: ""; position: absolute; left: 50%; top: 0; width: 60%; height: 70%; transform: translateX(-50%); background: radial-gradient(closest-side, var(--glow), transparent); }
|
||||
.floaters .t { position: absolute; display: flex; flex-direction: column; align-items: center; gap: .5rem; top: var(--y); animation: drift 9s ease-in-out infinite; animation-delay: var(--d); }
|
||||
.floaters .t.l { left: var(--x); } .floaters .t.r { right: var(--x); }
|
||||
@keyframes drift { 50% { transform: translateY(-16px) rotate(var(--r, 0deg)); } }
|
||||
.tile { display: grid; place-items: center; width: 60px; height: 60px; border-radius: 18px; border: 1px solid var(--o2-tile-bd); background: var(--o2-tile-bg); color: var(--ice);
|
||||
box-shadow: 0 0 30px rgba(26, 127, 255, .25), inset 0 0 18px rgba(159, 230, 255, .08); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); transition: box-shadow .3s, border-color .3s; }
|
||||
.t:hover .tile { border-color: var(--sky); box-shadow: 0 0 44px rgba(31, 183, 255, .5), inset 0 0 18px rgba(159, 230, 255, .15); }
|
||||
.tile svg { width: 28px; height: 28px; }
|
||||
.tl { font-size: .74rem; color: var(--o2-muted); white-space: nowrap; }
|
||||
.icon-row { display: none; grid-template-columns: repeat(3, 1fr); gap: 1.2rem .8rem; max-width: 380px; margin: 2.4rem auto 0; }
|
||||
.icon-row .t { display: flex; flex-direction: column; align-items: center; gap: .45rem; }
|
||||
.icon-row .tl { white-space: normal; text-align: center; line-height: 1.2; }
|
||||
@media (max-width: 1179px) { .floaters { display: none; } .icon-row { display: grid; } }
|
||||
@media (min-width: 600px) and (max-width: 1179px) { .icon-row { grid-template-columns: repeat(6, 1fr); max-width: 660px; } }
|
||||
.emblem { position: relative; width: clamp(116px, 12vw, 164px); margin: 0 auto; perspective: 700px; }
|
||||
.emblem img { display: block; width: 100%; height: auto; transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); transition: transform .3s ease-out; filter: drop-shadow(0 0 30px rgba(26, 127, 255, .65)); animation: float 6s ease-in-out infinite; }
|
||||
.emblem::after { content: ""; position: absolute; left: 10%; right: 10%; bottom: -14px; height: 18px; background: radial-gradient(closest-side, rgba(31, 183, 255, .55), transparent); filter: blur(6px); }
|
||||
@keyframes float { 50% { translate: 0 -10px; } }
|
||||
.hero-in { position: relative; z-index: 2; max-width: 1000px; padding-inline: var(--gutter); display: flex; flex-direction: column; align-items: center; }
|
||||
.hero-in > * { opacity: 0; transform: translateY(22px); filter: blur(8px); animation: heroIn 1s cubic-bezier(.2, .85, .2, 1) forwards; }
|
||||
.hero-in > *:nth-child(1) { animation-delay: .05s; } .hero-in > *:nth-child(2) { animation-delay: .25s; } .hero-in > *:nth-child(3) { animation-delay: .45s; }
|
||||
.hero-in > *:nth-child(4) { animation-delay: .65s; } .hero-in > *:nth-child(5) { animation-delay: .85s; } .hero-in > *:nth-child(6) { animation-delay: 1.05s; }
|
||||
@keyframes heroIn { to { opacity: 1; transform: none; filter: none; } }
|
||||
#hero .eyebrow { align-self: center; margin-top: 1rem; }
|
||||
#hero h1 { margin: 1.2rem auto 1.3rem; font-weight: 500; font-size: clamp(2.4rem, 5.6vw, 5.2rem); line-height: 1; letter-spacing: -0.05em; text-wrap: balance; }
|
||||
#hero h1 .grad, #hero h1 .grad2 { display: inline; }
|
||||
#hero .lead { margin: 0 auto 2rem; font-size: clamp(1rem, 1.5vw, 1.18rem); max-width: 62ch; }
|
||||
#hero .btn-row { justify-content: center; }
|
||||
.scroll-note { position: absolute; bottom: 1.6rem; left: 50%; transform: translateX(-50%); }
|
||||
.scroll-note i { display: block; width: 1px; height: 46px; background: linear-gradient(var(--sky), transparent); animation: drip 2s infinite; }
|
||||
@keyframes drip { 0% { transform: scaleY(0); transform-origin: top; } 50% { transform: scaleY(1); transform-origin: top; } 51% { transform-origin: bottom; } 100% { transform: scaleY(0); transform-origin: bottom; } }
|
||||
|
||||
/* ---------- giant words + cube ---------- */
|
||||
.o2 > .wsec { min-height: min(100svh, 820px); padding: 0; overflow: hidden; }
|
||||
.wsec-in { position: absolute; inset: 0; perspective: 1200px; }
|
||||
.cube { --c: min(36vmin, 320px); position: absolute; left: 50%; top: 50%; width: var(--c); height: var(--c); margin: calc(var(--c) / -2) 0 0 calc(var(--c) / -2); transform-style: preserve-3d; animation: spin 26s linear infinite; }
|
||||
.cube i { position: absolute; inset: 0; border: 1px solid rgba(159, 230, 255, .55); background: rgba(31, 183, 255, .05); box-shadow: inset 0 0 40px rgba(31, 183, 255, .12); }
|
||||
:root[data-theme="light"] .cube i { border-color: rgba(26, 127, 255, .45); }
|
||||
.cube i:nth-child(1) { transform: translateZ(calc(var(--c) / 2)); } .cube i:nth-child(2) { transform: rotateY(180deg) translateZ(calc(var(--c) / 2)); }
|
||||
.cube i:nth-child(3) { transform: rotateY(90deg) translateZ(calc(var(--c) / 2)); } .cube i:nth-child(4) { transform: rotateY(-90deg) translateZ(calc(var(--c) / 2)); }
|
||||
.cube i:nth-child(5) { transform: rotateX(90deg) translateZ(calc(var(--c) / 2)); } .cube i:nth-child(6) { transform: rotateX(-90deg) translateZ(calc(var(--c) / 2)); }
|
||||
@keyframes spin { to { transform: rotateX(360deg) rotateY(360deg); } }
|
||||
.cube-glow { position: absolute; left: 50%; top: 50%; width: 70vmin; height: 70vmin; transform: translate(-50%, -50%); background: radial-gradient(closest-side, rgba(26, 127, 255, .35), transparent); pointer-events: none; }
|
||||
:root[data-theme="light"] .cube-glow { background: radial-gradient(closest-side, rgba(26, 127, 255, .14), transparent); }
|
||||
.wmask { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); }
|
||||
#wTrack { display: inline-flex; gap: .35em; padding-right: .35em; white-space: nowrap; font-weight: 500; font-size: clamp(4.6rem, 17vw, 16rem); line-height: 1.05; letter-spacing: -0.06em; will-change: transform; }
|
||||
#wTrack span { background: linear-gradient(180deg, var(--grad-a) 10%, #7fcdff 62%, #1a7fff); -webkit-background-clip: text; background-clip: text; color: transparent; transition: opacity .15s; }
|
||||
.w-side { position: absolute; right: max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter))); bottom: clamp(2rem, 9vh, 5rem); max-width: 320px; display: flex; flex-direction: column; gap: .9rem; align-items: flex-start; z-index: 3; }
|
||||
.w-side p { color: var(--o2-muted); font-size: .98rem; }
|
||||
.w-top { position: absolute; left: max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter))); top: 2.5rem; z-index: 3; }
|
||||
@media (max-width: 700px) { .w-side { left: var(--gutter); right: var(--gutter); max-width: none; } }
|
||||
|
||||
/* ---------- building: copy beside the portfolio dashboard ---------- */
|
||||
.bgrid { display: grid; grid-template-columns: 0.75fr 1.25fr; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; }
|
||||
.bgrid > * { min-width: 0; }
|
||||
.bhead { display: flex; flex-direction: column; gap: 1.4rem; align-items: flex-start; }
|
||||
.cluster-fit { position: relative; width: 100%; }
|
||||
.cluster-fit .cluster { margin: 0; width: 760px; height: 740px; max-width: none; transform-origin: 0 0; transform: scale(var(--s, 1)); }
|
||||
.cluster-fit .dash { left: 176px; top: 0; width: 540px; }
|
||||
.cluster-fit .profile { left: 0; top: 210px; width: 196px; }
|
||||
.cluster-fit .code { right: 0; top: 420px; width: 320px; }
|
||||
.cluster-fit .dash-cols { grid-template-columns: 1.25fr 1fr; }
|
||||
.cluster-fit .row { font-size: 12.5px; }
|
||||
@media (max-width: 1100px) {
|
||||
.bgrid { grid-template-columns: 1fr; }
|
||||
.cluster-fit .cluster { width: auto; height: auto; transform: none; }
|
||||
.cluster-fit .panel { left: auto; right: auto; top: auto; width: auto; }
|
||||
.cluster-fit .profile { max-width: 340px; }
|
||||
}
|
||||
|
||||
/* ---------- ventures: tabs + themed 3D object ---------- */
|
||||
.vhead { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 1.1rem; margin-bottom: 2.4rem; }
|
||||
.vhead .lead { max-width: 60ch; }
|
||||
.vhead .eyebrow, .jhead .eyebrow { align-self: center; }
|
||||
.jhead .eyebrow { align-self: flex-start; }
|
||||
.vtabs { display: flex; gap: .4rem; padding: .35rem; border: 1px solid var(--bd); border-radius: 14px; background: var(--glass); overflow-x: auto; max-width: 100%; scrollbar-width: none; margin: 0 auto 1.6rem; width: max-content; }
|
||||
.vtabs::-webkit-scrollbar { display: none; }
|
||||
.vtabs button { padding: .55rem 1rem; border: 0; border-radius: 10px; background: none; font: 500 .88rem var(--font-sans); color: var(--o2-muted); white-space: nowrap; cursor: pointer; transition: all .25s; }
|
||||
.vtabs button:hover { color: var(--ink); }
|
||||
.vtabs button[aria-selected="true"] { background: var(--ink); color: var(--porcelain); }
|
||||
.vpanel { position: relative; display: grid; grid-template-columns: 1fr 1fr; min-height: 480px; border: 1px solid var(--bd); border-radius: 22px; color: #f3f7ff; overflow: hidden; --ac: #1fb7ff;
|
||||
--vp-title: #f3f7ff; --vp-text: #b3c0da; --vp-num: #55627f; --vp-lines: rgba(255, 255, 255, .04); --vp-glow: 40%; --vp-arrow-bg: rgba(10, 14, 26, .7); --vp-arrow-fg: #fff; --vp-arrow-bd: rgba(255, 255, 255, .09);
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .01)), #070b16; }
|
||||
:root[data-theme="light"] .vpanel { --vp-title: #0b1220; --vp-text: #4a5873; --vp-num: #8896b3; --vp-lines: rgba(15, 23, 42, .05); --vp-glow: 28%; --vp-arrow-bg: rgba(255, 255, 255, .85); --vp-arrow-fg: #0b1220; --vp-arrow-bd: rgba(15, 23, 42, .12);
|
||||
background: linear-gradient(135deg, #ffffff, #eef4fd); box-shadow: 0 30px 60px -40px rgba(15, 23, 42, .35); }
|
||||
.vpanel::before { content: ""; position: absolute; inset: 0; background: radial-gradient(60% 80% at 75% 50%, color-mix(in srgb, var(--ac) var(--vp-glow), transparent), transparent 70%); transition: background .6s; pointer-events: none; }
|
||||
.vpanel::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(var(--vp-lines) 1px, transparent 1px) 0 0 / 100% 38px; -webkit-mask-image: linear-gradient(90deg, transparent 30%, #000); mask-image: linear-gradient(90deg, transparent 30%, #000); }
|
||||
.vinfo { position: relative; z-index: 2; padding: 2.4rem; display: flex; flex-direction: column; justify-content: space-between; gap: 2rem; }
|
||||
.vnum { font-size: .9rem; color: var(--vp-num); font-variant-numeric: tabular-nums; }
|
||||
.vinfo h3 { font-weight: 500; font-size: clamp(1.8rem, 3.4vw, 3rem); line-height: 1.05; letter-spacing: -0.045em; margin-bottom: 1rem; color: var(--vp-title); }
|
||||
.vinfo p { color: var(--vp-text); font-size: 1.08rem; max-width: 34ch; }
|
||||
.vinfo .vt { transition: opacity .3s, transform .3s; }
|
||||
.vinfo .vt.out { opacity: 0; transform: translateY(10px); }
|
||||
#vcanvas { position: relative; z-index: 1; width: 100%; height: 100%; min-height: 340px; cursor: grab; }
|
||||
.varrows { position: absolute; z-index: 3; right: 1.2rem; bottom: 1.2rem; display: flex; gap: .5rem; }
|
||||
.varrows button { width: 44px; height: 44px; border: 1px solid var(--vp-arrow-bd); border-radius: 10px; background: var(--vp-arrow-bg); color: var(--vp-arrow-fg); backdrop-filter: blur(8px); display: grid; place-items: center; cursor: pointer; transition: border-color .2s; }
|
||||
.varrows button:hover { border-color: #1fb7ff; }
|
||||
.varrows svg { width: 16px; height: 16px; }
|
||||
@media (max-width: 860px) { .vpanel { grid-template-columns: 1fr; grid-template-rows: 280px auto; } #vcanvas { order: -1; min-height: 280px; } .vinfo { padding: 1.6rem 1.6rem 4.6rem; } }
|
||||
|
||||
/* ---------- journey: fireworks + Dylan's stats ---------- */
|
||||
.jgrid { display: grid; grid-template-columns: 1fr 1.1fr; grid-template-areas: "head stage" "body stage"; gap: 1.6rem clamp(2rem, 5vw, 4rem); align-items: start; }
|
||||
.jgrid > * { min-width: 0; }
|
||||
.jhead { grid-area: head; display: flex; flex-direction: column; gap: 1.1rem; align-items: flex-start; }
|
||||
.jbody { grid-area: body; }
|
||||
.jstage { grid-area: stage; position: relative; height: clamp(460px, 78vh, 680px); align-self: stretch; overflow: hidden; border-radius: 18px; border: 1px solid var(--bd); color: var(--ink);
|
||||
--floor-line: rgba(159, 230, 255, .26); --pin: #1fb7ff; --pin-label: #55627f; --pin-done: #fff; --final: #d6e8ff;
|
||||
background: radial-gradient(70% 55% at 50% 100%, rgba(26, 127, 255, .18), transparent 70%), #060a14; }
|
||||
:root[data-theme="light"] .jstage { --floor-line: rgba(26, 111, 224, .28); --pin: #1a7fff; --pin-label: #8896b3; --pin-done: #0b1220; --final: #13306b;
|
||||
background: radial-gradient(70% 55% at 50% 100%, rgba(26, 127, 255, .14), transparent 70%), linear-gradient(180deg, #ffffff, #eef4fd); }
|
||||
.jlist { display: grid; gap: .7rem; }
|
||||
.jx { display: flex; gap: 1rem; align-items: flex-start; padding: 1rem 1.2rem; border: 1px solid var(--bd); border-radius: 14px; background: var(--glass); opacity: .4; transition: opacity .6s, border-color .6s, box-shadow .6s, background .6s; }
|
||||
.jx .ji { flex: none; display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; border: 1px solid var(--bd); color: var(--o2-faint); transition: all .6s; }
|
||||
.jx svg { width: 24px; height: 24px; }
|
||||
.jx h3 { font-weight: 500; font-size: 1.15rem; letter-spacing: -0.03em; margin-bottom: .15rem; }
|
||||
.jx p { color: var(--o2-muted); font-size: .92rem; line-height: 1.45; }
|
||||
.jx.lit { opacity: 1; border-color: rgba(31, 183, 255, .5); background: linear-gradient(150deg, rgba(26, 127, 255, .14), var(--glass)); box-shadow: 0 0 36px rgba(26, 127, 255, .14); }
|
||||
.jx.lit .ji { color: var(--ice); border-color: rgba(31, 183, 255, .55); box-shadow: 0 0 18px rgba(31, 183, 255, .4); }
|
||||
.floor { position: absolute; left: -10%; right: -10%; bottom: -6%; height: 46%; transform: perspective(700px) rotateX(64deg); transform-origin: 50% 100%;
|
||||
background: linear-gradient(var(--floor-line) 1px, transparent 1px) 0 0 / 56px 56px, linear-gradient(90deg, var(--floor-line) 1px, transparent 1px) 0 0 / 56px 56px;
|
||||
-webkit-mask-image: radial-gradient(closest-side at 50% 70%, #000 35%, transparent); mask-image: radial-gradient(closest-side at 50% 70%, #000 35%, transparent); }
|
||||
.pins { position: absolute; left: 0; right: 0; bottom: 17%; height: 0; }
|
||||
.pn { position: absolute; bottom: 0; width: 0; }
|
||||
.pn i { position: absolute; left: 0; bottom: 0; width: 2px; height: 0; transform: translateX(-1px); background: linear-gradient(var(--pin), transparent); opacity: 0; }
|
||||
.pn.rise i { height: var(--ph, 100px); opacity: 1; transition: height .8s cubic-bezier(.25, .7, .35, 1), opacity .2s; }
|
||||
.pn b { position: absolute; left: -8px; bottom: var(--ph, 100px); width: 16px; height: 16px; margin-bottom: -8px; border-radius: 50%; background: #fff; border: 2px solid var(--pin); opacity: 0; transform: scale(.2); transition: opacity .3s, transform .5s cubic-bezier(.3, 1.6, .5, 1); }
|
||||
.pn.done b { opacity: 1; transform: scale(1); box-shadow: 0 0 24px 6px var(--pin); }
|
||||
.pn em { position: absolute; top: 20px; left: 0; transform: translateX(-50%); width: 84px; text-align: center; line-height: 1.15; font-style: normal; font-size: .76rem; color: var(--pin-label); transition: color .5s; }
|
||||
.pn.done em { color: var(--pin-done); }
|
||||
#fw { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
||||
.jfinal { position: absolute; left: 1.2rem; top: 1.2rem; max-width: 240px; font-size: .98rem; line-height: 1.35; color: var(--final); opacity: 0; transform: translateY(8px); transition: opacity .8s, transform .8s; }
|
||||
.jfinal.show { opacity: 1; transform: none; }
|
||||
.jreplay { position: absolute; right: 1rem; top: 1rem; opacity: 0; pointer-events: none; transition: opacity .4s; }
|
||||
.jreplay.show { opacity: 1; pointer-events: auto; }
|
||||
.o2 .stats b.pop { animation: pop .45s ease; }
|
||||
@keyframes pop { 40% { transform: scale(1.12); color: var(--sky); } }
|
||||
.o2 .stats { margin-top: 3.5rem; }
|
||||
.o2 .stats b { font-weight: 500; letter-spacing: -0.05em; }
|
||||
@media (max-width: 900px) { .jgrid { grid-template-columns: 1fr; grid-template-areas: "head" "stage" "body"; } .jstage { height: 420px; } }
|
||||
|
||||
/* ---------- about: neon words ---------- */
|
||||
.agrid { display: grid; grid-template-columns: 1.15fr .85fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; }
|
||||
.agrid > * { min-width: 0; }
|
||||
.awords span { display: block; padding-bottom: .14em; margin-bottom: -.1em; font-weight: 500; font-size: clamp(2.6rem, 7vw, 6.4rem); line-height: .98; letter-spacing: -0.055em; }
|
||||
.awords .neon { color: var(--neon-off); }
|
||||
.awords.is-in .neon { animation: neonOn 2.6s steps(1, end) .7s forwards, neonHum 7s ease-in-out 3.5s infinite; }
|
||||
.awords.is-in .neon.n2 { animation-delay: 1.5s, 4.3s; }
|
||||
@keyframes neonOn {
|
||||
0% { color: var(--neon-off); text-shadow: none; }
|
||||
10% { color: var(--neon-on); text-shadow: var(--neon-glow); }
|
||||
14% { color: var(--neon-off); text-shadow: none; }
|
||||
26% { color: var(--neon-on); text-shadow: var(--neon-glow); }
|
||||
30% { color: var(--neon-off); text-shadow: none; }
|
||||
36% { color: var(--neon-mid); text-shadow: 0 0 10px rgba(31, 183, 255, .5); }
|
||||
40% { color: var(--neon-off); text-shadow: none; }
|
||||
52% { color: var(--neon-on); text-shadow: var(--neon-glow); }
|
||||
56% { color: var(--neon-mid); text-shadow: 0 0 8px #1fb7ff, 0 0 22px rgba(26, 127, 255, .7); }
|
||||
60%, 100% { color: var(--neon-on); text-shadow: var(--neon-glow); }
|
||||
}
|
||||
@keyframes neonHum { 0%, 45%, 55%, 100% { opacity: 1; } 48% { opacity: .78; } 50% { opacity: 1; } 52% { opacity: .9; } }
|
||||
.atext { display: flex; flex-direction: column; gap: 1.1rem; align-items: flex-start; }
|
||||
.atext .q { font-size: clamp(1.15rem, 1.9vw, 1.5rem); line-height: 1.3; letter-spacing: -0.02em; color: var(--ink); max-width: 36ch; }
|
||||
@media (max-width: 860px) { .agrid { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ---------- consulting cards ---------- */
|
||||
.chead { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: end; margin-bottom: 2.6rem; }
|
||||
.chead > * { min-width: 0; }
|
||||
.cgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
.cc { position: relative; padding: 1.8rem; border: 1px solid var(--bd); border-radius: 16px; background: linear-gradient(160deg, var(--glass), transparent), var(--cloud); overflow: hidden; min-height: 210px; transition: border-color .3s, box-shadow .3s; }
|
||||
.cc::before { content: ""; position: absolute; right: -40px; top: -40px; width: 200px; height: 200px; background: radial-gradient(closest-side, rgba(26, 127, 255, .28), transparent); transition: opacity .4s; opacity: .6; }
|
||||
.cc:hover { border-color: rgba(31, 183, 255, .5); box-shadow: 0 0 40px rgba(26, 127, 255, .12); }
|
||||
.cc .cic { position: relative; display: grid; place-items: center; width: 58px; height: 58px; margin-bottom: 1.6rem; border-radius: 50%; border: 1px solid rgba(31, 183, 255, .4); background: radial-gradient(circle at 35% 30%, #16315f, #08101f); box-shadow: 0 0 24px rgba(26, 127, 255, .4); color: #9fe6ff; }
|
||||
.cc .cic svg { width: 26px; height: 26px; }
|
||||
.cc h3 { position: relative; font-weight: 500; font-size: 1.3rem; letter-spacing: -0.03em; margin-bottom: .5rem; }
|
||||
.cc p { position: relative; color: var(--o2-muted); font-size: .98rem; max-width: 46ch; }
|
||||
@media (max-width: 860px) { .chead { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 700px) { .cgrid { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ---------- playbook: outline word + our 3D book ---------- */
|
||||
#playbook { overflow: hidden; background: linear-gradient(transparent, rgba(26, 127, 255, .06), transparent); }
|
||||
.pbword { position: absolute; left: 0; top: 50%; transform: translateY(-50%); white-space: nowrap; font-weight: 500; font-size: clamp(7rem, 26vw, 24rem); letter-spacing: -0.07em; line-height: 1; color: transparent; -webkit-text-stroke: 1px var(--stroke-word); pointer-events: none; will-change: transform; }
|
||||
.pbgrid { position: relative; display: grid; grid-template-columns: 1.1fr .9fr; gap: 3rem; align-items: center; }
|
||||
.pbgrid > * { min-width: 0; }
|
||||
.pbtext { display: flex; flex-direction: column; gap: 1.2rem; align-items: flex-start; }
|
||||
.o2 .book-stage .book { box-shadow: -12px 0 0 #091a40, 0 40px 90px rgba(26, 127, 255, .4), inset 0 0 0 1px rgba(255, 255, 255, .12); transform: rotateY(-18deg) rotateX(4deg); cursor: pointer; }
|
||||
.o2 .book-stage:hover .book, .o2 .book.tap { transform: rotateY(-4deg) rotateX(1deg) translateY(-6px); }
|
||||
.o2 .book-stage .book .sheen { position: absolute; inset: 0; border-radius: inherit; background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, .14) 50%, transparent 60%); pointer-events: none; }
|
||||
@media (max-width: 860px) { .pbgrid { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ---------- press ---------- */
|
||||
.phead { display: flex; justify-content: space-between; align-items: end; gap: 2rem; flex-wrap: wrap; margin-bottom: 2.2rem; }
|
||||
.pgrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
|
||||
.pt { position: relative; display: flex; flex-direction: column; justify-content: space-between; min-height: 260px; padding: 1.4rem; border: 1px solid var(--bd); border-radius: 16px; background: var(--glass); overflow: hidden; transition: border-color .3s, transform .3s; }
|
||||
.pt::before { content: ""; position: absolute; inset: 0; background: radial-gradient(80% 60% at 50% 120%, rgba(26, 127, 255, .4), transparent); opacity: .5; transition: opacity .4s; }
|
||||
.pt:hover { border-color: rgba(31, 183, 255, .5); transform: translateY(-4px); }
|
||||
.pt:hover::before { opacity: 1; }
|
||||
.pt h3 { position: relative; font-weight: 500; font-size: 1.5rem; letter-spacing: -0.04em; }
|
||||
.pt .arrow { position: relative; align-self: flex-end; display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid var(--bd); border-radius: 10px; }
|
||||
.pt .arrow svg { width: 14px; height: 14px; }
|
||||
@media (max-width: 960px) { .pgrid { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 520px) { .pgrid { grid-template-columns: 1fr; } .pt { min-height: 150px; } }
|
||||
|
||||
/* ---------- poetry ---------- */
|
||||
.o2 > #poetry { min-height: 100svh; display: flex; align-items: center; justify-content: center; text-align: center; overflow: hidden; }
|
||||
.arc { position: absolute; left: 50%; bottom: -62vw; width: 130vw; height: 130vw; transform: translateX(-50%); border-radius: 50%; border: 2px solid rgba(31, 183, 255, .65);
|
||||
box-shadow: 0 0 80px rgba(26, 127, 255, .55), inset 0 0 120px rgba(26, 127, 255, .35), 0 -2px 18px rgba(159, 230, 255, .7); background: radial-gradient(closest-side at 50% 0%, rgba(26, 127, 255, .18), transparent); }
|
||||
:root[data-theme="light"] .arc { box-shadow: 0 0 60px rgba(26, 127, 255, .25), inset 0 0 120px rgba(26, 127, 255, .15); }
|
||||
#poetry .in-wrap { position: relative; z-index: 2; max-width: 820px; padding-inline: var(--gutter); display: flex; flex-direction: column; align-items: center; gap: 1.4rem; }
|
||||
#poetry .eyebrow { align-self: center; }
|
||||
#poetry .poem-line { color: var(--graphite); font-size: 1.25rem; }
|
||||
|
||||
/* ---------- connect ---------- */
|
||||
.o2 > #connect { padding: clamp(3rem, 8vh, 5rem) 0 0; overflow: hidden; }
|
||||
.talk { position: relative; width: 100%; height: clamp(14rem, 38vw, 34rem); display: flex; justify-content: center; align-items: center; overflow: hidden; -webkit-mask-image: linear-gradient(transparent, #000 20%, #000 80%, transparent); mask-image: linear-gradient(transparent, #000 20%, #000 80%, transparent); }
|
||||
.ribbon { position: absolute; left: 50%; top: 50%; width: max(130%, 1500px); max-width: none; height: auto; transform: translate(-50%, -50%); animation: rib 14s ease-in-out infinite alternate; filter: blur(.35px) saturate(1.08); image-rendering: auto; }
|
||||
@keyframes rib { from { transform: translate(-52%, -48%) rotate(-3deg) scale(1); } to { transform: translate(-48%, -52%) rotate(3deg) scale(1.06); } }
|
||||
.talk h2 { position: relative; z-index: 2; font-weight: 600; font-size: clamp(5rem, 23vw, 22rem); line-height: .9; letter-spacing: -0.07em; color: #fff; mix-blend-mode: difference; white-space: nowrap; }
|
||||
:root[data-theme="light"] .talk h2 { mix-blend-mode: normal; color: #0b1220; text-shadow: 0 2px 30px rgba(255, 255, 255, .6); }
|
||||
.cbody { position: relative; z-index: 2; padding-inline: var(--gutter); display: flex; flex-direction: column; align-items: center; text-align: center; gap: 1.2rem; margin-top: 1rem; }
|
||||
.cbody .eyebrow { align-self: center; }
|
||||
.cbody h3 { font-weight: 500; font-size: clamp(1.8rem, 3.6vw, 3rem); letter-spacing: -0.045em; max-width: 22ch; line-height: 1.08; }
|
||||
.cbody .prose { max-width: 62ch; align-items: center; }
|
||||
.cbody .prose p { margin-inline: auto; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero-in > * { opacity: 1; transform: none; filter: none; animation: none; }
|
||||
.awords .neon { color: var(--neon-on); text-shadow: var(--neon-glow); }
|
||||
}
|
||||
@media (max-width: 640px) { .cluster-fit .dash-cols { grid-template-columns: 1fr; } }
|
||||
@@ -0,0 +1,389 @@
|
||||
/* Homepage interactions ported from option 2: floating venture icons, giant
|
||||
words marquee, scroll-linked type, fireworks journey, themed 3D venture
|
||||
objects (three.js r128) and the scaled portfolio dashboard. */
|
||||
(() => {
|
||||
'use strict';
|
||||
const $ = (s, c = document) => c.querySelector(s);
|
||||
const $$ = (s, c = document) => Array.from(c.querySelectorAll(s));
|
||||
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
|
||||
const lerp = (a, b, t) => a + (b - a) * t;
|
||||
|
||||
/* ---------- hero icons: one per venture ---------- */
|
||||
const svg = (p) => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + p + '</svg>';
|
||||
const ICONS = [
|
||||
{ n: 'AI Software', side: 'l', x: '8%', y: '24%', r: '6deg', d: '0s', p: '<rect x="6" y="6" width="12" height="12" rx="2.5"/><path d="M9 3v3M15 3v3M9 18v3M15 18v3M3 9h3M3 15h3M18 9h3M18 15h3"/><path d="M9.6 15v-3.6L12 9.4l2.4 2V15M9.6 13.4h4.8"/>' },
|
||||
{ n: 'Marketing & AI Search', side: 'l', x: '15%', y: '50%', r: '-7deg', d: '-2s', p: '<circle cx="10.5" cy="10.5" r="6.2"/><path d="m15.2 15.2 5 5"/><path d="m7.6 12.6 2.2-2.4 1.8 1.6 2.6-3"/>' },
|
||||
{ n: 'Spirits', side: 'l', x: '6%', y: '73%', r: '8deg', d: '-4s', p: '<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"/>' },
|
||||
{ n: 'Education Technology', side: 'r', x: '8%', y: '22%', r: '-6deg', d: '-1s', p: '<path d="m2.5 9.5 9.5-5 9.5 5-9.5 5z"/><path d="M6.5 12v4.2c0 1.2 2.5 2.8 5.5 2.8s5.5-1.6 5.5-2.8V12"/><path d="M21.5 9.5v6"/>' },
|
||||
{ n: 'Cybersecurity', side: 'r', x: '15%', y: '50%', r: '7deg', d: '-3s', p: '<path d="M12 3 4.5 6v5.5c0 4.5 3.2 7.8 7.5 9.5 4.3-1.7 7.5-5 7.5-9.5V6z"/><rect x="9" y="10.8" width="6" height="5" rx="1"/><path d="M10.2 10.8V9.4a1.8 1.8 0 0 1 3.6 0v1.4"/>' },
|
||||
{ n: 'E-Commerce', side: 'r', x: '6%', y: '73%', r: '-8deg', d: '-5s', p: '<path d="M5 8h14l-1 12H6z"/><path d="M9 8V7a3 3 0 0 1 6 0v1"/>' },
|
||||
];
|
||||
const tileHTML = (i) => '<div class="tile">' + svg(i.p) + '</div><span class="tl">' + i.n.replace('&', '&') + '</span>';
|
||||
const fl = $('#floaters'), ir = $('#iconRow');
|
||||
if (fl && ir) {
|
||||
ICONS.forEach((i) => {
|
||||
const t = document.createElement('div');
|
||||
t.className = 't ' + i.side;
|
||||
t.style.cssText = '--x:' + i.x + ';--y:' + i.y + ';--r:' + i.r + ';--d:' + i.d;
|
||||
t.innerHTML = tileHTML(i);
|
||||
fl.appendChild(t);
|
||||
const t2 = document.createElement('div');
|
||||
t2.className = 't';
|
||||
t2.innerHTML = tileHTML(i);
|
||||
ir.appendChild(t2);
|
||||
});
|
||||
}
|
||||
|
||||
/* emblem follows the cursor a little */
|
||||
const emblemImg = $('#emblem img');
|
||||
addEventListener('pointermove', (e) => {
|
||||
if (!emblemImg || reduce || e.pointerType !== 'mouse') return;
|
||||
const mx = (e.clientX / innerWidth) * 2 - 1, my = (e.clientY / innerHeight) * 2 - 1;
|
||||
emblemImg.style.setProperty('--ry', (mx * 16).toFixed(1) + 'deg');
|
||||
emblemImg.style.setProperty('--rx', (-my * 11).toFixed(1) + 'deg');
|
||||
}, { passive: true });
|
||||
|
||||
/* neon words switch on when they scroll into view */
|
||||
const awords = $('.awords');
|
||||
if (awords) new IntersectionObserver((es, o) => es.forEach((e) => { if (e.isIntersecting) { awords.classList.add('is-in'); o.disconnect(); } }), { threshold: .3 }).observe(awords);
|
||||
|
||||
/* ---------- portfolio dashboard: scale the fixed-size cluster to its column ---------- */
|
||||
const fit = $('.cluster-fit'), cl = fit && $('.cluster', fit);
|
||||
const sizeCluster = () => {
|
||||
if (!fit || !cl) return;
|
||||
if (innerWidth <= 1100) { fit.style.height = ''; cl.style.removeProperty('--s'); return; }
|
||||
const s = Math.min(1, fit.clientWidth / 760);
|
||||
cl.style.setProperty('--s', s.toFixed(4));
|
||||
fit.style.height = Math.round(740 * s) + 'px';
|
||||
};
|
||||
sizeCluster();
|
||||
addEventListener('resize', sizeCluster);
|
||||
|
||||
/* ---------- scroll-linked playbook word ---------- */
|
||||
const pbSec = $('#playbook'), pbWord = $('#pbword');
|
||||
let ticking = false;
|
||||
function onScroll() {
|
||||
if (pbSec && pbWord) {
|
||||
const rp = pbSec.getBoundingClientRect();
|
||||
const kp = clamp((innerHeight - rp.top) / (innerHeight + rp.height), 0, 1);
|
||||
pbWord.style.transform = 'translate3d(' + (reduce ? 0 : lerp(-6, -38, kp)).toFixed(1) + '%,-50%,0)';
|
||||
}
|
||||
}
|
||||
addEventListener('scroll', () => { if (!ticking) { ticking = true; requestAnimationFrame(() => { ticking = false; onScroll(); }); } }, { passive: true });
|
||||
addEventListener('resize', onScroll);
|
||||
onScroll();
|
||||
const book = $('#book');
|
||||
if (book) book.addEventListener('click', () => book.classList.toggle('tap'));
|
||||
|
||||
/* ---------- giant words: moves on its own ---------- */
|
||||
const wTrack = $('#wTrack'), wSpans = wTrack ? $$('span', wTrack) : [], wSec = $('#words');
|
||||
let wx = 0, wVis = false;
|
||||
if (wSec) new IntersectionObserver((es) => { wVis = es[0].isIntersecting; }).observe(wSec);
|
||||
function marquee(dt) {
|
||||
const half = wTrack.scrollWidth / 2;
|
||||
if (!half) return;
|
||||
wx -= (reduce ? 0 : 58) * dt;
|
||||
if (wx <= -half) wx += half;
|
||||
wTrack.style.transform = 'translate3d(' + wx.toFixed(1) + 'px,0,0)';
|
||||
const vw = innerWidth;
|
||||
wSpans.forEach((s) => {
|
||||
const r = s.getBoundingClientRect();
|
||||
const d = Math.abs(r.left + r.width / 2 - vw / 2) / (vw * .55);
|
||||
s.style.opacity = (.25 + .75 * clamp(1 - d, 0, 1)).toFixed(3);
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- journey: fireworks, counters land in Dylan's stats row ---------- */
|
||||
const jst = $('#jstage'), fw = $('#fw');
|
||||
const FWOK = !!(jst && fw);
|
||||
const fx = FWOK ? fw.getContext('2d') : null;
|
||||
const pinEls = $$('.pn'), jx = $$('.jx'), jfinal = $('#jfinal'), jreplay = $('#jreplay');
|
||||
const cEx = $('#cEx'), cInd = $('#cInd'), cYr = $('#cYr');
|
||||
const PINX = [.12, .31, .5, .69, .88], PINF = [.22, .34, .46, .58, .72], LAUNCH = [200, 950, 1700, 2450, 3300];
|
||||
const COLORS_DARK = ['#ffffff', '#9fe6ff', '#1fb7ff', '#3d8bff', '#d6ecff'];
|
||||
const COLORS_LIGHT = ['#1a7fff', '#0b5fd0', '#1fb7ff', '#5b6cff', '#0870c4'];
|
||||
const isLight = () => document.documentElement.getAttribute('data-theme') === 'light';
|
||||
let FW = { w: 0, h: 0, floor: 0 }, PH = [0, 0, 0, 0, 0], rockets = [], parts = [], extra = [];
|
||||
let fwState = 'idle', fwStart = 0, fwVis = false, cleared = true, fwLaunched = [false, false, false, false, false], fwPlayed = false;
|
||||
const countUp = (el, to, suf) => {
|
||||
if (reduce) { el.textContent = to + (suf || ''); return; }
|
||||
const t0 = performance.now(), d = 1200;
|
||||
(function tick(n) {
|
||||
const k = clamp((n - t0) / d, 0, 1);
|
||||
el.textContent = Math.round(to * (1 - Math.pow(1 - k, 3))) + (k >= 1 ? (suf || '') : '');
|
||||
if (k < 1) requestAnimationFrame(tick);
|
||||
})(t0);
|
||||
};
|
||||
const pop = (el) => { el.classList.remove('pop'); void el.offsetWidth; el.classList.add('pop'); };
|
||||
function sizeFW() {
|
||||
if (!FWOK) return;
|
||||
const r = jst.getBoundingClientRect(), dpr = Math.min(devicePixelRatio || 1, 2);
|
||||
fw.width = Math.round(r.width * dpr); fw.height = Math.round(r.height * dpr);
|
||||
fx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
FW.w = r.width; FW.h = r.height; FW.floor = FW.h * .83;
|
||||
pinEls.forEach((p, i) => { PH[i] = Math.round(FW.h * PINF[i] * .88); p.style.left = (PINX[i] * 100) + '%'; p.style.setProperty('--ph', PH[i] + 'px'); });
|
||||
}
|
||||
function burst(x, y, n, spread) {
|
||||
const COLORS = isLight() ? COLORS_LIGHT : COLORS_DARK;
|
||||
const base = COLORS[(Math.random() * COLORS.length) | 0], sp = spread || 1;
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = Math.random() * Math.PI * 2, v = (50 + Math.random() * 190) * sp;
|
||||
parts.push({ x, y, px: x, py: y, vx: Math.cos(a) * v, vy: Math.sin(a) * v, life: 0, max: .85 + Math.random() * .7, c: Math.random() < .65 ? base : COLORS[(Math.random() * COLORS.length) | 0], w: Math.random() < .2 ? 2.2 : 1.3 });
|
||||
}
|
||||
}
|
||||
function launch(i) { pinEls[i].classList.add('rise'); rockets.push({ i, x: PINX[i] * FW.w, y: FW.floor, ty: FW.floor - PH[i], t: 0, dur: .55, trail: [] }); }
|
||||
function arrive(i) {
|
||||
const R = { x: PINX[i] * FW.w, y: FW.floor - PH[i] };
|
||||
pinEls[i].classList.add('done');
|
||||
if (i < 4) {
|
||||
jx[i] && jx[i].classList.add('lit');
|
||||
if (cEx) { cEx.textContent = i + 1; pop(cEx); }
|
||||
burst(R.x, R.y, 72, 1);
|
||||
} else {
|
||||
burst(R.x, R.y, 120, 1.3);
|
||||
extra = [{ t: .12, x: .3, y: .34 }, { t: .3, x: .68, y: .28 }, { t: .5, x: .5, y: .2 }, { t: .7, x: .15, y: .3 }, { t: .9, x: .85, y: .4 }];
|
||||
jfinal.classList.add('show');
|
||||
if (cInd) { countUp(cInd, 6); pop(cInd); }
|
||||
if (cYr) { countUp(cYr, 8, '+'); pop(cYr); }
|
||||
fwState = 'done'; fwPlayed = true; jreplay.classList.add('show');
|
||||
}
|
||||
}
|
||||
function resetFW() {
|
||||
fwState = 'idle'; rockets = []; parts = []; extra = [];
|
||||
pinEls.forEach((p) => p.classList.remove('rise', 'done')); jx.forEach((x) => x.classList.remove('lit'));
|
||||
[cEx, cInd, cYr].forEach((el) => el && (el.textContent = '0'));
|
||||
jfinal.classList.remove('show'); jreplay.classList.remove('show');
|
||||
fx.clearRect(0, 0, FW.w, FW.h);
|
||||
}
|
||||
function startFW() {
|
||||
if (fwState !== 'idle') return;
|
||||
sizeFW();
|
||||
if (reduce) {
|
||||
pinEls.forEach((p) => p.classList.add('rise', 'done')); jx.forEach((x) => x.classList.add('lit'));
|
||||
cEx.textContent = '4'; cInd.textContent = '6'; cYr.textContent = '8+'; jfinal.classList.add('show');
|
||||
fwState = 'done'; fwPlayed = true; return;
|
||||
}
|
||||
[cEx, cInd, cYr].forEach((el) => el && (el.textContent = '0'));
|
||||
fwState = 'play'; fwStart = performance.now(); fwLaunched = [false, false, false, false, false];
|
||||
}
|
||||
if (FWOK) {
|
||||
new IntersectionObserver((es) => {
|
||||
const e = es[0]; fwVis = e.isIntersecting;
|
||||
if (e.intersectionRatio >= .45 && fwState === 'idle' && !fwPlayed) startFW();
|
||||
}, { threshold: [0, .45] }).observe(jst);
|
||||
jreplay.addEventListener('click', () => { resetFW(); fwPlayed = false; startFW(); });
|
||||
addEventListener('resize', sizeFW);
|
||||
sizeFW();
|
||||
}
|
||||
function fwFrame(now, dt) {
|
||||
if (fwState === 'idle' && !parts.length && !rockets.length) { if (!cleared) { fx.clearRect(0, 0, FW.w, FW.h); cleared = true; } return; }
|
||||
cleared = false;
|
||||
if (fwState === 'play') { const el = now - fwStart; LAUNCH.forEach((t, i) => { if (!fwLaunched[i] && el >= t) { fwLaunched[i] = true; launch(i); } }); }
|
||||
if (extra.length) { extra.forEach((e) => { e.t -= dt; }); extra = extra.filter((e) => { if (e.t <= 0) { burst(e.x * FW.w, e.y * FW.h, 70, 1.1); return false; } return true; }); }
|
||||
const light = isLight();
|
||||
fx.clearRect(0, 0, FW.w, FW.h); fx.globalCompositeOperation = light ? 'source-over' : 'lighter'; fx.lineCap = 'round';
|
||||
rockets = rockets.filter((r) => {
|
||||
r.t += dt; const k = Math.min(1, r.t / r.dur), e = 1 - Math.pow(1 - k, 3); r.y = lerp(FW.floor, r.ty, e);
|
||||
r.trail.push({ x: r.x + (Math.random() - .5) * 1.5, y: r.y }); if (r.trail.length > 14) r.trail.shift();
|
||||
for (let j = 1; j < r.trail.length; j++) { const a = j / r.trail.length; fx.strokeStyle = (light ? 'rgba(26,127,255,' : 'rgba(159,230,255,') + (a * .8) + ')'; fx.lineWidth = 2.4 * a; fx.beginPath(); fx.moveTo(r.trail[j - 1].x, r.trail[j - 1].y); fx.lineTo(r.trail[j].x, r.trail[j].y); fx.stroke(); }
|
||||
fx.fillStyle = light ? '#0b5fd0' : '#fff'; fx.beginPath(); fx.arc(r.x, r.y, 2.6, 0, 6.283); fx.fill();
|
||||
if (k >= 1) { arrive(r.i); return false; } return true;
|
||||
});
|
||||
parts = parts.filter((p) => {
|
||||
p.life += dt; if (p.life >= p.max) return false;
|
||||
p.vx *= Math.exp(-1.3 * dt); p.vy = p.vy * Math.exp(-1.3 * dt) + 130 * dt;
|
||||
p.px = p.x; p.py = p.y; p.x += p.vx * dt; p.y += p.vy * dt;
|
||||
fx.globalAlpha = 1 - Math.pow(p.life / p.max, 2); fx.strokeStyle = p.c; fx.lineWidth = p.w;
|
||||
fx.beginPath(); fx.moveTo(p.px, p.py); fx.lineTo(p.x, p.y); fx.stroke();
|
||||
return true;
|
||||
});
|
||||
fx.globalAlpha = 1; fx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
/* ---------- main loop ---------- */
|
||||
let last = performance.now();
|
||||
(function loop(now) {
|
||||
requestAnimationFrame(loop);
|
||||
const dt = Math.min(.05, (now - last) / 1000); last = now;
|
||||
if (wVis && wTrack) marquee(dt);
|
||||
if (FWOK && (fwVis || fwState === 'play' || parts.length || rockets.length)) fwFrame(now, dt);
|
||||
})(last);
|
||||
|
||||
/* ---------- ventures: tabs + a 3D object that matches each business ---------- */
|
||||
const V = [
|
||||
{ n: 'MARKETING & AI SEARCH', d: 'Leading teams through a constantly changing search landscape.', c: '#1fb7ff', m: 'magnifier' },
|
||||
{ n: 'AI SOFTWARE', d: 'Building around new opportunities created by AI.', c: '#7a7bff', m: 'chip' },
|
||||
{ n: 'SPIRITS', d: 'Taking a consumer brand from idea to market.', c: '#3fa9ff', m: 'bottle' },
|
||||
{ n: 'EDUCATION TECHNOLOGY', d: 'Creating technology designed for real classrooms.', c: '#27d9ff', m: 'cap' },
|
||||
{ n: 'CYBERSECURITY', d: 'Solving security challenges for growing businesses.', c: '#8a6bff', m: 'lock' },
|
||||
{ n: 'E-COMMERCE', d: 'Turning product ideas into businesses built around real customers.', c: '#1a8bff', m: 'bag' },
|
||||
];
|
||||
const vtabs = $('#vtabs'), vpanel = $('#vpanel');
|
||||
if (!vtabs || !vpanel) return;
|
||||
const vt = $('#vt'), vname = $('#vname'), vdesc = $('#vdesc'), vnum = $('#vnum');
|
||||
let vi = 0, vSwitch = () => {};
|
||||
V.forEach((v, i) => {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button'; b.setAttribute('role', 'tab'); b.textContent = v.n;
|
||||
b.addEventListener('click', () => setV(i));
|
||||
vtabs.appendChild(b);
|
||||
});
|
||||
const vbtns = $$('button', vtabs);
|
||||
function setV(i, first) {
|
||||
vi = (i + V.length) % V.length;
|
||||
vbtns.forEach((b, n) => b.setAttribute('aria-selected', String(n === vi)));
|
||||
const apply = () => { vname.textContent = V[vi].n; vdesc.textContent = V[vi].d; vnum.textContent = '0' + (vi + 1) + ' / 06'; vpanel.style.setProperty('--ac', V[vi].c); vt.classList.remove('out'); };
|
||||
if (first || reduce) apply(); else { vt.classList.add('out'); setTimeout(apply, 200); }
|
||||
vSwitch(vi);
|
||||
const sel = vbtns[vi];
|
||||
if (!first && sel && vtabs.scrollWidth > vtabs.clientWidth) vtabs.scrollTo({ left: sel.offsetLeft - vtabs.clientWidth / 2 + sel.clientWidth / 2, behavior: 'smooth' });
|
||||
}
|
||||
$('#vprev').addEventListener('click', () => setV(vi - 1));
|
||||
$('#vnext').addEventListener('click', () => setV(vi + 1));
|
||||
vpanel.setAttribute('tabindex', '0');
|
||||
vpanel.addEventListener('keydown', (e) => { if (e.key === 'ArrowRight') setV(vi + 1); if (e.key === 'ArrowLeft') setV(vi - 1); });
|
||||
let sx0 = null;
|
||||
vpanel.addEventListener('touchstart', (e) => { sx0 = e.touches[0].clientX; }, { passive: true });
|
||||
vpanel.addEventListener('touchend', (e) => { if (sx0 === null) return; const dx = e.changedTouches[0].clientX - sx0; if (Math.abs(dx) > 50) setV(vi + (dx < 0 ? 1 : -1)); sx0 = null; });
|
||||
setV(0, true);
|
||||
|
||||
if (typeof THREE === 'undefined') return;
|
||||
(function () {
|
||||
const canvas = $('#vcanvas');
|
||||
let r;
|
||||
try { r = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); } catch (e) { return; }
|
||||
r.setPixelRatio(Math.min(devicePixelRatio || 1, 2));
|
||||
r.outputEncoding = THREE.sRGBEncoding; r.toneMapping = THREE.ACESFilmicToneMapping; r.toneMappingExposure = 1.2;
|
||||
|
||||
// Studio reflections painted on a canvas, as in option 2.
|
||||
const ec = document.createElement('canvas'); ec.width = 1024; ec.height = 512;
|
||||
const e = ec.getContext('2d');
|
||||
const g0 = e.createLinearGradient(0, 0, 0, 512); g0.addColorStop(0, '#1b2f5c'); g0.addColorStop(.5, '#05070f'); g0.addColorStop(1, '#000');
|
||||
e.fillStyle = g0; e.fillRect(0, 0, 1024, 512);
|
||||
const box = (x, y, w, h, c, b) => { e.save(); e.shadowColor = c; e.shadowBlur = b; e.fillStyle = c; e.fillRect(x, y, w, h); e.restore(); };
|
||||
box(120, 50, 300, 36, '#ffffff', 40); box(560, 40, 260, 30, '#d6e8ff', 40); box(250, 150, 26, 240, '#ffffff', 30); box(610, 130, 34, 260, '#2aa8ff', 34); box(860, 140, 30, 240, '#1a7fff', 34); box(40, 160, 26, 220, '#1fb7ff', 30); box(440, 200, 18, 200, '#bfe6ff', 26);
|
||||
const tex = new THREE.CanvasTexture(ec); tex.mapping = THREE.EquirectangularReflectionMapping; tex.encoding = THREE.sRGBEncoding;
|
||||
const env = new THREE.PMREMGenerator(r).fromEquirectangular(tex).texture;
|
||||
|
||||
const scene = new THREE.Scene(), cam = new THREE.PerspectiveCamera(32, 1, .1, 100);
|
||||
cam.position.set(0, 0, 9);
|
||||
const metal = new THREE.MeshStandardMaterial({ color: 0x0b2c6e, metalness: .9, roughness: .22, envMap: env, envMapIntensity: 1.5, emissive: 0x1a7fff, emissiveIntensity: .15 });
|
||||
const chrome = new THREE.MeshStandardMaterial({ color: 0xdfe8f5, metalness: 1, roughness: .12, envMap: env, envMapIntensity: 1.6 });
|
||||
const glass = new THREE.MeshPhysicalMaterial({ color: 0x9fdcff, metalness: .1, roughness: .05, transmission: .6, transparent: true, opacity: .55, envMap: env, envMapIntensity: 1.4 });
|
||||
const glow = new THREE.MeshBasicMaterial({ color: 0x9fe6ff });
|
||||
const holder = new THREE.Group();
|
||||
scene.add(holder);
|
||||
const key = new THREE.PointLight(0x1fb7ff, 60, 30); key.position.set(-4, 3, 6); scene.add(key);
|
||||
scene.add(new THREE.AmbientLight(0x2a4a99, .8));
|
||||
const k2 = new THREE.DirectionalLight(0xffffff, 1.1); k2.position.set(3, 4, 5); scene.add(k2);
|
||||
|
||||
const M = (geo, mat) => new THREE.Mesh(geo, mat);
|
||||
const BUILD = {
|
||||
// Marketing & AI Search: a magnifying glass over rising bars
|
||||
magnifier() {
|
||||
const g = new THREE.Group();
|
||||
const ring = M(new THREE.TorusGeometry(1.05, .17, 24, 64), metal);
|
||||
const lens = M(new THREE.CircleGeometry(1.0, 48), glass);
|
||||
const handle = M(new THREE.CylinderGeometry(.16, .2, 1.6, 24), chrome);
|
||||
handle.position.set(1.15, -1.15, 0); handle.rotation.z = Math.PI / 4;
|
||||
const lensGroup = new THREE.Group(); lensGroup.add(ring, lens, handle); lensGroup.position.set(.25, .35, .35);
|
||||
[[-.75, .45], [-.25, .8], [.25, 1.15]].forEach(([x, h], i) => {
|
||||
const bar = M(new THREE.BoxGeometry(.32, h, .32), i === 2 ? glow : metal);
|
||||
bar.position.set(x - .1, -1.05 + h / 2, -.6); g.add(bar);
|
||||
});
|
||||
g.add(lensGroup);
|
||||
return g;
|
||||
},
|
||||
// AI Software: a processor chip with pins and a glowing core
|
||||
chip() {
|
||||
const g = new THREE.Group();
|
||||
g.add(M(new THREE.BoxGeometry(2.2, 2.2, .32), metal));
|
||||
const core = M(new THREE.BoxGeometry(1.1, 1.1, .1), glow); core.position.z = .2; g.add(core);
|
||||
const pinGeo = new THREE.BoxGeometry(.14, .42, .1);
|
||||
for (let s = 0; s < 4; s++) for (let i = 0; i < 5; i++) {
|
||||
const p = M(pinGeo, chrome); const off = -0.8 + i * .4;
|
||||
if (s === 0) p.position.set(off, 1.3, 0);
|
||||
if (s === 1) p.position.set(off, -1.3, 0);
|
||||
if (s === 2) { p.position.set(1.3, off, 0); p.rotation.z = Math.PI / 2; }
|
||||
if (s === 3) { p.position.set(-1.3, off, 0); p.rotation.z = Math.PI / 2; }
|
||||
g.add(p);
|
||||
}
|
||||
g.rotation.x = -.35;
|
||||
return g;
|
||||
},
|
||||
// Spirits: a bottle (lathe profile) with a chrome cap
|
||||
bottle() {
|
||||
const g = new THREE.Group();
|
||||
const pts = [[0, -1.7], [.78, -1.7], [.86, -1.55], [.86, .35], [.7, .7], [.32, 1.05], [.27, 1.55], [0, 1.55]].map(([x, y]) => new THREE.Vector2(x, y));
|
||||
g.add(M(new THREE.LatheGeometry(pts, 48), glass));
|
||||
const liquid = M(new THREE.CylinderGeometry(.78, .78, 1.6, 48), metal); liquid.position.y = -.85; g.add(liquid);
|
||||
const label = M(new THREE.CylinderGeometry(.875, .875, .55, 48, 1, true), glow); label.position.y = -.35; g.add(label);
|
||||
const cap = M(new THREE.CylinderGeometry(.3, .3, .4, 32), chrome); cap.position.y = 1.72; g.add(cap);
|
||||
g.scale.setScalar(.85);
|
||||
return g;
|
||||
},
|
||||
// Education Technology: a graduation cap
|
||||
cap() {
|
||||
const g = new THREE.Group();
|
||||
const board = M(new THREE.BoxGeometry(2.6, .12, 2.6), metal); board.rotation.y = Math.PI / 4; board.position.y = .45; g.add(board);
|
||||
g.add(M(new THREE.CylinderGeometry(.95, 1.05, .9, 40, 1, true), chrome));
|
||||
const bottom = M(new THREE.CircleGeometry(.95, 40), metal); bottom.rotation.x = Math.PI / 2; bottom.position.y = -.45; g.add(bottom);
|
||||
const button = M(new THREE.SphereGeometry(.12, 16, 16), glow); button.position.y = .55; g.add(button);
|
||||
const cord = M(new THREE.CylinderGeometry(.03, .03, 1.3, 8), glow); cord.position.set(.85, -.1, .85); g.add(cord);
|
||||
const tassel = M(new THREE.ConeGeometry(.12, .4, 16), glow); tassel.position.set(.85, -.85, .85); g.add(tassel);
|
||||
g.rotation.x = .35;
|
||||
return g;
|
||||
},
|
||||
// Cybersecurity: a padlock
|
||||
lock() {
|
||||
const g = new THREE.Group();
|
||||
const body = M(new THREE.BoxGeometry(2, 1.6, .7), metal); body.position.y = -.5; g.add(body);
|
||||
const shackle = M(new THREE.TorusGeometry(.62, .16, 20, 48, Math.PI), chrome); shackle.position.y = .3; g.add(shackle);
|
||||
[-.62, .62].forEach((x) => { const leg = M(new THREE.CylinderGeometry(.16, .16, .5, 20), chrome); leg.position.set(x, .05, 0); g.add(leg); });
|
||||
const hole = M(new THREE.CylinderGeometry(.17, .17, .1, 24), glow); hole.rotation.x = Math.PI / 2; hole.position.set(0, -.35, .36); g.add(hole);
|
||||
const slot = M(new THREE.BoxGeometry(.12, .4, .1), glow); slot.position.set(0, -.62, .36); g.add(slot);
|
||||
return g;
|
||||
},
|
||||
// E-commerce: a shopping bag with handles
|
||||
bag() {
|
||||
const g = new THREE.Group();
|
||||
g.add(M(new THREE.BoxGeometry(2, 2.2, .9), metal));
|
||||
const band = M(new THREE.BoxGeometry(2.02, .28, .92), glow); band.position.y = .6; g.add(band);
|
||||
[-.42, .42].forEach((z) => { const h = M(new THREE.TorusGeometry(.5, .09, 16, 40, Math.PI), chrome); h.position.set(0, 1.1, z * .9); g.add(h); });
|
||||
const tag = M(new THREE.BoxGeometry(.5, .32, .05), chrome); tag.position.set(.55, -.45, .48); tag.rotation.z = -.25; g.add(tag);
|
||||
g.rotation.x = .1; g.scale.setScalar(.9);
|
||||
return g;
|
||||
},
|
||||
};
|
||||
|
||||
let model = null, born = 0;
|
||||
const dispose = (o) => o.traverse((c) => { if (c.geometry) c.geometry.dispose(); });
|
||||
vSwitch = (i) => {
|
||||
if (model) { holder.remove(model); dispose(model); }
|
||||
model = BUILD[V[i].m]();
|
||||
holder.add(model);
|
||||
born = performance.now();
|
||||
key.color.set(V[i].c); metal.emissive.set(V[i].c); glow.color.set(V[i].c).lerp(new THREE.Color(0xffffff), .45);
|
||||
};
|
||||
vSwitch(vi);
|
||||
let vis = true;
|
||||
new IntersectionObserver((es) => { vis = es[0].isIntersecting; }).observe(canvas);
|
||||
const resize = () => { const w = canvas.clientWidth || 1, h = canvas.clientHeight || 1; r.setSize(w, h, false); cam.aspect = w / h; cam.updateProjectionMatrix(); };
|
||||
addEventListener('resize', resize); resize();
|
||||
let mx = 0, my = 0;
|
||||
vpanel.addEventListener('pointermove', (ev) => { const b = vpanel.getBoundingClientRect(); mx = ((ev.clientX - b.left) / b.width - .5) * 2; my = ((ev.clientY - b.top) / b.height - .5) * 2; });
|
||||
let lt = performance.now();
|
||||
(function loop(now) {
|
||||
requestAnimationFrame(loop);
|
||||
if (!vis) return;
|
||||
const dt = Math.min(.05, (now - lt) / 1000); lt = now;
|
||||
const k = reduce ? 1 : clamp((now - born) / 650, 0, 1), sc = reduce ? 1 : (.3 + .7 * (1 - Math.pow(1 - k, 3)));
|
||||
holder.scale.setScalar(sc);
|
||||
if (!reduce) { holder.rotation.y += dt * (.45 + (1 - k) * 6); }
|
||||
holder.rotation.x += ((my * .35) - holder.rotation.x) * .06;
|
||||
holder.position.x += ((mx * .5) - holder.position.x) * .06;
|
||||
r.render(scene, cam);
|
||||
})(lt);
|
||||
})();
|
||||
})();
|
||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 223 KiB |
@@ -0,0 +1,147 @@
|
||||
(function () {
|
||||
window.__site = true;
|
||||
var root = document.documentElement;
|
||||
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
// Theme toggle: remembers the visitor's choice; otherwise follows the system.
|
||||
var themeBtn = document.getElementById('theme-btn');
|
||||
var current = function () { return root.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'; };
|
||||
var paint = function () {
|
||||
if (!themeBtn) return;
|
||||
var dark = current() === 'dark';
|
||||
themeBtn.setAttribute('aria-label', dark ? 'Switch to light mode' : 'Switch to dark mode');
|
||||
themeBtn.setAttribute('aria-pressed', String(dark));
|
||||
};
|
||||
var apply = function (t, save) {
|
||||
root.setAttribute('data-theme', t);
|
||||
if (save) { try { localStorage.setItem('theme', t); } catch (e) {} }
|
||||
paint();
|
||||
};
|
||||
paint();
|
||||
if (themeBtn) themeBtn.addEventListener('click', function () { apply(current() === 'dark' ? 'light' : 'dark', true); });
|
||||
|
||||
// Nav border on scroll
|
||||
var nav = document.getElementById('nav');
|
||||
var progress = document.getElementById('progress');
|
||||
var onScroll = function () {
|
||||
nav && nav.classList.toggle('is-scrolled', window.scrollY > 8);
|
||||
if (progress) {
|
||||
var max = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
|
||||
progress.style.transform = 'scaleX(' + Math.min(1, Math.max(0, window.scrollY / max)) + ')';
|
||||
}
|
||||
};
|
||||
onScroll();
|
||||
window.addEventListener('scroll', onScroll, { passive: true });
|
||||
|
||||
// Mobile menu
|
||||
var menuBtn = document.getElementById('menu-btn');
|
||||
var menu = document.getElementById('mobile-menu');
|
||||
if (menuBtn && menu) {
|
||||
menuBtn.addEventListener('click', function () {
|
||||
var open = menu.hidden;
|
||||
menu.hidden = !open;
|
||||
menuBtn.setAttribute('aria-expanded', String(open));
|
||||
menuBtn.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
|
||||
});
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && !menu.hidden) { menu.hidden = true; menuBtn.setAttribute('aria-expanded', 'false'); menuBtn.focus(); }
|
||||
});
|
||||
}
|
||||
|
||||
// Reveal on scroll
|
||||
var reveals = document.querySelectorAll('.reveal');
|
||||
if ('IntersectionObserver' in window && !reduce) {
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (en) {
|
||||
if (en.isIntersecting) { en.target.classList.add('is-in'); io.unobserve(en.target); }
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px', threshold: 0.05 });
|
||||
reveals.forEach(function (el) { io.observe(el); });
|
||||
} else {
|
||||
reveals.forEach(function (el) { el.classList.add('is-in'); });
|
||||
}
|
||||
|
||||
// Count-up numbers: <b data-count="8" data-suffix="+">8+</b>
|
||||
var counters = document.querySelectorAll('[data-count]');
|
||||
var run = function (el) {
|
||||
var end = parseInt(el.getAttribute('data-count'), 10);
|
||||
var suffix = el.getAttribute('data-suffix') || '';
|
||||
var start = null;
|
||||
var dur = 1200;
|
||||
var step = function (t) {
|
||||
if (!start) start = t;
|
||||
var p = Math.min((t - start) / dur, 1);
|
||||
var eased = 1 - Math.pow(1 - p, 3);
|
||||
el.textContent = Math.round(end * eased) + suffix;
|
||||
if (p < 1) requestAnimationFrame(step);
|
||||
};
|
||||
requestAnimationFrame(step);
|
||||
};
|
||||
if ('IntersectionObserver' in window && !reduce) {
|
||||
var co = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (en) { if (en.isIntersecting) { run(en.target); co.unobserve(en.target); } });
|
||||
}, { threshold: 0.6 });
|
||||
counters.forEach(function (el) { co.observe(el); });
|
||||
}
|
||||
|
||||
// Tabs (service lines)
|
||||
document.querySelectorAll('[data-tabs]').forEach(function (wrap) {
|
||||
var btns = wrap.querySelectorAll('[role="tab"]');
|
||||
var panels = wrap.querySelectorAll('[role="tabpanel"]');
|
||||
var select = function (i, focus) {
|
||||
btns.forEach(function (b, j) {
|
||||
var on = i === j;
|
||||
b.setAttribute('aria-selected', String(on));
|
||||
b.tabIndex = on ? 0 : -1;
|
||||
panels[j].hidden = !on;
|
||||
});
|
||||
if (focus) btns[i].focus();
|
||||
};
|
||||
btns.forEach(function (b, i) {
|
||||
b.addEventListener('click', function () { select(i); });
|
||||
b.addEventListener('keydown', function (e) {
|
||||
var k = e.key;
|
||||
if (k === 'ArrowDown' || k === 'ArrowRight') { e.preventDefault(); select((i + 1) % btns.length, true); }
|
||||
if (k === 'ArrowUp' || k === 'ArrowLeft') { e.preventDefault(); select((i - 1 + btns.length) % btns.length, true); }
|
||||
});
|
||||
});
|
||||
var hash = location.hash.replace('#', '');
|
||||
var start = 0;
|
||||
panels.forEach(function (p, j) { if (p.id === hash) start = j; });
|
||||
select(start);
|
||||
});
|
||||
|
||||
// Copy buttons
|
||||
document.querySelectorAll('[data-copy]').forEach(function (b) {
|
||||
b.addEventListener('click', function () {
|
||||
var text = b.getAttribute('data-copy');
|
||||
var label = b.textContent;
|
||||
var done = function () { b.textContent = 'Copied'; setTimeout(function () { b.textContent = label; }, 1600); };
|
||||
try { navigator.clipboard.writeText(text).then(done, function () {}); } catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
// Inquiry form: opens the visitor's email client with the message prefilled.
|
||||
var form = document.getElementById('inquiry');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
if (!form.reportValidity()) return;
|
||||
var d = new FormData(form);
|
||||
var body = [
|
||||
'Name: ' + d.get('name'),
|
||||
'Email: ' + d.get('email'),
|
||||
d.get('company') ? 'Company: ' + d.get('company') : '',
|
||||
d.get('topic') ? 'Topic: ' + d.get('topic') : '',
|
||||
'',
|
||||
d.get('message')
|
||||
].filter(function (l, i) { return l !== '' || i === 4; }).join('\n');
|
||||
var subject = 'Inquiry from ' + d.get('name') + (d.get('topic') ? ' — ' + d.get('topic') : '');
|
||||
window.location.href = 'mailto:' + form.getAttribute('data-to') + '?subject=' + encodeURIComponent(subject) + '&body=' + encodeURIComponent(body);
|
||||
var status = document.getElementById('form-status');
|
||||
if (status) status.textContent = 'Your email app should open with the message ready to send.';
|
||||
});
|
||||
}
|
||||
|
||||
root.classList.add('ready');
|
||||
})();
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="16" fill="#0b0b0d"/><g transform="translate(6 15) scale(.5)"><path fill="#fff" 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"/></g></svg>
|
||||
|
After Width: | Height: | Size: 441 B |
@@ -0,0 +1,93 @@
|
||||
Copyright 2022 The Inter Project Authors (https://github.com/rsms/inter-tight) InterTight-Italic[wght].ttf: Copyright 2022 The Inter Project Authors (https://github.com/rsms/inter-tight)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright © 2010-2021 three.js authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist",
|
||||
"cleanUrls": true,
|
||||
"trailingSlash": true,
|
||||
"headers": [
|
||||
{
|
||||
"source": "/assets/(.*)",
|
||||
"headers": [{ "key": "Cache-Control", "value": "public, max-age=604800, stale-while-revalidate=86400" }]
|
||||
}
|
||||
]
|
||||
}
|
||||