diff --git a/index.html b/index.html index 526507d7..fbcb8a4c 100644 --- a/index.html +++ b/index.html @@ -75,7 +75,12 @@ "logo": "https://oullin.io/brand/logo-touch.png", "image": "https://oullin.io/brand/logo-touch.png", "description": "Oullin is a boutique software engineering and architecture consultancy for startups and scale-ups navigating the AI era. AI architecture, modernisation, and resilient systems for regulated and high-trust environments.", - "sameAs": ["https://github.com/oullin"] + "sameAs": ["https://github.com/oullin"], + "founder": { + "@type": "Person", + "name": "Gustavo Ocanto", + "url": "https://gocanto.sh" + } } ] diff --git a/src/pages/AboutPage.vue b/src/pages/AboutPage.vue index 3b7ed20f..cda5a660 100644 --- a/src/pages/AboutPage.vue +++ b/src/pages/AboutPage.vue @@ -16,7 +16,7 @@
{{ sidebar.founder.name }}

{{ sidebar.founder.copy }}

- {{ sidebar.founder.linkLabel }} + {{ sidebar.founder.linkLabel }}
diff --git a/src/pages/WorkWithUsPage.vue b/src/pages/WorkWithUsPage.vue index 93e24364..fedd6a53 100644 --- a/src/pages/WorkWithUsPage.vue +++ b/src/pages/WorkWithUsPage.vue @@ -51,8 +51,9 @@ {{ item }}

You leave with: {{ engagement.outcome }}

-

{{ engagement.price }}

