{"id":34950286,"url":"https://github.com/cbmongithub/cbm-portfolio","last_synced_at":"2026-04-29T05:37:37.093Z","repository":{"id":327739604,"uuid":"1110040059","full_name":"cbmongithub/cbm-portfolio","owner":"cbmongithub","description":"My developer portfolio","archived":false,"fork":false,"pushed_at":"2026-01-19T19:58:29.000Z","size":2401,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-29T05:37:14.693Z","etag":null,"topics":["animated-portfolio","developer-portfolio","framer-motion","framer-website","front-end-development","fullstack-developer-portfolio","fullstack-development","motion","next-js-portfolio","nextjs16-portfolio","portfolio","portfolio-website","react-portfolio","tailwindcss-portfolio","typescript"],"latest_commit_sha":null,"homepage":"https://christianbmartinez.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cbmongithub.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-04T16:20:46.000Z","updated_at":"2026-01-19T19:58:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cbmongithub/cbm-portfolio","commit_stats":null,"previous_names":["cbmongithub/cbm-portfolio"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cbmongithub/cbm-portfolio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbmongithub%2Fcbm-portfolio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbmongithub%2Fcbm-portfolio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbmongithub%2Fcbm-portfolio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbmongithub%2Fcbm-portfolio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cbmongithub","download_url":"https://codeload.github.com/cbmongithub/cbm-portfolio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbmongithub%2Fcbm-portfolio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32412890,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T05:20:56.964Z","status":"ssl_error","status_checked_at":"2026-04-29T05:19:54.749Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animated-portfolio","developer-portfolio","framer-motion","framer-website","front-end-development","fullstack-developer-portfolio","fullstack-development","motion","next-js-portfolio","nextjs16-portfolio","portfolio","portfolio-website","react-portfolio","tailwindcss-portfolio","typescript"],"created_at":"2025-12-26T21:24:23.131Z","updated_at":"2026-04-29T05:37:37.088Z","avatar_url":"https://github.com/cbmongithub.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv style=\"display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;\"\u003e\n  \u003cdiv\u003e\n    \u003ch1 style=\"margin:0;\"\u003ecbm-portfolio\u003c/h1\u003e\n  \u003c/div\u003e\n  \u003cdiv\u003e\n    \u003ca href=\"https://github.com/cbmongithub/cbm-portfolio/actions/workflows/ci.yml\"\u003e\n      \u003cimg src=\"https://github.com/cbmongithub/cbm-portfolio/actions/workflows/ci.yml/badge.svg\" alt=\"Build \u0026 Verify\" /\u003e\n    \u003c/a\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\n[![Github Repo Image](public/repo-image.png)](public/repo-image.png)\n\n## Stack\n\n- Next.js 16 (App Router), React 19\n- Tailwind CSS v4\n- TypeScript\n- Vercel Analytics + Speed Insights\n- Bun\n\n## Tooling\n\n- Formatting: Prettier + `prettier-plugin-tailwindcss` (class sorting), runs in CI with `--write`.\n- Linting: ESLint flat config (Next core-web-vitals + TS); `eslint-plugin-simple-import-sort` groups imports; `eslint-plugin-tailwindcss` enforces class order/syntax (v4 aware).\n- Imports: Ordered react/next/external → hooks → components → lib → types → alias → relative via simple-import-sort.\n- Styles: Color tokens live in `styles/globals.css` (light/dark via CSS vars); no prose plugin.\n- Content: TSX in `src/content/blog`; metadata exported per file.\n\n## Custom Blog Engine (“Hand-Rolled MDX”)\n\nInstead of pulling in MDX, frontmatter parsers, or `@tailwindcss/typography`, the blog is authored as plain `.tsx` modules placed in `src/content/blog`. Each post exports two things:\n\n```tsx\nimport { formatIsoDate, formatModifiedDate, type PostMetadata } from \"@/lib/posts\";\nimport { generateOgImageUrl } from \"@/lib/config/metadata\";\n\nexport const metadata: PostMetadata = {\n  slug: \"an-example-slug\",\n  title: \"Example Blog Title\",\n  description: \"An example description\",\n  tags: [\"nextjs\", \"react\"],\n  authors: \"Christian B. Martinez\",\n  get publishedTime() {\n    return formatIsoDate(\"2025-12-14\");\n  },\n  get modifiedTime() {\n    return formatModifiedDate(this.publishedTime, \"2025-12-16\");\n  },\n  get ogImageData() {\n    return {\n      title: this.title,\n      description: this.description,\n      route: `/blog/${this.slug}`,\n    };\n  },\n  get image() {\n    return generateOgImageUrl(this.ogImageData);\n  },\n};\n\nexport default function Article() {\n  return (\n    \u003c\u003e\n      \u003cFigure\n        title=\"This is an example title\"\n        imageSrc=\"https://example.com\"\n        caption=\"Photo by Bob\"\n        priority\n      /\u003e\n      \u003cHeading level={2}\u003e...\u003c/Heading\u003e\n      \u003cText\u003e...\u003c/Text\u003e\n      \u003cCodeBlock language=\"ts\" code={`const nextConfig = { cacheComponents: true };`} /\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n### Why roll my own?\n\n- **Zero MDX pipeline** – No remark/rehype transforms, no shared runtime between client and server. A post is just a React Server Component, so it’s compiled once and streamed as plain HTML.\n- **Self-contained metadata** – The `metadata` object behaves like frontmatter but stays type-safe and can expose computed getters (`ogImageData`, `image`). Post pages import it and Next’s metadata helpers consume it directly.\n- **Custom typography primitives** – Components such as `Heading`, `Text`, `Lead`, `Quote`, `List`, `Table`, `Surface`, `Figure`, and `CodeBlock` live under `src/components/ui/typography`. They encapsulate spacing, anchors, color tokens, and responsive behavior. Switching design systems is a matter of editing those components, not prose CSS overrides.\n- **Tiny HTML footprint** – Because everything is server-rendered TSX, there’s no hydration cost unless a post deliberately imports a client component. Most posts deliver pure static markup.\n- **Blueprint for portfolios** – The goal is to show engineering choices, not just content. Keeping metadata, layout, and rendering logic in one module highlights the architecture in a way frontmatter/MDX often hides.\n\n### Date helpers \u0026 OG metadata\n\n- `formatIsoDate(value?)` accepts `\"YYYY-MM-DD\"`, `Date.now()`, or a `Date` instance and always returns a canonical ISO timestamp without forcing every post to remember the trailing `\"T00:00:00.000Z\"`.\n- `formatModifiedDate(published, updated?)` reuses the published timestamp when no updated date is provided so crawlers see accurate `lastModified` data.\n- `ogImageData` is a getter as well, ensuring OG routes, titles, and descriptions stay in sync with whatever the post metadata currently returns.\n\n### Comparing to Tailwind Prose + MDX\n\n| Concern       | Hand-rolled TSX                                              | Tailwind Prose / MDX                                                          |\n| ------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------- |\n| Dependencies  | React + Next only                                            | `@mdx-js/loader`, remark, rehype, tailwind typography plugin                  |\n| Styling       | Explicit components ensure consistent spacing + semantics    | Global prose cascade; overriding specifics can get verbose                    |\n| Metadata      | Plain object with getters, no parsing step                   | YAML/gray-matter parsing + type guards                                        |\n| Authoring     | JSX (great for devs, less friendly for non-dev contributors) | Markdown/MDX is more approachable for large content teams                     |\n| Extensibility | Add new primitives as React components                       | Leverage existing MDX plugins for embeds/shortcodes                           |\n| Scale         | Works best for handfuls of curated posts                     | MDX/frontmatter shines when dozens of posts and multiple authors are involved |\n\nFor this portfolio (targeting ~10 articles), the trade-off favors control and minimalism. If publishing volume grows or non-dev authors get involved, the architecture can pivot to MDX or a CMS-backed pipeline without rewriting the current posts.\n\n### Typography \u0026 Rendering Details\n\n- **Headings** (`Heading`) add scroll-margin anchors and optional `asChild` slots for custom tags while keeping consistent `pt/pb`.\n- **Text primitives**: `Text`, `Lead`, `Small`, `Quote`, `List` (switchable ordered/unordered), `InlineCode`, and `Surface`.\n- **Tables**: `Table`, `Tr`, `Th`, `Td` enforce consistent padding and border tokens.\n- **Code**: `CodeBlock` uses [Bright](https://github.com/codehike/bright) for dual-theme SSR highlighting; inline snippets use `InlineCode`.\n- **Figures**: `Figure` wraps `next/image`, accepts `blurDataURL`, caption, priority, and enforces the 1200×630 aspect.\n- **Content Loader**: `blog/[slug]/page.tsx` uses `loadPost()` (cached) to import the component + metadata, enforce spacing, and render `ScrollProgress`, tags, and badges.\n\n## Scripts\n\n- `bun run dev` — start dev server\n- `bun run build` — production build\n- `bun run build:verify` — test → typecheck → prettier → lint → build (mutating)\n- `bun run test` — bun tests in `tests/`\n- `bun run lint` — eslint with auto-fix\n- `bun run prettier` — format all files\n\n## Pages\n\n- Routes: `/`, `/blog`, `/blog/:slug`\n- SEO helpers: `robots.ts`, `sitemap.ts`, metadata in `layout.tsx`\n\n## Dependencies (versions)\n\n| Category  | Packages                                                                                                                                                                                                                       |\n| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Core      | `next 16.0.7`, `react 19.2.1`, `react-dom 19.2.1`, `@radix-ui/react-icons 1.3.2`                                                                                                                                               |\n| Code      | `bright ^1.0.0`                                                                                                                                                                                                                |\n| Styling   | `tailwindcss 4.1.17`, `prettier-plugin-tailwindcss 0.7.2`, `@tailwindcss/postcss 4.1.17`                                                                                                                                       |\n| Lint/Type | `eslint 9.39.1`, `eslint-config-next 16.0.7`, `eslint-plugin-simple-import-sort 12.1.1`, `eslint-plugin-tailwindcss 4.0.0-beta.0`, `typescript 5.9.3`, `@types/node 24.10.1`, `@types/react 19.2.7`, `@types/react-dom 19.2.3` |\n| Analytics | `@vercel/analytics 1.6.1`, `@vercel/speed-insights 1.3.1`                                                                                                                                                                      |\n| Runtime   | `bun 1.2.21`                                                                                                                                                                                                                   |\n\n## Roadmap (living)\n\n- [x] Scaffolded core routes (home/index, blog index/detail)\n- [x] SEO helpers wired (metadata, robots, sitemap with lastModified)\n- [x] ESLint + Prettier configured (Tailwind/import sorting)\n- [x] Tailwind v4 tokens/theme set up\n- [x] Blog rendering pipeline (TSX posts + dynamic import) and listing\n- [x] Write tests for posts parsing, sitemap/robots output, and button variants\n- [x] Theme-aware components; consider light toggle\n- [x] Code highlight refinements and code block components\n- [x] Unified button/badge/callout variants with shared color tokens\n- [x] Per-post OG image generation (shared generator for /blog)\n- [x] Replace placeholder page content with real sections/components\n- [x] SEO generation extracted from post meta\n- [x] Blog rich layout enhancements - tags, filtering blogs, further reading, image captions underneath blog\n- [x] Per-post OG image generation\n- [x] Deploy to Vercel and verify analytics\n- [ ] Post-launch performance and accessibility optimizations\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbmongithub%2Fcbm-portfolio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcbmongithub%2Fcbm-portfolio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbmongithub%2Fcbm-portfolio/lists"}