All projects
MaintenanceWebSep 2026 — present

portfolio-creator

Fill in a form, get a portfolio.

A guided form that writes the one file a portfolio is configured by. It runs entirely in the browser with no backend and no account, validates every answer against the portfolio's own JSON Schema, and can create the repository and turn on Pages at the end.

Built with

ReactViteTailwind CSSAjvJSON SchemaGitHub API

Highlights

  • ▸Validates against the same split JSON Schema the portfolio builds from, so a file this form accepts is a file the portfolio will build.
  • ▸Works with the network gone, reloads included — a service worker precaches the app, and only publishing needs a connection.
  • ▸Three ways out: download the file, paste a scoped token and let it create the repository and enable Pages, or take the file to another host.
  • ▸No hardcoded identity anywhere. The blank starter is derived from a real portfolio with the owner stripped, and the derivation fails if one trace survives.

Screenshots

The form, with the questions in the middle and the remaining problems on the right
The questions in the middle, the schema's opinion of the answers on the right.
Image 1 of 4

From the README

Portfolio Creator

Fill in a form, get a portfolio.

→ Open the app

This is a guided form that writes one file — settings.json — and that file is the entire portfolio. Everything else is already built: pages, routing, dark mode, animations, an SEO head, a sitemap, and a project list that reads itself out of your GitHub account so the site keeps up with your work without you touching it again.

It runs entirely in your browser. There is no backend, no account, and no database. The only network calls it makes are to api.github.com, and only when you ask for them.

Which also means it works offline. Open it once and it keeps working with the network gone, reloads included — the only thing that needs a connection is publishing, at the end.

The form, with the questions in the middle and everything still to fix on the right

More of it

Answering a section. Every question is one key in settings.json; the panel on the right is the schema's opinion of your answers, updating as you type.

The GitHub section

SEO and AI. Three switches decide who may read the site: search engines, AI training crawlers, and the machine-readable copies of your data.

The SEO and AI section

Publishing. Download it, let a token do it, or take the file somewhere else entirely.

The publish step


Why a form and not a template to edit

A template you edit by hand goes stale. You change a colour, the upstream adds a page, and now merging is a chore you will not do — so you do not, and the site quietly rots.

Here your answers live in one file that nothing else touches. Pulling in new pages, new sections and new schema fields is a merge of the template rather than a rewrite of your site. Come back to this form later and it will show you the new questions.

The form is checked against the same JSON Schema the portfolio itself uses, split across public/schemas/settings/. There is no second set of rules to drift out of sync: if this page accepts your answers, the portfolio will build.

Those files are a copy, so keeping them a faithful copy is a command rather than a habit — see syncing the schema.


What you get

  • Projects that update themselves. The portfolio reads your public repositories at build time. Push something new and it appears.
  • A résumé an AI can read. The build publishes llms.txt, humans.txt and /api/portfolio.json, /api/projects.json, /api/about.json, /api/contact.json. Hand someone your URL, ask a model to write your résumé from it, and there is something real for it to read — not scraped HTML.
  • Crawling you control. Three switches: search engines, AI training crawlers, and the machine-readable data above. Off means off, written into robots.txt.
  • A copy-this-page button, so a visitor can take your whole portfolio as Markdown in one click.
  • No hardcoded anybody. Every piece of identity on the site comes from settings.json. The starter this form begins from is derived from a real portfolio with the owner stripped out — and the derivation script fails if a single trace survives.

Three ways to publish

  1. Download and do it yourself. Fork the template, drop settings.json into public/, push. No token, works everywhere.
  2. Let it publish for you. Paste a fine-grained personal access token; it creates the repository, commits your settings and turns on Pages. The token lives in the page for as long as the tab is open — never stored, never logged, never sent anywhere but api.github.com.
  3. Somewhere other than GitHub. See docs/hosting.md for GitHub Pages, Cloudflare Pages, Cloudflare Workers, Netlify, Vercel and Render, with the trade-offs that actually matter.

Why there is no "Sign in with GitHub" button

A browser-only OAuth flow cannot complete against github.com — the token endpoint sends no CORS headers, and no amount of client-side code gets around that. The honest options are a token you paste, or a small server doing the exchange. A Cloudflare Worker is about thirty lines and is written up in the hosting doc; until that exists, a scoped token you revoke afterwards is strictly safer than a long-lived OAuth grant anyway.


Running it locally

npm install
npm run dev
ScriptWhat it does
npm run devVite dev server
npm run buildBuilds to dist/, then writes robots.txt, sitemap.xml, humans.txt, the web app manifest and the service worker
npm run previewServes the built output
npm run lintESLint
npm run sync:schemaCopies the portfolio's JSON Schema into public/
npm run derive:starterRegenerates src/data/starter.json from a real settings.json

Two environment variables matter at build time: BASE_PATH (for a project-page subpath, e.g. /portfolio-creator/) and SITE_URL (what the sitemap advertises). The Pages workflow sets both.


How it is put together

src/data/steps.js       the form, as data: steps → fields → dotted paths
src/data/starter.json   a blank, schema-valid settings.json (generated)
src/lib/schema.js       Ajv over the portfolio's own split schema
src/lib/settings.js     get/set by dotted path, export formats
src/lib/github.js       api.github.com, client-side only
src/lib/derive.js       fields worked out rather than asked about twice
src/components/         generic renderers — nothing knows what a field means
scripts/sync-schema.js     copies the portfolio's schema into public/
scripts/derive-starter.js  regenerates the starter from a real settings.json
scripts/generate-seo.js    post-build crawlable files and the web app manifest
scripts/generate-sw.js     post-build service worker, from sw-template.js

Adding a question to the form is adding an entry to steps.js. The renderers are generic and the schema is the validator, so there is nothing else to change.

Syncing the schema

npm run sync:schema            # assumes ../VKrishna04.github.io
npm run sync:schema -- <path>  # or point it at the checkout

The schema lives in the portfolio; this repository holds a copy so the form can validate offline. Copying it across by hand is how three drifts got in — a missing navigation.schema.json, a technology-name pattern that rejected C++, and a projects section without tier, order or packages. Each made this form disagree with the portfolio about a file the portfolio would happily build. Run this whenever the schema upstream moves, then regenerate the starter and commit both.

Regenerating the starter

npm run derive:starter

It keeps the structure and every styling default, empties what carries a person, deletes keys that cannot legally be blank (rather than inventing a placeholder), and then sweeps the result for any trace of the source portfolio's owner. It exits non-zero if it finds one, or if a remaining gap is not a question the form actually asks.

Regenerating the share card

public/og.png is the 1200×630 card that appears when the link is pasted into Slack, Discord or X. Its source is scripts/og.html — edit the words there and screenshot it at exactly 1200×630. The screenshots above are the app itself at 1440×900, taken with a headless browser; nothing about either is load-bearing, so a stale one costs you nothing but a slightly old picture.


Credits

Designed & built by Krishna GSVV.

Built on the portfolio template — see one live.

Licensed under Apache 2.0. Attribution is required by the template's NOTICE under Apache License 2.0 section 4(d): keep the credit, and the rest is yours to change.

Sister projects worth switching on: CodeLedger for DSA practice statistics, and CounterAPI for a visitor count that does not track anyone.

Local Storage Notice

This site uses local storage to cache GitHub data for better performance, and an anonymous visit counter. No personal data is stored or shared.