Buffering...
Narrative Interface System is the evolution of my personal landing page codebase into a reusable, section-driven foundation. It keeps the proven landing experience intact today while treating every section (hero, timeline, skills, projects, learning, contact) as a composable building block.
This repo is where I harden the data contracts, layout shell, and Contentful mapping patterns that will power future narrative surfaces. The current deployment still serves the personal site, but each change aims to make the same system ready for additional pages without rewriting the core.
Narrative Interface System is a high-signal personal landing page that treats SEO, narrative, and content modeling as one system.
Built UI-first, CMS-second, with Contentful + Agent-assisted reviews to keep the landing page sharp.
Structure version: v0.1.0
See VERSION and CHANGELOG.md for history.
- Project Goals
- Status & Metrics
- Tech Stack
- Architecture & Folder Structure
- Data & Agent Flow
- Getting Started
- Development Scripts
- Phases (0–9)
- Next Steps – Landing Page To-Dos
- AgentOps Workflow
- Deployment (Netlify)
Core focus: a strong landing page
- Build a single-page personal landing that:
- Reads clearly for recruiters, hiring managers, collaborators.
- Shows SEO sense, content thinking, and frontend craft.
- Treat the page as an Interface & Narrative Toolkit:
- Clear positioning, key projects, and “Now / Learning” sections that tell a coherent story.
SEO & Content
- Go beyond “SEO is configured” into SEO that actually sells you:
- Good headings, clear copy, structured data, and compelling social previews.
- Use Contentful to keep copy and sections flexible:
- One bucket (Bucket 1 – Personal Landing) for this repo.
- Other buckets (Blog/Notes, Projects index, etc.) live in other repos/docs.
Agents
- Use Agent Checkpoints to:
- Review structure, copy, SEO, and UX.
- Capture decisions in
agent/CHECKPOINT_LOG.mdfor traceability.
- Structure version:
v0.1.0 - Changelog:
CHANGELOG.md
Latest Lighthouse (Netlify plugin):
- Performance: 99
- Accessibility: 96
- Best Practices: 100
- SEO: 100
- PWA: 30 (PWA is explicitly not a goal for this project)
Phase status (Bucket 1 – Personal Landing):
- Phases 0–9: ✅ complete
This repo is now “Phase 9 complete” – a solid, tested, deployed landing page.
Everything under “Next Steps” is optional polish to keep sharpening the same page.
- Vite + React (SPA)
- TypeScript
- CSS with design tokens
tokens.css– colors, typography, spacing, radii.layout.css– layout, sections, header, responsive behavior.
- Contentful (e.g.
masterenvironment)- Content model for Bucket 1 lives in sibling repo:
gharo-content-models. - This app reads a single
pagePersonalLandingentry with:slug: "/",- ordered
sections[]referencing:sectionHero,sectionTimeline,sectionSkills,sectionProjects,sectionLearning,sectionContact.
- Content model for Bucket 1 lives in sibling repo:
- Static TS fallback
personalLandingPageobject matches the Contentful-backed TS types.- Used as a typed fallback when CMS fetch fails or for local development.
Seo.tsx– manages<title>+<meta name="description">for SPA.PersonSchema.tsx– JSON-LD Person schema for personal-brand queries.
- AgentOps loop
- Checkpoint prompts + payloads in
agent/checkpoints/. - Helper:
agent/scripts/buildCheckpointPayload.mjs.
- Checkpoint prompts + payloads in
- Used to review:
- Data modeling, mapping, SEO choices, UX, and layout.
- Netlify
- Build:
npm run build - Publish:
dist
- Build:
- Env vars
VITE_CONTENTFUL_SPACE_IDVITE_CONTENTFUL_ENVIRONMENTVITE_CONTENTFUL_DELIVERY_TOKENSECRETS_SCAN_OMIT_KEYS
- Lighthouse CI
@netlify/plugin-lighthousevianetlify.toml.
narrative-interface-system/
├── VERSION
├── README.md
├── CHANGELOG.md
├── package.json
├── tsconfig.json
├── .eslintrc.cjs
├── .prettierrc
├── .gitignore
├── .env.example # template for Contentful env vars (no secrets)
├── .nvmrc # Node version hint for CI/Netlify
├── netlify.toml # Netlify build + Lighthouse plugin config
├── agent/
│ ├── CHECKPOINT_LOG.md # log of agent checkpoints + decisions
│ ├── checkpoints/ # markdown payloads/prompts per phase
│ └── scripts/
│ └── buildCheckpointPayload.mjs
├── src/
│ ├── main.tsx # Vite entrypoint, mounts <App />
│ ├── App.tsx # wraps the Page component
│ ├── data/
│ │ └── page-personal-landing.ts
│ ├── services/
│ │ ├── contentfulClient.ts
│ │ └── fetchPersonalLandingPage.ts
│ ├── components/
│ │ ├── layout/
│ │ │ └── Page.tsx
│ │ ├── sections/
│ │ │ ├── SectionRenderer.tsx
│ │ │ ├── HeroSection.tsx
│ │ │ ├── TimelineSection.tsx
│ │ │ ├── SkillsSection.tsx
│ │ │ ├── ProjectsSection.tsx
│ │ │ ├── LearningSection.tsx
│ │ │ └── ContactSection.tsx
│ │ ├── Seo.tsx
│ │ └── PersonSchema.tsx
│ ├── styles/
│ │ ├── tokens.css
│ │ └── layout.css
│ └── __tests__/ # or co-located *.test.ts files
└── public/
# static assets (favicons, etc.) – optional
-
contentfulClient.ts:- Uses
VITE_CONTENTFUL_*env vars to create a CDA client.
- Uses
-
fetchPersonalLandingPage.ts:-
Fetches
pagePersonalLandingwithslug: "/". -
Uses
includedepth to pull in all linked sections/items. -
Maps into:
PersonalLandingPage- Section types (
HeroSection,TimelineSection,SkillsSection,ProjectsSection,LearningSection,ContactSection).
-
Uses:
- Section mapper registry (
contentTypeId→ mapper). - Safe array helpers and defensive defaults.
- Unknown section types are logged and skipped instead of crashing.
- Section mapper registry (
-
-
Page.tsx:-
Loads CMS data on mount; falls back to
personalLandingPageon error. -
Renders:
- Skip link, sticky header, nav, and all sections via
SectionRenderer. <Seo />+<PersonSchema />for SEO and structured data.
- Skip link, sticky header, nav, and all sections via
-
-
SectionRenderer.tsx:- Switches on
section.sectionTypeand renders the appropriate section component.
- Switches on
-
Use
agent:buildor checkpoint.mdfiles to:- Bundle code + context for review.
- Ask about SEO, layout, types, mapping, or narrative.
-
Apply suggestions and log them in
agent/CHECKPOINT_LOG.md.
- Node 18+ or 20+
- npm
npm installcp .env.example .env.localThen fill:
VITE_CONTENTFUL_SPACE_ID=your_space_id
VITE_CONTENTFUL_ENVIRONMENT=master
VITE_CONTENTFUL_DELIVERY_TOKEN=your_delivery_tokennpm run devOpen http://localhost:5173.
npm run build
npm run previewOpen http://localhost:4173.
npm run dev– dev server.npm run build– production build.npm run preview– preview built site.npm run lint– ESLint.npm run format– Prettier.npm run test/test:watch– tests (mappers, components).npm run agent:build– build an Agent checkpoint payload.
All complete for Bucket 1 – Personal Landing:
- Phase 0 – Setup – scaffold, tooling, Agent skeleton.
- Phase 1 – Static Data Shape – TS types + static
personalLandingPage. - Phase 2 – Layout –
Page.tsx,SectionRenderer, sections. - Phase 3 – Visual Design & Tokens – tokens + layout CSS.
- Phase 4 – UX & Accessibility – skip link, focus, headings, sticky nav.
- Phase 5 – Contentful Model – Bucket 1 model in
gharo-content-models. - Phase 6 – Seed Content – real data in Contentful.
- Phase 7 – Integration & Mapping – CMS → TS mapping, safe patterns.
- Phase 8 – Go Live + SEO / Lighthouse – Netlify deploy, plugin, SPA SEO basics.
- Phase 9 – Hardening & Beyond – JSON-LD Person, Vitest, CI workflow (lint + test + build).
From here on out, changes are incremental polish on this one landing page.
Phase 9 – Hardening & Beyond is complete for this repo:
the landing page is live, tested, mapped to Contentful, and has solid SEO basics.
What follows are focused, incremental improvements for this landing page only.
Deeper modeling work now lives in landing-page-content-models v2.
- Finalize structured data
- Fill real
sameAslinks insrc/components/PersonSchema.tsx:- LinkedIn, GitHub, portfolio domain.
- Confirm
urlpoints at the production domain (e.g.https://gilbertoharo.com).
- Fill real
- Lock in title & meta description pattern
- In
Seo.tsx, codify a final title format, for example:Gilberto Haro – Web Engineer & Content Systems
- Ensure the meta description:
- Mentions web engineering, content platforms, and UX in a natural, scannable sentence.
- In
- Social preview card
- Add an OG image asset, e.g.
public/og/landing-default.pngwith:- Name, role, and a clean gradient or subtle graphic.
- Add OG tags (either in
index.htmlor viaSeo.tsx):og:title,og:description,og:url,og:type=website,og:image.
- Add an OG image asset, e.g.
- Hero presentation (configurable)
- Hero now supports typographic, avatar, and image modes via:
heroStyle: "typographic" | "avatar" | "image"- Optional
avatarUrl/heroImageUrlmapped from Contentful assets.
HeroSection.tsxrenders the three variants with safe fallbacks:- Defaults to
typographicifheroStyleis missing or assets are not set. - Uses proper
alttext for avatar portraits; hero image uses decorativealt=""unless needed.
- Defaults to
layout.cssadds responsive layout styles:- Split layout on desktop for avatar/image modes.
- Stacked layout on mobile for all modes.
- Tests:
fetchPersonalLandingPage.test.tscovers heroStyle defaults, avatar/image asset handling, and fallback behavior.
- Hero now supports typographic, avatar, and image modes via:
-
Global spacing & layout rhythm
- Define a simple vertical spacing scale in
tokens.css(e.g.--space-4,--space-6,--space-8,--space-12) and use it consistently for:- Section top/bottom padding.
- Gaps between headings and body text.
- Gaps between cards (projects, timeline items).
- In
layout.css:- Aim for ~3–4rem vertical space between major sections on desktop.
- Ensure there is clear separation between hero, experience, skills, projects, learning, and contact.
- Define a simple vertical spacing scale in
-
Typography scale & hierarchy
- In
tokens.css, define an explicit type scale and stick to it:--font-size-h1(hero title),--font-size-h2(section titles),--font-size-h3(card/section sub-headings).
- Update headings to use this scale:
h1→ hero name.h2→ section headings (Experience,Skills,Projects,Learning,Contact).h3→ project titles, timeline items, skill group titles (if needed).
- Check mobile:
- Slightly reduce heading sizes where necessary so text doesn’t wrap awkwardly on small screens.
- Ensure line-height is comfortable for reading (especially in hero intro and section intros).
- In
-
Color & contrast polish
- In
tokens.css:- Verify
--color-mutedremains readable against the background (meets contrast guidelines). - Add/verify a
--color-focus-ringtoken and use it for:focus-visibleoutlines.
- Verify
- In
layout.css:- Make sure focus outlines and hover states are clearly visible on links and buttons (especially in the header and hero CTAs).
- Ensure any borders (project cards, timeline items) use
--color-borderor a subtle variant for consistency.
- In
- Hero mode verification in Contentful
- Flip
heroStyle+ asset combinations in Contentful to visually verify all three modes:heroStyle = typographicwith no images.heroStyle = avatarwithavatarImageset.heroStyle = imagewithheroImageset.
- Check each mode on:
- Desktop (spacing, alignment, avatar/image size).
- Mobile (stacking, readability, tap target spacing).
- Once visually confirmed, run the
agent/checkpoints/cp-hero-visuals.mdcheckpoint (with screenshots) for an Agent review of the visual/UX choices.
- Flip
-
Project visuals (lightweight)
- Decide how to present 1–2 flagship projects:
- Text-only cards, or
- Small thumbnail/icon + text.
- If using thumbnails:
- Add an optional
thumbnailfield to theprojectcontent type inlanding-page-content-modelsv2. - Map it into the UI and render with meaningful
alttext (e.g. “Screenshot of project dashboard for …”). - Keep layout simple and responsive: image + text on desktop, stacked on mobile.
- Add an optional
- Decide how to present 1–2 flagship projects:
-
Tokens & readability check
- In
tokens.css:- Reconfirm
--color-text,--color-muted, and body font-size are comfortable for sustained reading.
- Reconfirm
- In
layout.css:- Slightly increase line-height on mobile paragraphs (e.g.
1.6–1.8) for better readability. - Confirm that long sections (Experience, Projects, Learning) don’t feel cramped or overly dense.
- Slightly increase line-height on mobile paragraphs (e.g.
- In
- Sharpen hero copy
- One sentence that clearly says:
- Who you are, what you do, and the kind of work you want.
- One sentence that clearly says:
- Section intros
- For each section (Experience, Skills, Projects, Learning, Contact), add or refine a single intro line that:
- Explains what the section is for in recruiter-friendly language.
- For each section (Experience, Skills, Projects, Learning, Contact), add or refine a single intro line that:
- Contact CTA
- In Contact section:
- Make the main CTA explicit (“Email me about X / Y / Z types of roles or collaborations”).
- Ensure email + social links are easily tappable on mobile.
- In Contact section:
The landing-page code is stable; modeling evolution now lives in the dedicated repo:
- Document Bucket 1 v2
- In
landing-page-content-modelsv2:- Update IA/decisions docs to reflect any new fields (hero avatar, project thumbnails, etc.).
- In
- Link map for the landing page
- Maintain a simple diagram or doc describing:
pagePersonalLanding→ section entries → nested items (timeline items, skills, projects).
- Keep it aligned with what the landing-page UI actually uses.
- Maintain a simple diagram or doc describing:
After SEO polish, the next big win is making your AgentOps workflow smoother and more automated.
- Checkpoint metadata & structure
- Standardize checkpoint files under
agent/checkpoints/with:- A short header (Phase, focus, date).
- Explicit
Artifactslist (files + paths). - Explicit
Questionssection.
- Consider using a mini front-matter block (YAML or JSON) at the top of each checkpoint for easier parsing.
- Standardize checkpoint files under
- Automatic context snapshots
- Add a small script (e.g.
agent/scripts/snapshotStatus.ts) that:- Reads Lighthouse scores (from the latest report or CI logs).
- Reads
post-launch-status.md(if present). - Outputs a short status summary you can paste into checkpoints.
- Add a small script (e.g.
- AgentOps logging discipline
- Any time you:
- Change content meaningfully in Contentful, or
- Modify a section’s structure or copy in code,
- Add a 1–2 bullet summary in
agent/CHECKPOINT_LOG.md:- “What changed?”
- “Why (SEO/UX/system reason)?”
- Any time you:
These make it much easier for future agents (and future you) to reconstruct context quickly.
A few high-impact ways to use your ChatGPT Pro account just for this landing page:
- Dedicated Project for this repo
- Create a ChatGPT Project named
narrative-interface-system. - Upload:
README.md- Latest
lighthouse-report.html(from Netlify or local run) - Key modeling doc excerpts from
landing-page-content-modelsv2 (Bucket 1 only).
- Use this project as the default context for AgentOps checkpoints related to the landing page.
- Create a ChatGPT Project named
- Checkpoint templates as reusable prompts
- Save 2–3 prompt templates in your workspace:
- “SEO & Copy Review” (Hero + sections).
- “UI/UX Review” (layout + tokens).
- “Mapping/Modeling Review” (for when you touch mapping or fields).
- This keeps the questions consistent and reduces friction each time you run a checkpoint.
- Save 2–3 prompt templates in your workspace:
- Attach artifacts directly in sessions
- When doing a checkpoint:
- Attach the exact files you’re working with (e.g.
HeroSection.tsx,Seo.tsx,PersonSchema.tsx, relevant CSS). - Include any recent Lighthouse or content snapshots.
- Attach the exact files you’re working with (e.g.
- This lets the agent give more precise, code-aware feedback without you copy/pasting huge chunks every time.
- When doing a checkpoint:
These aren’t required for the landing page to work—but they make your review loop faster, more consistent, and more clearly demonstrable as an engineering practice.
For future tweaks:
-
Pick one focus: SEO, Hero UI, or copy.
-
Use
agent:buildto package:- Relevant components (Hero, Seo, PersonSchema, etc.).
- Any updated copy or Lighthouse notes.
-
Ask your agent:
- “How can I tighten this for hiring managers?”
- “What small SEO adjustments would help without changing architecture?”
-
Apply changes and update:
agent/CHECKPOINT_LOG.mdwith what changed and why.
netlify login
netlify link # choose your Netlify site (e.g. narrative-interface-system)netlify env:set VITE_CONTENTFUL_SPACE_ID your_space_id
netlify env:set VITE_CONTENTFUL_ENVIRONMENT master
netlify env:set VITE_CONTENTFUL_DELIVERY_TOKEN your_delivery_token
# Ignore non-sensitive space ID in secrets scan
netlify env:set SECRETS_SCAN_OMIT_KEYS VITE_CONTENTFUL_SPACE_IDnpm run build
netlify deploy --prod
# Build command: npm run build
# Publish directory: dist