- {{ engagement.cta.label }} + {{ + engagement.cta.label + }}
diff --git a/src/support/content-types.ts b/src/support/content-types.ts index e8095757..8ce93a62 100644 --- a/src/support/content-types.ts +++ b/src/support/content-types.ts @@ -30,6 +30,10 @@ export interface SiteContent { organization: { description: string; sameAs: string[]; + founder: { + name: string; + url: string; + }; }; nav: { links: RouteLink[]; @@ -204,7 +208,6 @@ export interface WorkWithUsPageContent { includesLabel: string; includes: string[]; outcome: string; - price: string; cta: RouteLink; }>; faq: { diff --git a/src/support/seo.ts b/src/support/seo.ts index b421664d..d3cd097e 100644 --- a/src/support/seo.ts +++ b/src/support/seo.ts @@ -26,6 +26,11 @@ export const ORGANIZATION_JSON_LD = { image: siteUrlFor(SITE_LOGO), description: siteContent.organization.description, sameAs: siteContent.organization.sameAs, + founder: { + '@type': 'Person', + name: siteContent.organization.founder.name, + url: siteContent.organization.founder.url, + }, }; type TwitterCard = 'summary' | 'summary_large_image' | 'app' | 'player'; diff --git a/storage/fixtures/about-page.json b/storage/fixtures/about-page.json index dcf32b80..f64aa203 100644 --- a/storage/fixtures/about-page.json +++ b/storage/fixtures/about-page.json @@ -12,8 +12,8 @@ "label": "// founder & lead architect", "name": "Gustavo Ocanto", "copy": "Engineering leader with 20+ years across software engineering, consulting, architecture, AI-first products and companies, and technical management. 10+ years in banking and fintech.", - "linkLabel": "LinkedIn Profile →", - "linkUrl": "https://www.linkedin.com/in/gocanto/" + "linkLabel": "Founder bio →", + "linkUrl": "https://gocanto.sh" }, "proof": { "label": "// proof", diff --git a/storage/fixtures/site.json b/storage/fixtures/site.json index a15012a5..a750ff38 100644 --- a/storage/fixtures/site.json +++ b/storage/fixtures/site.json @@ -23,7 +23,11 @@ }, "organization": { "description": "Oullin is a boutique software engineering and architecture consultancy for startups and scale-ups navigating the AI era. AI architecture, modernisation, and resilient systems for regulated and high-trust environments.", - "sameAs": ["https://github.com/oullin"] + "sameAs": ["https://github.com/oullin"], + "founder": { + "name": "Gustavo Ocanto", + "url": "https://gocanto.sh" + } }, "nav": { "links": [ diff --git a/storage/fixtures/work-with-us-page.json b/storage/fixtures/work-with-us-page.json index 6b3d785a..c7039d29 100644 --- a/storage/fixtures/work-with-us-page.json +++ b/storage/fixtures/work-with-us-page.json @@ -39,8 +39,7 @@ "— Two 90-minute working sessions with the founding/engineering team" ], "outcome": "A clear, honest answer to where AI fits in your stack — and an architectural plan that survives contact with production.", - "price": "From $8,000 · Fixed fee", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } }, { "label": "[ 02 ] Fractional AI Architect", @@ -59,8 +58,7 @@ "— Quarterly roadmap refresh aligned to business goals" ], "outcome": "Senior architectural oversight without a full-time hire. A team that gets better at building as the engagement runs.", - "price": "From $8,000/mo · 3-month minimum", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } }, { "label": "[ 03 ] Production Hardening", @@ -79,8 +77,7 @@ "— 2-week async support window post-engagement" ], "outcome": "AI features that hold under real conditions. Documentation your team owns. No dependency on us to keep it running.", - "price": "From $15,000 · Scoped after a paid discovery session ($2,500)", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } }, { "label": "[ 04 ] AI-Era Team Enablement", @@ -97,8 +94,7 @@ "— A written playbook the team owns after we leave" ], "outcome": "Engineers who can build with AI and review what it produces with real judgment. The capability stays in your team.", - "price": "From $6,000 · 4-week programme", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } } ], "faq": { diff --git a/tests/pages/AboutPage.test.ts b/tests/pages/AboutPage.test.ts index 380bc114..4fffe242 100644 --- a/tests/pages/AboutPage.test.ts +++ b/tests/pages/AboutPage.test.ts @@ -75,7 +75,9 @@ describe('AboutPage', () => { expect(wrapper.text()).toContain(aboutPageContent.sidebar.founder.copy); expect(wrapper.text()).toContain(aboutPageContent.sidebar.proof.items[1]); expect(wrapper.find('[data-testid="recommendation-partial"]').text()).toContain('Recommendations partial'); - expect(wrapper.html()).toContain(aboutPageContent.sidebar.founder.linkUrl); + const founderLink = wrapper.find(`a[href="${aboutPageContent.sidebar.founder.linkUrl}"]`); + expect(founderLink.text()).toBe('Founder bio →'); + expect(founderLink.attributes('rel')).toContain('me'); }); it('renders the about footer without the skills marquee band', async () => { diff --git a/tests/pages/WorkWithUsPage.test.ts b/tests/pages/WorkWithUsPage.test.ts index f8be2c3f..280e3e3e 100644 --- a/tests/pages/WorkWithUsPage.test.ts +++ b/tests/pages/WorkWithUsPage.test.ts @@ -22,4 +22,16 @@ describe('WorkWithUsPage', () => { expect(wrapper.text()).toContain(workWithUsPageContent.faq.items[0].question); expect(wrapper.text()).toContain(workWithUsPageContent.cta.button.label); }); + + it('replaces engagement prices with contact links', () => { + const wrapper = mount(WorkWithUsPage, { global }); + const moreInfoLinks = wrapper.findAll('a').filter((link) => link.text() === 'More info'); + + expect(moreInfoLinks).toHaveLength(workWithUsPageContent.engagements.length); + moreInfoLinks.forEach((link, index) => { + expect(link.attributes('to')).toBe('/contact'); + expect(link.attributes('aria-label')).toBe(`${workWithUsPageContent.engagements[index]?.cta.label} about ${workWithUsPageContent.engagements[index]?.label}`); + }); + expect(wrapper.text()).not.toMatch(/(?:[$€£]\s*\d[\d,.]*(?:[km])?|\b\d[\d,.]*(?:[km])?\s*(?:USD|EUR|GBP)\b)/i); + }); }); diff --git a/tests/support/seo-content.test.ts b/tests/support/seo-content.test.ts index 3903e621..5fd9532f 100644 --- a/tests/support/seo-content.test.ts +++ b/tests/support/seo-content.test.ts @@ -9,6 +9,7 @@ import { siteContent } from '@/support/content.ts'; import { termsAndPoliciesPageContent } from '@/support/content/terms-and-policies-page.ts'; import { workWithUsPageContent } from '@/support/content/work-with-us-page.ts'; import { writingPageContent } from '@/support/content/writing-page.ts'; +import { ORGANIZATION_JSON_LD } from '@/support/seo.ts'; const indexHtml = readFileSync(resolve(process.cwd(), 'index.html'), 'utf8'); const staleGustavoTitle = 'Gustavo Ocanto - Engineering, Leadership, Fintech & eCommerce | Software Engineer, Architect, AI , AI Architect & Manager'; @@ -46,6 +47,21 @@ describe('SEO content fixtures', () => { expect(writingPageContent.seo.description).toContain('engineering judgment'); expect(termsAndPoliciesPageContent.seo.description).toContain("Oullin's terms and policies"); }); + + it('connects the organization founder metadata to the canonical bio site', () => { + expect(ORGANIZATION_JSON_LD.founder).toEqual({ + '@type': 'Person', + name: 'Gustavo Ocanto', + url: 'https://gocanto.sh', + }); + + const jsonLdMatch = indexHtml.match(/]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/); + expect(jsonLdMatch?.[1]).toBeDefined(); + + const indexJsonLd = JSON.parse(jsonLdMatch?.[1] ?? '[]') as Array>; + const indexOrganization = indexJsonLd.find((entry) => entry['@type'] === 'Organization'); + expect(indexOrganization?.founder).toEqual(ORGANIZATION_JSON_LD.founder); + }); }); describe('index.html seo defaults', () => {