From 18870a4663dea884e34d45a86e2f0118a1b811a5 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:29:29 -0400 Subject: [PATCH 1/6] fix: correct LinkedIn URL, surface send confirmation, drop footer credit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LinkedIn href pointed at linkedin.com/in/dommangonon in all four places it appears (nav schema, Contact, Resume, Footer); the real profile is www.linkedin.com/in/dominicmangonon. The contact form set a success message into state but never rendered it — only the error branch had markup. The sole success cue was the submit button's label flipping to "Sent ✓" for four seconds, which is easy to miss once the form has cleared. Render an accent-bordered confirmation block with role="status", and drop the 4s auto-dismiss so it clears when the visitor starts a new message rather than on a timer. Remove "Built with Claude Code" from the footer legal line. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V3tDuknYtu9JiCr4w2s4b8 --- app/globals.css | 28 ++++++++++++++++++++++++++++ app/layout.tsx | 2 +- components/landing/Contact.tsx | 15 ++++++++++++--- components/landing/Footer.tsx | 4 +--- components/landing/Resume.tsx | 2 +- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/app/globals.css b/app/globals.css index 4bf78bf..40baff4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -945,6 +945,34 @@ input, letter-spacing: 0.04em; color: var(--blood-400); } +.form-success { + display: grid; + grid-template-columns: auto 1fr; + gap: 2px var(--s-3); + align-items: center; + padding: var(--s-4); + border: 2px solid var(--accent); + background: color-mix(in srgb, var(--accent) 14%, transparent); +} +.form-success-mark { + grid-row: 1 / span 2; + font-size: 30px; + line-height: 1; + color: var(--accent-text); +} +.form-success-head { + font-family: var(--font-mono); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--fg); +} +.form-success-body { + font-size: 14px; + line-height: 1.4; + color: var(--fg-muted); +} .form-captcha { align-self: flex-start; } /* ---- Resume ---------------------------------------------- */ diff --git a/app/layout.tsx b/app/layout.tsx index 934f657..5680a88 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -102,7 +102,7 @@ export default function RootLayout({ url: SITE_URL, description: "Builds software with AI.", sameAs: [ - "https://linkedin.com/in/dommangonon", + "https://www.linkedin.com/in/dominicmangonon/", "https://x.com/collapsecontext", "https://dommangonon.substack.com", "https://github.com/dommango", diff --git a/components/landing/Contact.tsx b/components/landing/Contact.tsx index 45a07c1..42c6489 100644 --- a/components/landing/Contact.tsx +++ b/components/landing/Contact.tsx @@ -18,8 +18,10 @@ export function Contact() { const recaptchaRef = useRef(null) const update = - (key: keyof typeof form) => (e: React.ChangeEvent) => + (key: keyof typeof form) => (e: React.ChangeEvent) => { + setStatus((st) => (st === 'idle' ? st : 'idle')) setForm((s) => ({ ...s, [key]: e.target.value })) + } const submit = async (e: React.FormEvent) => { e.preventDefault() @@ -45,7 +47,6 @@ export function Contact() { setForm({ name: '', email: '', message: '' }) setCaptchaOk(false) recaptchaRef.current?.reset() - setTimeout(() => setStatus('idle'), 4000) } else { setStatus('error') setFeedback(result.message) @@ -69,7 +70,7 @@ export function Contact() { dom.mangonon@gmail.com →
- + LinkedIn ↗ @@ -123,6 +124,14 @@ export function Contact() { {status === 'error' &&

{feedback}

} + {status === 'success' && ( +
+ + Message sent + {feedback} +
+ )} +
diff --git a/components/landing/Resume.tsx b/components/landing/Resume.tsx index d9bbdb7..45fb181 100644 --- a/components/landing/Resume.tsx +++ b/components/landing/Resume.tsx @@ -62,7 +62,7 @@ export function Resume() {

From ce5bc6cae6f654934941cc76e7faf594205e09b0 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:34:29 -0400 Subject: [PATCH 2/6] copy: describe the work as built with coding agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the four "Claude Code" mentions in body copy (Hero, Projects, Resume timeline, chatbot persona) with "coding agents". The Claude Code Placemat keeps its name — that's the actual product. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V3tDuknYtu9JiCr4w2s4b8 --- components/landing/Hero.tsx | 6 +- components/landing/Projects.tsx | 2 +- components/landing/Resume.tsx | 2 +- lib/services/chat.ts | 264 ++++++++++++++++---------------- 4 files changed, 137 insertions(+), 137 deletions(-) diff --git a/components/landing/Hero.tsx b/components/landing/Hero.tsx index 568e318..27d8219 100644 --- a/components/landing/Hero.tsx +++ b/components/landing/Hero.tsx @@ -42,9 +42,9 @@ export function Hero() {

In 2025 I went all in on AI and started shipping actual software: a restaurant cost - tracker, a tournament bracket engine, an SEC filings pipeline. All built with Claude - Code, including this site. The projects below are real, deployed, and mostly still - running. + tracker, a tournament bracket engine, an SEC filings pipeline. All built using + coding agents, including this site. The projects below are real, deployed, and mostly + still running.

diff --git a/components/landing/Projects.tsx b/components/landing/Projects.tsx index 264d76b..082924e 100644 --- a/components/landing/Projects.tsx +++ b/components/landing/Projects.tsx @@ -35,7 +35,7 @@ export function Projects() { Projects

Things I built.

- Mostly nights and weekends, mostly with Claude Code. A couple are running in the wild; + Mostly nights and weekends, mostly with coding agents. A couple are running in the wild; a couple are pipelines that only ever needed to run once.

diff --git a/components/landing/Resume.tsx b/components/landing/Resume.tsx index 45fb181..fb3c0fd 100644 --- a/components/landing/Resume.tsx +++ b/components/landing/Resume.tsx @@ -6,7 +6,7 @@ const TIMELINE = [ { y: '2025 →', t: 'Building with AI', - d: 'Shipping software nights and weekends with Claude Code (see above)', + d: 'Shipping software nights and weekends with coding agents (see above)', }, { y: '2021 →', diff --git a/lib/services/chat.ts b/lib/services/chat.ts index faa2e29..cd605eb 100644 --- a/lib/services/chat.ts +++ b/lib/services/chat.ts @@ -1,132 +1,132 @@ -/** - * AI Chat Service - * Handles communication with the AI chat API - */ - -export interface ChatMessage { - role: 'user' | 'assistant' | 'system' - content: string -} - -export interface ChatResponse { - success: boolean - message?: string - error?: string -} - -// Context about Dom that the AI should know. Keep this in sync with -// lib/content/projects.ts — the projects are the point of the site. -export const DOM_CONTEXT = `You are Dom Mangonon's AI assistant on his personal site. The site is a portfolio: it leads with the things Dom has built. Help visitors understand those projects first, and his career only if they ask. - -## About Dom -- Dominic "Dom" Mangonon, New York metropolitan area -- Seventeen years in financial services; currently SVP, Transformation at Citi, where he works on enterprise AI adoption -- Went all in on AI in 2025 and started shipping software nights and weekends, all built with Claude Code — including this site - -## Projects (the main thing on the site) -- SousIQ — restaurant cost management. Parses vendor invoices into line items and matches them to inventory products using embeddings and fuzzy search. Postgres with pgvector, row-level security for multi-tenancy, Claude Haiku for parsing and Sonnet for harder passes. Live and field-tested in a working bakery. Source is private. -- Bracketeer — tournament bracket pool. Create a pool, invite friends, make picks, watch a live leaderboard. Knockout seeding implements FIFA Annex C. Next.js, Prisma, Auth.js, Railway. Live, and ran a real World Cup pool. Started as a pool for friends. -- Claude Code Placemat — a one-page Claude Code reference (shortcuts, slash commands, flags, hooks, MCP) that maintains itself: a scheduled agent re-reads each release and opens a PR when anything drifts. Static HTML on GitHub Pages, MIT licensed. -- modular-mind — builds a corpus of 3,500+ VCV Rack modular-synth patches and generates new ones from learned patterns. Decodes the binary patch format, profiles modules, validates signal flow. Python. -- PRIAL Pipeline — turns monthly SEC Form ADV bulk filings into a deduped registry of 23,000+ investment-adviser firms, with officers and firm sites. Python. Private. - -## Career, briefly -Operations at BNP Paribas through the 2008 crisis, MBA at CMU Tepper, consulting at PwC/Strategy& and Treliant, wealth-management strategy at Morgan Stanley, and Citi since 2021. The site has a short timeline; LinkedIn has the long version. - -## Writing -Dom writes at Context//Collapse on Substack (dommangonon.substack.com) — notes on building with AI. - -## Also -Has travelled to 52 countries across 5 continents; the site has an interactive map. - -## Response Guidelines -- Be friendly and direct. Keep it to 2-3 sentences for simple questions. -- The site is a single page — point people to a section (Projects, Writing, Career, Travel, Contact), never to a separate page or URL path. -- Never invent numbers, dates, or facts. If you don't know, say so and suggest they reach out via the Contact section. -- Don't oversell him. The projects speak for themselves; describe them plainly. -` - -/** - * Send a message to the AI chat API - */ -export async function sendChatMessage( - messages: ChatMessage[] -): Promise { - const apiUrl = process.env.NEXT_PUBLIC_CHAT_API_URL - - // If no API configured, return a helpful message - if (!apiUrl) { - return { - success: true, - message: getOfflineResponse(messages[messages.length - 1]?.content || '') - } - } - - try { - const response = await fetch(apiUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - messages: [ - { role: 'system', content: DOM_CONTEXT }, - ...messages - ] - }) - }) - - if (!response.ok) { - throw new Error(`API error: ${response.status}`) - } - - const data = await response.json() - return { - success: true, - message: data.message || data.content || data.choices?.[0]?.message?.content - } - } catch (error) { - console.error('Chat API error:', error) - return { - success: false, - error: 'Sorry, I encountered an error. Please try again.' - } - } -} - -/** - * Provide offline responses when API is not configured - */ -function getOfflineResponse(userMessage: string): string { - const lowerMessage = userMessage.toLowerCase() - - if (lowerMessage.includes('hello') || lowerMessage.includes('hi') || lowerMessage.includes('hey')) { - return "Hi there! I'm Dom's AI assistant. I can tell you about his career, skills, travel experiences, and this website. What would you like to know?" - } - - if (lowerMessage.includes('career') || lowerMessage.includes('work') || lowerMessage.includes('job')) { - return "Dom is a Senior Vice President at Citi, working at the intersection of finance and technology. He has experience in data analytics, process automation, and digital transformation. Check out the Career page for more details!" - } - - if (lowerMessage.includes('skill') || lowerMessage.includes('tech') || lowerMessage.includes('programming')) { - return "Dom works with TypeScript, React, Next.js, Python, and SQL among other technologies. He's experienced in both frontend and backend development. Visit the Skills page to see the full list!" - } - - if (lowerMessage.includes('travel') || lowerMessage.includes('countr') || lowerMessage.includes('visit')) { - return "Dom has traveled to 52 countries across 5 continents! He's explored extensively in Asia, Europe, and the Americas. Check out the Travel page to see the interactive map of his adventures." - } - - if (lowerMessage.includes('contact') || lowerMessage.includes('resume') || lowerMessage.includes('hire') || lowerMessage.includes('email')) { - return "You can request Dom's resume through the Contact page - just enter your name and email, and it'll be sent right to you. You can also reach out via the contact form there." - } - - if (lowerMessage.includes('website') || lowerMessage.includes('built') || lowerMessage.includes('stack')) { - return "This portfolio is built with Next.js 16, React 19, TypeScript, and Tailwind CSS. It's statically exported and deployed to GitHub Pages. Dom built it himself to showcase his work and travels." - } - - if (lowerMessage.includes('education') || lowerMessage.includes('degree') || lowerMessage.includes('school')) { - return "You can find details about Dom's educational background on the Education page. It covers his academic journey and certifications." - } - - return "I can help you learn about Dom's career, technical skills, travel adventures, or this website. Feel free to ask me anything, or explore the navigation above to dive into specific topics!" -} +/** + * AI Chat Service + * Handles communication with the AI chat API + */ + +export interface ChatMessage { + role: 'user' | 'assistant' | 'system' + content: string +} + +export interface ChatResponse { + success: boolean + message?: string + error?: string +} + +// Context about Dom that the AI should know. Keep this in sync with +// lib/content/projects.ts — the projects are the point of the site. +export const DOM_CONTEXT = `You are Dom Mangonon's AI assistant on his personal site. The site is a portfolio: it leads with the things Dom has built. Help visitors understand those projects first, and his career only if they ask. + +## About Dom +- Dominic "Dom" Mangonon, New York metropolitan area +- Seventeen years in financial services; currently SVP, Transformation at Citi, where he works on enterprise AI adoption +- Went all in on AI in 2025 and started shipping software nights and weekends, all built using coding agents — including this site + +## Projects (the main thing on the site) +- SousIQ — restaurant cost management. Parses vendor invoices into line items and matches them to inventory products using embeddings and fuzzy search. Postgres with pgvector, row-level security for multi-tenancy, Claude Haiku for parsing and Sonnet for harder passes. Live and field-tested in a working bakery. Source is private. +- Bracketeer — tournament bracket pool. Create a pool, invite friends, make picks, watch a live leaderboard. Knockout seeding implements FIFA Annex C. Next.js, Prisma, Auth.js, Railway. Live, and ran a real World Cup pool. Started as a pool for friends. +- Claude Code Placemat — a one-page Claude Code reference (shortcuts, slash commands, flags, hooks, MCP) that maintains itself: a scheduled agent re-reads each release and opens a PR when anything drifts. Static HTML on GitHub Pages, MIT licensed. +- modular-mind — builds a corpus of 3,500+ VCV Rack modular-synth patches and generates new ones from learned patterns. Decodes the binary patch format, profiles modules, validates signal flow. Python. +- PRIAL Pipeline — turns monthly SEC Form ADV bulk filings into a deduped registry of 23,000+ investment-adviser firms, with officers and firm sites. Python. Private. + +## Career, briefly +Operations at BNP Paribas through the 2008 crisis, MBA at CMU Tepper, consulting at PwC/Strategy& and Treliant, wealth-management strategy at Morgan Stanley, and Citi since 2021. The site has a short timeline; LinkedIn has the long version. + +## Writing +Dom writes at Context//Collapse on Substack (dommangonon.substack.com) — notes on building with AI. + +## Also +Has travelled to 52 countries across 5 continents; the site has an interactive map. + +## Response Guidelines +- Be friendly and direct. Keep it to 2-3 sentences for simple questions. +- The site is a single page — point people to a section (Projects, Writing, Career, Travel, Contact), never to a separate page or URL path. +- Never invent numbers, dates, or facts. If you don't know, say so and suggest they reach out via the Contact section. +- Don't oversell him. The projects speak for themselves; describe them plainly. +` + +/** + * Send a message to the AI chat API + */ +export async function sendChatMessage( + messages: ChatMessage[] +): Promise { + const apiUrl = process.env.NEXT_PUBLIC_CHAT_API_URL + + // If no API configured, return a helpful message + if (!apiUrl) { + return { + success: true, + message: getOfflineResponse(messages[messages.length - 1]?.content || '') + } + } + + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + messages: [ + { role: 'system', content: DOM_CONTEXT }, + ...messages + ] + }) + }) + + if (!response.ok) { + throw new Error(`API error: ${response.status}`) + } + + const data = await response.json() + return { + success: true, + message: data.message || data.content || data.choices?.[0]?.message?.content + } + } catch (error) { + console.error('Chat API error:', error) + return { + success: false, + error: 'Sorry, I encountered an error. Please try again.' + } + } +} + +/** + * Provide offline responses when API is not configured + */ +function getOfflineResponse(userMessage: string): string { + const lowerMessage = userMessage.toLowerCase() + + if (lowerMessage.includes('hello') || lowerMessage.includes('hi') || lowerMessage.includes('hey')) { + return "Hi there! I'm Dom's AI assistant. I can tell you about his career, skills, travel experiences, and this website. What would you like to know?" + } + + if (lowerMessage.includes('career') || lowerMessage.includes('work') || lowerMessage.includes('job')) { + return "Dom is a Senior Vice President at Citi, working at the intersection of finance and technology. He has experience in data analytics, process automation, and digital transformation. Check out the Career page for more details!" + } + + if (lowerMessage.includes('skill') || lowerMessage.includes('tech') || lowerMessage.includes('programming')) { + return "Dom works with TypeScript, React, Next.js, Python, and SQL among other technologies. He's experienced in both frontend and backend development. Visit the Skills page to see the full list!" + } + + if (lowerMessage.includes('travel') || lowerMessage.includes('countr') || lowerMessage.includes('visit')) { + return "Dom has traveled to 52 countries across 5 continents! He's explored extensively in Asia, Europe, and the Americas. Check out the Travel page to see the interactive map of his adventures." + } + + if (lowerMessage.includes('contact') || lowerMessage.includes('resume') || lowerMessage.includes('hire') || lowerMessage.includes('email')) { + return "You can request Dom's resume through the Contact page - just enter your name and email, and it'll be sent right to you. You can also reach out via the contact form there." + } + + if (lowerMessage.includes('website') || lowerMessage.includes('built') || lowerMessage.includes('stack')) { + return "This portfolio is built with Next.js 16, React 19, TypeScript, and Tailwind CSS. It's statically exported and deployed to GitHub Pages. Dom built it himself to showcase his work and travels." + } + + if (lowerMessage.includes('education') || lowerMessage.includes('degree') || lowerMessage.includes('school')) { + return "You can find details about Dom's educational background on the Education page. It covers his academic journey and certifications." + } + + return "I can help you learn about Dom's career, technical skills, travel adventures, or this website. Feel free to ask me anything, or explore the navigation above to dive into specific topics!" +} From b89e946d8a84c6b0800aa35f5a41e98ff226170d Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:34:29 -0400 Subject: [PATCH 3/6] fix: correct Argentina coordinates, add Aruba MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Argentina's latitude was 38.4161 where every other South American entry is negative — the sign was dropped on import, so it rendered in the North Atlantic off Portugal rather than in South America. Adds Aruba (first visited August 2026), confirmed against the Southwest and JetBlue itineraries: MDW-MCO-AUA on 08/17, AUA-JFK on 08/21. Country count 52 -> 53, range 1986-2024 -> 1986-2026. The source CSV in ~/personal/travel (a Notion export) got the row too, so a regen keeps it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V3tDuknYtu9JiCr4w2s4b8 --- content/travel/countries.json | 22 +++++++++++++++++----- scripts/process-travel-data.js | 5 +++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/content/travel/countries.json b/content/travel/countries.json index ac45492..9e0c285 100644 --- a/content/travel/countries.json +++ b/content/travel/countries.json @@ -608,7 +608,7 @@ "order": 51, "continent": "South America", "coordinates": [ - 38.4161, + -38.4161, -63.6167 ] }, @@ -623,10 +623,22 @@ -14.235, -51.9253 ] + }, + { + "id": "aruba", + "name": "Aruba", + "flag": "🇦🇼", + "firstVisited": 2026, + "order": 53, + "continent": "North America", + "coordinates": [ + 12.5211, + -69.9683 + ] } ], "stats": { - "totalCountries": 52, + "totalCountries": 53, "continents": 5, "continentNames": [ "Africa", @@ -635,14 +647,14 @@ "North America", "South America" ], - "yearRange": "1986-2024", + "yearRange": "1986-2026", "firstYear": 1986, - "lastYear": 2024, + "lastYear": 2026, "countriesByContinent": { "Africa": 1, "Asia": 15, "Europe": 21, - "North America": 9, + "North America": 10, "South America": 6 } } diff --git a/scripts/process-travel-data.js b/scripts/process-travel-data.js index d13b9d7..fa674e1 100644 --- a/scripts/process-travel-data.js +++ b/scripts/process-travel-data.js @@ -59,8 +59,9 @@ const COUNTRY_DATA = { 'Portugal': { continent: 'Europe', coords: [39.3999, -8.2245] }, 'Taiwan': { continent: 'Asia', coords: [23.6978, 120.9605] }, 'Qatar': { continent: 'Asia', coords: [25.3548, 51.1839] }, - 'Argentina': { continent: 'South America', coords: [38.4161, -63.6167] }, - 'Brazil': { continent: 'South America', coords: [-14.2350, -51.9253] } + 'Argentina': { continent: 'South America', coords: [-38.4161, -63.6167] }, + 'Brazil': { continent: 'South America', coords: [-14.2350, -51.9253] }, + 'Aruba': { continent: 'North America', coords: [12.5211, -69.9683] } } function parseYear(yearStr) { From e2f0b411daacb430c08436da293c388622b95564 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:51:09 -0400 Subject: [PATCH 4/6] fix: add 2026 flights, make route direction deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six flights flown since the Flighty->Notion sync stopped on 2025-06-29, added to the Notion log and regenerated: UA2143/UA2036 EWR-SJU-EWR (04/10, 04/13), UA3562 EWR-MKE (08/14), WN65/WN1443 MDW-MCO-AUA (08/17), B6 1858 AUA-JFK (08/21). Flighty is gone, so Notion is now the log itself rather than a sync target. generateRoutes normalized the map key so A->B and B->A aggregate, but stored from/to from whichever leg Notion returned first, so the rendered direction — and the diff — changed on every re-run. Store the canonical alphabetical direction instead; output is now stable across runs. Regenerating also reconciles drift: the committed file listed an EWR-SJU pair that isn't in Notion and recorded EWR-LHR as 1 where Notion holds 3. Totals 213 -> 219 flights, 106 routes, no routes lost. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V3tDuknYtu9JiCr4w2s4b8 --- content/travel/flights.json | 1549 +++++++++++++++++++++++++---------- scripts/fetch-flights.js | 15 +- 2 files changed, 1112 insertions(+), 452 deletions(-) diff --git a/content/travel/flights.json b/content/travel/flights.json index 7be8f88..9c5e3de 100644 --- a/content/travel/flights.json +++ b/content/travel/flights.json @@ -1,727 +1,1384 @@ { - "totalFlights": 213, + "totalFlights": 219, "routes": [ { - "from": "EWR", - "to": "SJU", - "fromCoords": [40.6895, -74.1745], - "toCoords": [18.4394, -66.0018], + "from": "AUA", + "to": "JFK", + "fromCoords": [ + 12.5014, + -70.0152 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], "count": 1 }, { - "from": "SJU", - "to": "EWR", - "fromCoords": [18.4394, -66.0018], - "toCoords": [40.6895, -74.1745], + "from": "AUA", + "to": "MCO", + "fromCoords": [ + 12.5014, + -70.0152 + ], + "toCoords": [ + 28.4312, + -81.3081 + ], "count": 1 }, { - "from": "DEN", - "to": "MSP", - "fromCoords": [39.8561, -104.6737], - "toCoords": [44.8848, -93.2223], - "count": 4 + "from": "MCO", + "to": "MDW", + "fromCoords": [ + 28.4312, + -81.3081 + ], + "toCoords": [ + 41.7868, + -87.7522 + ], + "count": 1 }, { - "from": "MDW", - "to": "MSP", - "fromCoords": [41.7868, -87.7522], - "toCoords": [44.8848, -93.2223], - "count": 56 + "from": "EWR", + "to": "MKE", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 42.9472, + -87.8966 + ], + "count": 1 }, { "from": "EWR", - "to": "MSP", - "fromCoords": [40.6895, -74.1745], - "toCoords": [44.8848, -93.2223], - "count": 5 + "to": "SJU", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 18.4394, + -66.0018 + ], + "count": 2 }, { - "from": "YYZ", - "to": "BCN", - "fromCoords": [43.6777, -79.6248], - "toCoords": [41.2974, 2.0833], + "from": "EWR", + "to": "LHR", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 51.47, + -0.4543 + ], + "count": 3 + }, + { + "from": "OAX", + "to": "PXM", + "fromCoords": [ + 16.9999, + -96.7266 + ], + "toCoords": [ + 15.8769, + -97.0891 + ], "count": 1 }, { - "from": "MSP", - "to": "LAX", - "fromCoords": [44.8848, -93.2223], - "toCoords": [33.9416, -118.4085], + "from": "HAV", + "to": "MIA", + "fromCoords": [ + 22.9892, + -82.4091 + ], + "toCoords": [ + 25.7959, + -80.287 + ], "count": 2 }, { - "from": "ATL", - "to": "LGA", - "fromCoords": [33.6407, -84.4277], - "toCoords": [40.7769, -73.874], + "from": "EWR", + "to": "PTY", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 9.0714, + -79.3835 + ], "count": 2 }, { - "from": "CMN", - "to": "JFK", - "fromCoords": [33.3675, -7.5898], - "toCoords": [40.6413, -73.7781], + "from": "EWR", + "to": "SJO", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 9.9939, + -84.2088 + ], "count": 2 }, { - "from": "JFK", - "to": "MNL", - "fromCoords": [40.6413, -73.7781], - "toCoords": [14.5086, 121.0194], + "from": "EWR", + "to": "SEA", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 47.4502, + -122.3088 + ], "count": 1 }, { - "from": "YWG", - "to": "YVR", - "fromCoords": [49.91, -97.2399], - "toCoords": [49.1967, -123.1815], + "from": "MSY", + "to": "PHL", + "fromCoords": [ + 29.9934, + -90.258 + ], + "toCoords": [ + 39.8729, + -75.2437 + ], "count": 1 }, { "from": "BOS", "to": "PIT", - "fromCoords": [42.3656, -71.0096], - "toCoords": [40.4915, -80.2329], + "fromCoords": [ + 42.3656, + -71.0096 + ], + "toCoords": [ + 40.4915, + -80.2329 + ], "count": 2 }, { - "from": "MSP", - "to": "LGA", - "fromCoords": [44.8848, -93.2223], - "toCoords": [40.7769, -73.874], - "count": 9 + "from": "DEN", + "to": "PIT", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 40.4915, + -80.2329 + ], + "count": 2 }, { - "from": "MDW", - "to": "EWR", - "fromCoords": [41.7868, -87.7522], - "toCoords": [40.6895, -74.1745], + "from": "LGA", + "to": "MDW", + "fromCoords": [ + 40.7769, + -73.874 + ], + "toCoords": [ + 41.7868, + -87.7522 + ], "count": 2 }, { - "from": "DOH", - "to": "SGN", - "fromCoords": [25.2609, 51.6138], - "toCoords": [10.8188, 106.652], + "from": "JFK", + "to": "PIT", + "fromCoords": [ + 40.6413, + -73.7781 + ], + "toCoords": [ + 40.4915, + -80.2329 + ], "count": 1 }, { - "from": "LGA", - "to": "ORD", - "fromCoords": [40.7769, -73.874], - "toCoords": [41.9742, -87.9073], - "count": 4 + "from": "ATL", + "to": "PIT", + "fromCoords": [ + 33.6407, + -84.4277 + ], + "toCoords": [ + 40.4915, + -80.2329 + ], + "count": 1 }, { - "from": "MSY", - "to": "ATL", - "fromCoords": [29.9934, -90.258], - "toCoords": [33.6407, -84.4277], + "from": "ATL", + "to": "MSY", + "fromCoords": [ + 33.6407, + -84.4277 + ], + "toCoords": [ + 29.9934, + -90.258 + ], "count": 2 }, { - "from": "JFK", - "to": "SLC", - "fromCoords": [40.6413, -73.7781], - "toCoords": [40.7899, -111.9791], + "from": "CLT", + "to": "MSY", + "fromCoords": [ + 35.214, + -80.9431 + ], + "toCoords": [ + 29.9934, + -90.258 + ], "count": 1 }, { - "from": "SLC", - "to": "DEN", - "fromCoords": [40.7899, -111.9791], - "toCoords": [39.8561, -104.6737], - "count": 3 - }, - { - "from": "BCN", - "to": "EAS", - "fromCoords": [41.2974, 2.0833], - "toCoords": [43.3565, -1.7906], + "from": "CLT", + "to": "JFK", + "fromCoords": [ + 35.214, + -80.9431 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], "count": 1 }, { - "from": "LGA", - "to": "MDW", - "fromCoords": [40.7769, -73.874], - "toCoords": [41.7868, -87.7522], + "from": "ATL", + "to": "LGA", + "fromCoords": [ + 33.6407, + -84.4277 + ], + "toCoords": [ + 40.7769, + -73.874 + ], "count": 2 }, { - "from": "EWR", - "to": "LHR", - "fromCoords": [40.6895, -74.1745], - "toCoords": [51.47, -0.4543], + "from": "ATL", + "to": "GIG", + "fromCoords": [ + 33.6407, + -84.4277 + ], + "toCoords": [ + -22.8099, + -43.2505 + ], "count": 1 }, { - "from": "MNL", - "to": "ENI", - "fromCoords": [14.5086, 121.0194], - "toCoords": [11.2296, 125.2593], - "count": 2 + "from": "AEP", + "to": "GIG", + "fromCoords": [ + -34.5592, + -58.4156 + ], + "toCoords": [ + -22.8099, + -43.2505 + ], + "count": 1 }, { - "from": "JFK", - "to": "CUN", - "fromCoords": [40.6413, -73.7781], - "toCoords": [21.0365, -86.8771], + "from": "AEP", + "to": "FTE", + "fromCoords": [ + -34.5592, + -58.4156 + ], + "toCoords": [ + -50.2803, + -72.0531 + ], "count": 2 }, { - "from": "LIS", - "to": "EWR", - "fromCoords": [38.7756, -9.1354], - "toCoords": [40.6895, -74.1745], + "from": "EZE", + "to": "JFK", + "fromCoords": [ + -34.8222, + -58.5358 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], "count": 1 }, { - "from": "ORD", - "to": "KEF", - "fromCoords": [41.9742, -87.9073], - "toCoords": [63.985, -22.6056], - "count": 1 + "from": "DOH", + "to": "JFK", + "fromCoords": [ + 25.2609, + 51.6138 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], + "count": 2 }, { - "from": "DTW", - "to": "LEX", - "fromCoords": [42.2162, -83.3554], - "toCoords": [38.0365, -84.6059], + "from": "BKK", + "to": "DOH", + "fromCoords": [ + 13.69, + 100.7501 + ], + "toCoords": [ + 25.2609, + 51.6138 + ], "count": 1 }, { - "from": "DEN", - "to": "ORD", - "fromCoords": [39.8561, -104.6737], - "toCoords": [41.9742, -87.9073], + "from": "BKK", + "to": "HKT", + "fromCoords": [ + 13.69, + 100.7501 + ], + "toCoords": [ + 8.1132, + 98.3169 + ], "count": 1 }, { - "from": "KEF", - "to": "AMS", - "fromCoords": [63.985, -22.6056], - "toCoords": [52.3105, 4.7683], + "from": "HKT", + "to": "SGN", + "fromCoords": [ + 8.1132, + 98.3169 + ], + "toCoords": [ + 10.8188, + 106.652 + ], "count": 1 }, { - "from": "SJO", - "to": "EWR", - "fromCoords": [9.9939, -84.2088], - "toCoords": [40.6895, -74.1745], - "count": 2 - }, - { - "from": "EWR", - "to": "SLC", - "fromCoords": [40.6895, -74.1745], - "toCoords": [40.7899, -111.9791], + "from": "DOH", + "to": "SGN", + "fromCoords": [ + 25.2609, + 51.6138 + ], + "toCoords": [ + 10.8188, + 106.652 + ], "count": 1 }, { - "from": "MBJ", + "from": "CUN", "to": "JFK", - "fromCoords": [18.5037, -77.9134], - "toCoords": [40.6413, -73.7781], - "count": 2 - }, - { - "from": "MSP", - "to": "SFO", - "fromCoords": [44.8848, -93.2223], - "toCoords": [37.6213, -122.379], + "fromCoords": [ + 21.0365, + -86.8771 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], "count": 2 }, { - "from": "MSY", - "to": "EWR", - "fromCoords": [29.9934, -90.258], - "toCoords": [40.6895, -74.1745], + "from": "ATL", + "to": "LEX", + "fromCoords": [ + 33.6407, + -84.4277 + ], + "toCoords": [ + 38.0365, + -84.6059 + ], "count": 1 }, { - "from": "MSP", - "to": "YWG", - "fromCoords": [44.8848, -93.2223], - "toCoords": [49.91, -97.2399], + "from": "DTW", + "to": "LEX", + "fromCoords": [ + 42.2162, + -83.3554 + ], + "toCoords": [ + 38.0365, + -84.6059 + ], "count": 1 }, { - "from": "BIO", - "to": "LIS", - "fromCoords": [43.3011, -2.9106], - "toCoords": [38.7756, -9.1354], + "from": "DTW", + "to": "LGA", + "fromCoords": [ + 42.2162, + -83.3554 + ], + "toCoords": [ + 40.7769, + -73.874 + ], "count": 1 }, { - "from": "TPA", - "to": "LGA", - "fromCoords": [27.9756, -82.5333], - "toCoords": [40.7769, -73.874], - "count": 2 + "from": "JFK", + "to": "TPE", + "fromCoords": [ + 40.6413, + -73.7781 + ], + "toCoords": [ + 25.0797, + 121.2342 + ], + "count": 1 }, { "from": "MNL", "to": "TPE", - "fromCoords": [14.5086, 121.0194], - "toCoords": [25.0797, 121.2342], + "fromCoords": [ + 14.5086, + 121.0194 + ], + "toCoords": [ + 25.0797, + 121.2342 + ], "count": 1 }, { - "from": "CLT", - "to": "MSY", - "fromCoords": [35.214, -80.9431], - "toCoords": [29.9934, -90.258], + "from": "ENI", + "to": "MNL", + "fromCoords": [ + 11.2296, + 125.2593 + ], + "toCoords": [ + 14.5086, + 121.0194 + ], + "count": 2 + }, + { + "from": "JFK", + "to": "MNL", + "fromCoords": [ + 40.6413, + -73.7781 + ], + "toCoords": [ + 14.5086, + 121.0194 + ], "count": 1 }, { - "from": "HKG", - "to": "JFK", - "fromCoords": [22.308, 113.9185], - "toCoords": [40.6413, -73.7781], + "from": "EWR", + "to": "MEX", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 19.4363, + -99.0721 + ], "count": 2 }, { - "from": "DEN", - "to": "RNO", - "fromCoords": [39.8561, -104.6737], - "toCoords": [39.4991, -119.7681], + "from": "MEX", + "to": "PXM", + "fromCoords": [ + 19.4363, + -99.0721 + ], + "toCoords": [ + 15.8769, + -97.0891 + ], "count": 1 }, { - "from": "RNO", - "to": "SFO", - "fromCoords": [39.4991, -119.7681], - "toCoords": [37.6213, -122.379], + "from": "MEX", + "to": "OAX", + "fromCoords": [ + 19.4363, + -99.0721 + ], + "toCoords": [ + 16.9999, + -96.7266 + ], "count": 1 }, { - "from": "KRK", - "to": "STN", - "fromCoords": [50.0777, 19.7848], - "toCoords": [51.885, 0.235], - "count": 2 - }, - { - "from": "KTM", - "to": "BOM", - "fromCoords": [27.6966, 85.3591], - "toCoords": [19.0896, 72.8656], + "from": "EWR", + "to": "SLC", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 40.7899, + -111.9791 + ], "count": 1 }, { - "from": "ORD", - "to": "PVR", - "fromCoords": [41.9742, -87.9073], - "toCoords": [20.6801, -105.2543], + "from": "EWR", + "to": "LAS", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 36.084, + -115.1537 + ], "count": 2 }, { - "from": "AEP", - "to": "FTE", - "fromCoords": [-34.5592, -58.4156], - "toCoords": [-50.2803, -72.0531], - "count": 2 + "from": "DEN", + "to": "LGA", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 40.7769, + -73.874 + ], + "count": 3 }, { - "from": "YTZ", - "to": "MDW", - "fromCoords": [43.6275, -79.3962], - "toCoords": [41.7868, -87.7522], - "count": 2 + "from": "DEN", + "to": "SLC", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 40.7899, + -111.9791 + ], + "count": 3 }, { - "from": "AEP", - "to": "GIG", - "fromCoords": [-34.5592, -58.4156], - "toCoords": [-22.8099, -43.2505], + "from": "DEN", + "to": "RNO", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 39.4991, + -119.7681 + ], "count": 1 }, { - "from": "HAV", - "to": "MIA", - "fromCoords": [22.9892, -82.4091], - "toCoords": [25.7959, -80.287], - "count": 2 + "from": "LGA", + "to": "ORD", + "fromCoords": [ + 40.7769, + -73.874 + ], + "toCoords": [ + 41.9742, + -87.9073 + ], + "count": 4 }, { - "from": "ORD", - "to": "YYZ", - "fromCoords": [41.9742, -87.9073], - "toCoords": [43.6777, -79.6248], - "count": 5 + "from": "EWR", + "to": "LIS", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 38.7756, + -9.1354 + ], + "count": 1 }, { - "from": "MDE", - "to": "MIA", - "fromCoords": [6.1645, -75.4231], - "toCoords": [25.7959, -80.287], + "from": "BIO", + "to": "LIS", + "fromCoords": [ + 43.3011, + -2.9106 + ], + "toCoords": [ + 38.7756, + -9.1354 + ], "count": 1 }, { - "from": "SFO", - "to": "YVR", - "fromCoords": [37.6213, -122.379], - "toCoords": [49.1967, -123.1815], + "from": "BCN", + "to": "EAS", + "fromCoords": [ + 41.2974, + 2.0833 + ], + "toCoords": [ + 43.3565, + -1.7906 + ], "count": 1 }, { - "from": "EWR", - "to": "PTY", - "fromCoords": [40.6895, -74.1745], - "toCoords": [9.0714, -79.3835], - "count": 2 + "from": "BCN", + "to": "YYZ", + "fromCoords": [ + 41.2974, + 2.0833 + ], + "toCoords": [ + 43.6777, + -79.6248 + ], + "count": 1 }, { - "from": "EWR", - "to": "MEX", - "fromCoords": [40.6895, -74.1745], - "toCoords": [19.4363, -99.0721], + "from": "LGA", + "to": "YYZ", + "fromCoords": [ + 40.7769, + -73.874 + ], + "toCoords": [ + 43.6777, + -79.6248 + ], "count": 2 }, { - "from": "SEA", - "to": "EWR", - "fromCoords": [47.4502, -122.3088], - "toCoords": [40.6895, -74.1745], + "from": "JFK", + "to": "SLC", + "fromCoords": [ + 40.6413, + -73.7781 + ], + "toCoords": [ + 40.7899, + -111.9791 + ], "count": 1 }, { - "from": "PIT", - "to": "DEN", - "fromCoords": [40.4915, -80.2329], - "toCoords": [39.8561, -104.6737], + "from": "LGA", + "to": "TPA", + "fromCoords": [ + 40.7769, + -73.874 + ], + "toCoords": [ + 27.9756, + -82.5333 + ], "count": 2 }, { - "from": "EWR", - "to": "YYZ", - "fromCoords": [40.6895, -74.1745], - "toCoords": [43.6777, -79.6248], + "from": "SFO", + "to": "YVR", + "fromCoords": [ + 37.6213, + -122.379 + ], + "toCoords": [ + 49.1967, + -123.1815 + ], + "count": 1 + }, + { + "from": "RNO", + "to": "SFO", + "fromCoords": [ + 39.4991, + -119.7681 + ], + "toCoords": [ + 37.6213, + -122.379 + ], "count": 1 }, { "from": "EWR", - "to": "MIA", - "fromCoords": [40.6895, -74.1745], - "toCoords": [25.7959, -80.287], + "to": "MDW", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 41.7868, + -87.7522 + ], "count": 2 }, { - "from": "ICN", - "to": "DTW", - "fromCoords": [37.4602, 126.4407], - "toCoords": [42.2162, -83.3554], - "count": 1 + "from": "DTW", + "to": "ORD", + "fromCoords": [ + 42.2162, + -83.3554 + ], + "toCoords": [ + 41.9742, + -87.9073 + ], + "count": 2 }, { - "from": "BKK", - "to": "DOH", - "fromCoords": [13.69, 100.7501], - "toCoords": [25.2609, 51.6138], + "from": "DTW", + "to": "ICN", + "fromCoords": [ + 42.2162, + -83.3554 + ], + "toCoords": [ + 37.4602, + 126.4407 + ], "count": 1 }, { - "from": "LIM", - "to": "PTY", - "fromCoords": [-12.0219, -77.1143], - "toCoords": [9.0714, -79.3835], + "from": "ICN", + "to": "SGN", + "fromCoords": [ + 37.4602, + 126.4407 + ], + "toCoords": [ + 10.8188, + 106.652 + ], "count": 1 }, { - "from": "MSP", - "to": "IAH", - "fromCoords": [44.8848, -93.2223], - "toCoords": [29.9902, -95.3368], - "count": 2 + "from": "KUL", + "to": "SGN", + "fromCoords": [ + 2.7456, + 101.7099 + ], + "toCoords": [ + 10.8188, + 106.652 + ], + "count": 1 }, { - "from": "EWR", - "to": "LAS", - "fromCoords": [40.6895, -74.1745], - "toCoords": [36.084, -115.1537], - "count": 2 + "from": "COK", + "to": "KUL", + "fromCoords": [ + 10.152, + 76.4019 + ], + "toCoords": [ + 2.7456, + 101.7099 + ], + "count": 1 }, { - "from": "EWR", - "to": "PDX", - "fromCoords": [40.6895, -74.1745], - "toCoords": [45.5898, -122.5951], + "from": "BOM", + "to": "GOI", + "fromCoords": [ + 19.0896, + 72.8656 + ], + "toCoords": [ + 15.3808, + 73.8314 + ], "count": 1 }, { - "from": "AMS", - "to": "DMM", - "fromCoords": [52.3105, 4.7683], - "toCoords": [26.4712, 49.7979], + "from": "BOM", + "to": "KTM", + "fromCoords": [ + 19.0896, + 72.8656 + ], + "toCoords": [ + 27.6966, + 85.3591 + ], "count": 1 }, { - "from": "ATL", - "to": "PIT", - "fromCoords": [33.6407, -84.4277], - "toCoords": [40.4915, -80.2329], + "from": "KTM", + "to": "MCT", + "fromCoords": [ + 27.6966, + 85.3591 + ], + "toCoords": [ + 23.5933, + 58.2844 + ], "count": 1 }, { - "from": "PNH", - "to": "HKG", - "fromCoords": [11.5466, 104.8441], - "toCoords": [22.308, 113.9185], - "count": 2 + "from": "DMM", + "to": "MCT", + "fromCoords": [ + 26.4712, + 49.7979 + ], + "toCoords": [ + 23.5933, + 58.2844 + ], + "count": 1 }, { - "from": "JFK", - "to": "EZE", - "fromCoords": [40.6413, -73.7781], - "toCoords": [-34.8222, -58.5358], + "from": "AMS", + "to": "DMM", + "fromCoords": [ + 52.3105, + 4.7683 + ], + "toCoords": [ + 26.4712, + 49.7979 + ], "count": 1 }, { - "from": "LGA", - "to": "DTW", - "fromCoords": [40.7769, -73.874], - "toCoords": [42.2162, -83.3554], + "from": "AMS", + "to": "KEF", + "fromCoords": [ + 52.3105, + 4.7683 + ], + "toCoords": [ + 63.985, + -22.6056 + ], "count": 1 }, { - "from": "OAX", - "to": "PXM", - "fromCoords": [16.9999, -96.7266], - "toCoords": [15.8769, -97.0891], + "from": "KEF", + "to": "ORD", + "fromCoords": [ + 63.985, + -22.6056 + ], + "toCoords": [ + 41.9742, + -87.9073 + ], "count": 1 }, { - "from": "BOM", - "to": "GOI", - "fromCoords": [19.0896, 72.8656], - "toCoords": [15.3808, 73.8314], + "from": "DEN", + "to": "ORD", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 41.9742, + -87.9073 + ], "count": 1 }, { - "from": "BKK", - "to": "USM", - "fromCoords": [13.69, 100.7501], - "toCoords": [9.5478, 100.0623], + "from": "DEN", + "to": "MDW", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 41.7868, + -87.7522 + ], "count": 1 }, { - "from": "LHR", - "to": "MSP", - "fromCoords": [51.47, -0.4543], - "toCoords": [44.8848, -93.2223], - "count": 1 + "from": "ORD", + "to": "YYZ", + "fromCoords": [ + 41.9742, + -87.9073 + ], + "toCoords": [ + 43.6777, + -79.6248 + ], + "count": 5 }, { - "from": "HKG", - "to": "HAN", - "fromCoords": [22.308, 113.9185], - "toCoords": [21.2187, 105.8042], - "count": 1 + "from": "MDW", + "to": "YTZ", + "fromCoords": [ + 41.7868, + -87.7522 + ], + "toCoords": [ + 43.6275, + -79.3962 + ], + "count": 2 }, { - "from": "HAN", - "to": "SGN", - "fromCoords": [21.2187, 105.8042], - "toCoords": [10.8188, 106.652], + "from": "EWR", + "to": "YYZ", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 43.6777, + -79.6248 + ], "count": 1 }, { - "from": "SGN", - "to": "ICN", - "fromCoords": [10.8188, 106.652], - "toCoords": [37.4602, 126.4407], + "from": "JFK", + "to": "YVR", + "fromCoords": [ + 40.6413, + -73.7781 + ], + "toCoords": [ + 49.1967, + -123.1815 + ], "count": 1 }, { - "from": "LGA", - "to": "YYZ", - "fromCoords": [40.7769, -73.874], - "toCoords": [43.6777, -79.6248], - "count": 2 + "from": "YVR", + "to": "YWG", + "fromCoords": [ + 49.1967, + -123.1815 + ], + "toCoords": [ + 49.91, + -97.2399 + ], + "count": 1 }, { - "from": "PHL", - "to": "MSY", - "fromCoords": [39.8729, -75.2437], - "toCoords": [29.9934, -90.258], + "from": "MSP", + "to": "YWG", + "fromCoords": [ + 44.8848, + -93.2223 + ], + "toCoords": [ + 49.91, + -97.2399 + ], "count": 1 }, + { + "from": "MDW", + "to": "MSP", + "fromCoords": [ + 41.7868, + -87.7522 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], + "count": 56 + }, { "from": "DTW", - "to": "ORD", - "fromCoords": [42.2162, -83.3554], - "toCoords": [41.9742, -87.9073], - "count": 2 + "to": "YYZ", + "fromCoords": [ + 42.2162, + -83.3554 + ], + "toCoords": [ + 43.6777, + -79.6248 + ], + "count": 1 }, { "from": "LGA", - "to": "DEN", - "fromCoords": [40.7769, -73.874], - "toCoords": [39.8561, -104.6737], - "count": 3 + "to": "MSP", + "fromCoords": [ + 40.7769, + -73.874 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], + "count": 9 }, { - "from": "MIA", - "to": "LPB", - "fromCoords": [25.7959, -80.287], - "toCoords": [-16.5133, -68.1923], - "count": 1 + "from": "ORD", + "to": "PVR", + "fromCoords": [ + 41.9742, + -87.9073 + ], + "toCoords": [ + 20.6801, + -105.2543 + ], + "count": 2 }, { - "from": "GIG", - "to": "ATL", - "fromCoords": [-22.8099, -43.2505], - "toCoords": [33.6407, -84.4277], - "count": 1 + "from": "DEN", + "to": "MSP", + "fromCoords": [ + 39.8561, + -104.6737 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], + "count": 4 }, { - "from": "TPE", - "to": "JFK", - "fromCoords": [25.0797, 121.2342], - "toCoords": [40.6413, -73.7781], - "count": 1 + "from": "EWR", + "to": "MSP", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], + "count": 5 }, { - "from": "SGN", - "to": "HKT", - "fromCoords": [10.8188, 106.652], - "toCoords": [8.1132, 98.3169], - "count": 1 + "from": "MSP", + "to": "SFO", + "fromCoords": [ + 44.8848, + -93.2223 + ], + "toCoords": [ + 37.6213, + -122.379 + ], + "count": 2 }, { - "from": "COK", - "to": "KUL", - "fromCoords": [10.152, 76.4019], - "toCoords": [2.7456, 101.7099], - "count": 1 + "from": "IAH", + "to": "MSP", + "fromCoords": [ + 29.9902, + -95.3368 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], + "count": 2 }, { - "from": "DMM", - "to": "MCT", - "fromCoords": [26.4712, 49.7979], - "toCoords": [23.5933, 58.2844], + "from": "LHR", + "to": "MSP", + "fromCoords": [ + 51.47, + -0.4543 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], "count": 1 }, { - "from": "HKT", - "to": "BKK", - "fromCoords": [8.1132, 98.3169], - "toCoords": [13.69, 100.7501], - "count": 1 + "from": "KRK", + "to": "STN", + "fromCoords": [ + 50.0777, + 19.7848 + ], + "toCoords": [ + 51.885, + 0.235 + ], + "count": 2 }, { - "from": "PXM", - "to": "MEX", - "fromCoords": [15.8769, -97.0891], - "toCoords": [19.4363, -99.0721], - "count": 1 + "from": "LAX", + "to": "MSP", + "fromCoords": [ + 33.9416, + -118.4085 + ], + "toCoords": [ + 44.8848, + -93.2223 + ], + "count": 2 }, { - "from": "PIT", - "to": "JFK", - "fromCoords": [40.4915, -80.2329], - "toCoords": [40.6413, -73.7781], + "from": "ATL", + "to": "EWR", + "fromCoords": [ + 33.6407, + -84.4277 + ], + "toCoords": [ + 40.6895, + -74.1745 + ], "count": 1 }, { - "from": "KUL", + "from": "HAN", "to": "SGN", - "fromCoords": [2.7456, 101.7099], - "toCoords": [10.8188, 106.652], + "fromCoords": [ + 21.2187, + 105.8042 + ], + "toCoords": [ + 10.8188, + 106.652 + ], "count": 1 }, { - "from": "JFK", - "to": "BOG", - "fromCoords": [40.6413, -73.7781], - "toCoords": [4.7016, -74.1469], + "from": "BKK", + "to": "USM", + "fromCoords": [ + 13.69, + 100.7501 + ], + "toCoords": [ + 9.5478, + 100.0623 + ], "count": 1 }, { - "from": "JFK", - "to": "CLT", - "fromCoords": [40.6413, -73.7781], - "toCoords": [35.214, -80.9431], + "from": "HAN", + "to": "HKG", + "fromCoords": [ + 21.2187, + 105.8042 + ], + "toCoords": [ + 22.308, + 113.9185 + ], "count": 1 }, { - "from": "EWR", - "to": "ATL", - "fromCoords": [40.6895, -74.1745], - "toCoords": [33.6407, -84.4277], + "from": "NRT", + "to": "ORD", + "fromCoords": [ + 35.772, + 140.3929 + ], + "toCoords": [ + 41.9742, + -87.9073 + ], "count": 1 }, { - "from": "MEX", - "to": "OAX", - "fromCoords": [19.4363, -99.0721], - "toCoords": [16.9999, -96.7266], + "from": "ORD", + "to": "PIT", + "fromCoords": [ + 41.9742, + -87.9073 + ], + "toCoords": [ + 40.4915, + -80.2329 + ], "count": 1 }, { - "from": "DOH", + "from": "CMN", "to": "JFK", - "fromCoords": [25.2609, 51.6138], - "toCoords": [40.6413, -73.7781], + "fromCoords": [ + 33.3675, + -7.5898 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], "count": 2 }, { - "from": "YYZ", - "to": "DTW", - "fromCoords": [43.6777, -79.6248], - "toCoords": [42.2162, -83.3554], - "count": 1 + "from": "EWR", + "to": "MIA", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 25.7959, + -80.287 + ], + "count": 2 }, { - "from": "PTY", - "to": "MDE", - "fromCoords": [9.0714, -79.3835], - "toCoords": [6.1645, -75.4231], + "from": "MDE", + "to": "MIA", + "fromCoords": [ + 6.1645, + -75.4231 + ], + "toCoords": [ + 25.7959, + -80.287 + ], "count": 1 }, { - "from": "YVR", - "to": "JFK", - "fromCoords": [49.1967, -123.1815], - "toCoords": [40.6413, -73.7781], + "from": "MDE", + "to": "PTY", + "fromCoords": [ + 6.1645, + -75.4231 + ], + "toCoords": [ + 9.0714, + -79.3835 + ], "count": 1 }, { - "from": "PIT", - "to": "ORD", - "fromCoords": [40.4915, -80.2329], - "toCoords": [41.9742, -87.9073], + "from": "LIM", + "to": "PTY", + "fromCoords": [ + -12.0219, + -77.1143 + ], + "toCoords": [ + 9.0714, + -79.3835 + ], "count": 1 }, { - "from": "MDW", - "to": "DEN", - "fromCoords": [41.7868, -87.7522], - "toCoords": [39.8561, -104.6737], + "from": "LPB", + "to": "MIA", + "fromCoords": [ + -16.5133, + -68.1923 + ], + "toCoords": [ + 25.7959, + -80.287 + ], "count": 1 }, { - "from": "LEX", - "to": "ATL", - "fromCoords": [38.0365, -84.6059], - "toCoords": [33.6407, -84.4277], + "from": "EWR", + "to": "MSY", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 29.9934, + -90.258 + ], "count": 1 }, { - "from": "ORD", - "to": "NRT", - "fromCoords": [41.9742, -87.9073], - "toCoords": [35.772, 140.3929], + "from": "EWR", + "to": "PDX", + "fromCoords": [ + 40.6895, + -74.1745 + ], + "toCoords": [ + 45.5898, + -122.5951 + ], "count": 1 }, { - "from": "MCT", - "to": "KTM", - "fromCoords": [23.5933, 58.2844], - "toCoords": [27.6966, 85.3591], + "from": "HKG", + "to": "JFK", + "fromCoords": [ + 22.308, + 113.9185 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], + "count": 2 + }, + { + "from": "HKG", + "to": "PNH", + "fromCoords": [ + 22.308, + 113.9185 + ], + "toCoords": [ + 11.5466, + 104.8441 + ], + "count": 2 + }, + { + "from": "BOG", + "to": "JFK", + "fromCoords": [ + 4.7016, + -74.1469 + ], + "toCoords": [ + 40.6413, + -73.7781 + ], "count": 1 + }, + { + "from": "JFK", + "to": "MBJ", + "fromCoords": [ + 40.6413, + -73.7781 + ], + "toCoords": [ + 18.5037, + -77.9134 + ], + "count": 2 } ], - "generatedAt": "2026-03-08T16:10:55.272Z" -} + "generatedAt": "2026-09-13T19:50:24.732Z" +} \ No newline at end of file diff --git a/scripts/fetch-flights.js b/scripts/fetch-flights.js index d8cb7fc..658de00 100644 --- a/scripts/fetch-flights.js +++ b/scripts/fetch-flights.js @@ -269,17 +269,20 @@ function generateRoutes(flights) { const key = `${flight.from}-${flight.to}` const reverseKey = `${flight.to}-${flight.from}` - // Normalize to always have the same direction - const normalizedKey = key < reverseKey ? key : reverseKey + // A->B and B->A are one route. Store the canonical (alphabetical) direction + // rather than whichever leg happened to come back from Notion first — + // otherwise the rendered direction, and the diff, changes on every re-run. + const forward = key < reverseKey + const normalizedKey = forward ? key : reverseKey if (routeMap.has(normalizedKey)) { routeMap.get(normalizedKey).count++ } else { routeMap.set(normalizedKey, { - from: flight.from, - to: flight.to, - fromCoords: flight.fromCoords, - toCoords: flight.toCoords, + from: forward ? flight.from : flight.to, + to: forward ? flight.to : flight.from, + fromCoords: forward ? flight.fromCoords : flight.toCoords, + toCoords: forward ? flight.toCoords : flight.fromCoords, count: 1 }) } From 631c75d3b30826ac7a13f55dd73f7df191b5216e Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sun, 13 Sep 2026 15:51:21 -0400 Subject: [PATCH 5/6] docs: record that Notion is now the flight log, not a sync target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flighty was deleted, so the `Flighty - App Data` DB has no producer — new flights are added to it by hand before re-running fetch-flights.js. Also notes where the countries CSV lives and that route output is now stable across re-runs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V3tDuknYtu9JiCr4w2s4b8 --- AGENTS.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 272f94c..c051857 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,11 @@ all data loaded at build time) · GitHub Pages deploy via `.github/workflows/dep - Content is hand-authored TS in `lib/content/`, not markdown/JSON (`resolveJsonModule` + `strict` infers `never[]` from an empty JSON array, which fails typecheck). Travel data is the exception — script-generated JSON via `scripts/process-travel-data.js` / - `scripts/fetch-flights.js` (both manual, not automated). + `scripts/fetch-flights.js` (both manual, not automated). Countries come from a Notion-exported + CSV at `~/personal/travel/`; flights from the `Flighty - App Data` Notion DB. Flighty itself was + deleted in 2026, so that DB is no longer a sync target — it's the hand-maintained log of record, + and new flights get added to it before re-running the script. `generateRoutes` merges A→B with + B→A and stores the alphabetical direction, so a re-run is byte-stable apart from `generatedAt`. - `github-actions[bot]` pushes with the default `GITHUB_TOKEN` don't trigger workflows, so a cron that commits data can't make the site rebuild. Anything needing fresh data at deploy time must fetch during the build, not commit-then-rebuild. From e8e7d3485137350929f92b697293aa541f28fa30 Mon Sep 17 00:00:00 2001 From: dommango-sys <251805093+dommango@users.noreply.github.com> Date: Sun, 13 Sep 2026 16:13:17 -0400 Subject: [PATCH 6/6] chore: restore CRLF line endings in chat.ts The copy reword in ce5bc6c rewrote the file LF, showing all 132 lines as changed. Restores the file's original CRLF so the diff is just the one reworded line. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V3tDuknYtu9JiCr4w2s4b8 --- lib/services/chat.ts | 264 +++++++++++++++++++++---------------------- 1 file changed, 132 insertions(+), 132 deletions(-) diff --git a/lib/services/chat.ts b/lib/services/chat.ts index cd605eb..9fa7d70 100644 --- a/lib/services/chat.ts +++ b/lib/services/chat.ts @@ -1,132 +1,132 @@ -/** - * AI Chat Service - * Handles communication with the AI chat API - */ - -export interface ChatMessage { - role: 'user' | 'assistant' | 'system' - content: string -} - -export interface ChatResponse { - success: boolean - message?: string - error?: string -} - -// Context about Dom that the AI should know. Keep this in sync with -// lib/content/projects.ts — the projects are the point of the site. -export const DOM_CONTEXT = `You are Dom Mangonon's AI assistant on his personal site. The site is a portfolio: it leads with the things Dom has built. Help visitors understand those projects first, and his career only if they ask. - -## About Dom -- Dominic "Dom" Mangonon, New York metropolitan area -- Seventeen years in financial services; currently SVP, Transformation at Citi, where he works on enterprise AI adoption -- Went all in on AI in 2025 and started shipping software nights and weekends, all built using coding agents — including this site - -## Projects (the main thing on the site) -- SousIQ — restaurant cost management. Parses vendor invoices into line items and matches them to inventory products using embeddings and fuzzy search. Postgres with pgvector, row-level security for multi-tenancy, Claude Haiku for parsing and Sonnet for harder passes. Live and field-tested in a working bakery. Source is private. -- Bracketeer — tournament bracket pool. Create a pool, invite friends, make picks, watch a live leaderboard. Knockout seeding implements FIFA Annex C. Next.js, Prisma, Auth.js, Railway. Live, and ran a real World Cup pool. Started as a pool for friends. -- Claude Code Placemat — a one-page Claude Code reference (shortcuts, slash commands, flags, hooks, MCP) that maintains itself: a scheduled agent re-reads each release and opens a PR when anything drifts. Static HTML on GitHub Pages, MIT licensed. -- modular-mind — builds a corpus of 3,500+ VCV Rack modular-synth patches and generates new ones from learned patterns. Decodes the binary patch format, profiles modules, validates signal flow. Python. -- PRIAL Pipeline — turns monthly SEC Form ADV bulk filings into a deduped registry of 23,000+ investment-adviser firms, with officers and firm sites. Python. Private. - -## Career, briefly -Operations at BNP Paribas through the 2008 crisis, MBA at CMU Tepper, consulting at PwC/Strategy& and Treliant, wealth-management strategy at Morgan Stanley, and Citi since 2021. The site has a short timeline; LinkedIn has the long version. - -## Writing -Dom writes at Context//Collapse on Substack (dommangonon.substack.com) — notes on building with AI. - -## Also -Has travelled to 52 countries across 5 continents; the site has an interactive map. - -## Response Guidelines -- Be friendly and direct. Keep it to 2-3 sentences for simple questions. -- The site is a single page — point people to a section (Projects, Writing, Career, Travel, Contact), never to a separate page or URL path. -- Never invent numbers, dates, or facts. If you don't know, say so and suggest they reach out via the Contact section. -- Don't oversell him. The projects speak for themselves; describe them plainly. -` - -/** - * Send a message to the AI chat API - */ -export async function sendChatMessage( - messages: ChatMessage[] -): Promise { - const apiUrl = process.env.NEXT_PUBLIC_CHAT_API_URL - - // If no API configured, return a helpful message - if (!apiUrl) { - return { - success: true, - message: getOfflineResponse(messages[messages.length - 1]?.content || '') - } - } - - try { - const response = await fetch(apiUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - messages: [ - { role: 'system', content: DOM_CONTEXT }, - ...messages - ] - }) - }) - - if (!response.ok) { - throw new Error(`API error: ${response.status}`) - } - - const data = await response.json() - return { - success: true, - message: data.message || data.content || data.choices?.[0]?.message?.content - } - } catch (error) { - console.error('Chat API error:', error) - return { - success: false, - error: 'Sorry, I encountered an error. Please try again.' - } - } -} - -/** - * Provide offline responses when API is not configured - */ -function getOfflineResponse(userMessage: string): string { - const lowerMessage = userMessage.toLowerCase() - - if (lowerMessage.includes('hello') || lowerMessage.includes('hi') || lowerMessage.includes('hey')) { - return "Hi there! I'm Dom's AI assistant. I can tell you about his career, skills, travel experiences, and this website. What would you like to know?" - } - - if (lowerMessage.includes('career') || lowerMessage.includes('work') || lowerMessage.includes('job')) { - return "Dom is a Senior Vice President at Citi, working at the intersection of finance and technology. He has experience in data analytics, process automation, and digital transformation. Check out the Career page for more details!" - } - - if (lowerMessage.includes('skill') || lowerMessage.includes('tech') || lowerMessage.includes('programming')) { - return "Dom works with TypeScript, React, Next.js, Python, and SQL among other technologies. He's experienced in both frontend and backend development. Visit the Skills page to see the full list!" - } - - if (lowerMessage.includes('travel') || lowerMessage.includes('countr') || lowerMessage.includes('visit')) { - return "Dom has traveled to 52 countries across 5 continents! He's explored extensively in Asia, Europe, and the Americas. Check out the Travel page to see the interactive map of his adventures." - } - - if (lowerMessage.includes('contact') || lowerMessage.includes('resume') || lowerMessage.includes('hire') || lowerMessage.includes('email')) { - return "You can request Dom's resume through the Contact page - just enter your name and email, and it'll be sent right to you. You can also reach out via the contact form there." - } - - if (lowerMessage.includes('website') || lowerMessage.includes('built') || lowerMessage.includes('stack')) { - return "This portfolio is built with Next.js 16, React 19, TypeScript, and Tailwind CSS. It's statically exported and deployed to GitHub Pages. Dom built it himself to showcase his work and travels." - } - - if (lowerMessage.includes('education') || lowerMessage.includes('degree') || lowerMessage.includes('school')) { - return "You can find details about Dom's educational background on the Education page. It covers his academic journey and certifications." - } - - return "I can help you learn about Dom's career, technical skills, travel adventures, or this website. Feel free to ask me anything, or explore the navigation above to dive into specific topics!" -} +/** + * AI Chat Service + * Handles communication with the AI chat API + */ + +export interface ChatMessage { + role: 'user' | 'assistant' | 'system' + content: string +} + +export interface ChatResponse { + success: boolean + message?: string + error?: string +} + +// Context about Dom that the AI should know. Keep this in sync with +// lib/content/projects.ts — the projects are the point of the site. +export const DOM_CONTEXT = `You are Dom Mangonon's AI assistant on his personal site. The site is a portfolio: it leads with the things Dom has built. Help visitors understand those projects first, and his career only if they ask. + +## About Dom +- Dominic "Dom" Mangonon, New York metropolitan area +- Seventeen years in financial services; currently SVP, Transformation at Citi, where he works on enterprise AI adoption +- Went all in on AI in 2025 and started shipping software nights and weekends, all built using coding agents — including this site + +## Projects (the main thing on the site) +- SousIQ — restaurant cost management. Parses vendor invoices into line items and matches them to inventory products using embeddings and fuzzy search. Postgres with pgvector, row-level security for multi-tenancy, Claude Haiku for parsing and Sonnet for harder passes. Live and field-tested in a working bakery. Source is private. +- Bracketeer — tournament bracket pool. Create a pool, invite friends, make picks, watch a live leaderboard. Knockout seeding implements FIFA Annex C. Next.js, Prisma, Auth.js, Railway. Live, and ran a real World Cup pool. Started as a pool for friends. +- Claude Code Placemat — a one-page Claude Code reference (shortcuts, slash commands, flags, hooks, MCP) that maintains itself: a scheduled agent re-reads each release and opens a PR when anything drifts. Static HTML on GitHub Pages, MIT licensed. +- modular-mind — builds a corpus of 3,500+ VCV Rack modular-synth patches and generates new ones from learned patterns. Decodes the binary patch format, profiles modules, validates signal flow. Python. +- PRIAL Pipeline — turns monthly SEC Form ADV bulk filings into a deduped registry of 23,000+ investment-adviser firms, with officers and firm sites. Python. Private. + +## Career, briefly +Operations at BNP Paribas through the 2008 crisis, MBA at CMU Tepper, consulting at PwC/Strategy& and Treliant, wealth-management strategy at Morgan Stanley, and Citi since 2021. The site has a short timeline; LinkedIn has the long version. + +## Writing +Dom writes at Context//Collapse on Substack (dommangonon.substack.com) — notes on building with AI. + +## Also +Has travelled to 52 countries across 5 continents; the site has an interactive map. + +## Response Guidelines +- Be friendly and direct. Keep it to 2-3 sentences for simple questions. +- The site is a single page — point people to a section (Projects, Writing, Career, Travel, Contact), never to a separate page or URL path. +- Never invent numbers, dates, or facts. If you don't know, say so and suggest they reach out via the Contact section. +- Don't oversell him. The projects speak for themselves; describe them plainly. +` + +/** + * Send a message to the AI chat API + */ +export async function sendChatMessage( + messages: ChatMessage[] +): Promise { + const apiUrl = process.env.NEXT_PUBLIC_CHAT_API_URL + + // If no API configured, return a helpful message + if (!apiUrl) { + return { + success: true, + message: getOfflineResponse(messages[messages.length - 1]?.content || '') + } + } + + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + messages: [ + { role: 'system', content: DOM_CONTEXT }, + ...messages + ] + }) + }) + + if (!response.ok) { + throw new Error(`API error: ${response.status}`) + } + + const data = await response.json() + return { + success: true, + message: data.message || data.content || data.choices?.[0]?.message?.content + } + } catch (error) { + console.error('Chat API error:', error) + return { + success: false, + error: 'Sorry, I encountered an error. Please try again.' + } + } +} + +/** + * Provide offline responses when API is not configured + */ +function getOfflineResponse(userMessage: string): string { + const lowerMessage = userMessage.toLowerCase() + + if (lowerMessage.includes('hello') || lowerMessage.includes('hi') || lowerMessage.includes('hey')) { + return "Hi there! I'm Dom's AI assistant. I can tell you about his career, skills, travel experiences, and this website. What would you like to know?" + } + + if (lowerMessage.includes('career') || lowerMessage.includes('work') || lowerMessage.includes('job')) { + return "Dom is a Senior Vice President at Citi, working at the intersection of finance and technology. He has experience in data analytics, process automation, and digital transformation. Check out the Career page for more details!" + } + + if (lowerMessage.includes('skill') || lowerMessage.includes('tech') || lowerMessage.includes('programming')) { + return "Dom works with TypeScript, React, Next.js, Python, and SQL among other technologies. He's experienced in both frontend and backend development. Visit the Skills page to see the full list!" + } + + if (lowerMessage.includes('travel') || lowerMessage.includes('countr') || lowerMessage.includes('visit')) { + return "Dom has traveled to 52 countries across 5 continents! He's explored extensively in Asia, Europe, and the Americas. Check out the Travel page to see the interactive map of his adventures." + } + + if (lowerMessage.includes('contact') || lowerMessage.includes('resume') || lowerMessage.includes('hire') || lowerMessage.includes('email')) { + return "You can request Dom's resume through the Contact page - just enter your name and email, and it'll be sent right to you. You can also reach out via the contact form there." + } + + if (lowerMessage.includes('website') || lowerMessage.includes('built') || lowerMessage.includes('stack')) { + return "This portfolio is built with Next.js 16, React 19, TypeScript, and Tailwind CSS. It's statically exported and deployed to GitHub Pages. Dom built it himself to showcase his work and travels." + } + + if (lowerMessage.includes('education') || lowerMessage.includes('degree') || lowerMessage.includes('school')) { + return "You can find details about Dom's educational background on the Education page. It covers his academic journey and certifications." + } + + return "I can help you learn about Dom's career, technical skills, travel adventures, or this website. Feel free to ask me anything, or explore the navigation above to dive into specific topics!" +}