Portfolio site for Sean August Horvath — and a deliberately dependency-free, JSON-driven architecture for fast, agent-authorable portfolio and showcase sites.
Every page (homepage, project entry, art collection, tag/filter view) renders from a structured JSON file, hydrated by a few hundred lines of vanilla JS. No framework, no bundler, no build step beyond a single Python manifest script. Deployed on Vercel, with media on Cloudflare R2.
Live: august.style
- No framework, no dependencies. Vanilla HTML/CSS/JS, plus one
@aws-sdk/client-s3import behind the upload endpoint. It loads instantly and will still run, unchanged, years from now. - Content is data, not code. A project is one JSON file. Adding work never touches the rendering layer — drop a file in
assets/entries/, run one script, and it's live with SEO meta baked in. - Agents can author it end-to-end. The schemas plus a single SOP (
assets/docs/ENTRY_SOP.md) are explicit enough that an AI agent can source media, upload it to the CDN, write the JSON, validate, and ship — no human in the rendering loop. - Cheap and durable to run. Static files on Vercel + images on Cloudflare R2 (no egress fees). A full portfolio costs next to nothing to host and has almost nothing to break.
If you build sites for clients, it's a clean base to fork: a small content model, legible rendering, and an agent-authoring pipeline that lets the client keep their own site current. The architecture reference below is the whole thing — nothing hidden behind a build.
- 34 project entries (
assets/entries/uid-*.json, schema v6.1) in four layouts —columns(two-column case study),flow(typed-block long-form),gallery(image-forward art showcase), andurl(a redirect tile that renders normally but links straight to an external site/demo, with no entry page). The three case-study layouts share one top zone (hero thumbnail row + title/subtitle + role) and one tag model; they differ only in the content zone below. - Art collections (
assets/collections/uid-col-*.json, schema v6.1) — orderedimages[]of CDN URLs, each belonging to exactly one parent entry (an entry can have many). On a gallery entry they render as one full-bleed, accent-matted "art-wall" per collection; on the filter page they appear as first-class tiles; each collection page links back to its parent with a breadcrumb. - Tag-based filtering across four groups — role, skill, product, company — plus a
featuredboolean. The filter page gives each group its own dropdown (with an any/all toggle and searchable checkboxes) and a Projects / Collections content-type selector. - Configurable homepage via
assets/js/homepage-content.json— a three-phase narrative spine, a four-card featured-tiles grid (one auto-playing video tile driven by thefeaturedboolean + three phase cards), a full-bleed art wall pulled across the collections of entries taggedArt Collection, process steps, and achievements rolled up from entries. - Media on CDN at
cdn.august.style(Cloudflare R2 bucketportfolio, AWS CLI profiler2). Images upload through the/api/uploadendpoint (Cloudinary resize → WebP → R2); video uploads viaaws s3 sync. - Pre-rendered SEO —
generate_manifest.pywrites_pages/{slug}.html(entries) and_pages/{entry}/{coll}.html(collections) with baked-in meta tags for social sharing. - Agentic content creation — AI agents can author new entries and collections end-to-end via the standard operating procedure in
assets/docs/ENTRY_SOP.md.
See assets/docs/AUGUST_STYLE.md for the full architecture reference (schemas, controllers, data flow, URL routing, file structure) and assets/docs/ENTRY_SOP.md for the content-authoring pipeline.
| File | Purpose |
|---|---|
index.html |
Homepage (hero → narrative spine → featured tiles → art wall → process → credentials → achievements → CTA) |
entry.html |
Entry-page template (layout: "columns", "flow", or "gallery") |
section.html |
Universal tag/filter page (per-group dropdowns + content-type selector) |
collection.html |
Collection-page template (gallery art-walls, breadcrumb to parent entry) |
generate_manifest.py |
Manifest builder + per-slug HTML page generator with SEO meta |
api/upload.ts |
Image-upload endpoint — Cloudinary resize-to-2400 + WebP → R2 (Bearer-auth, single caller) |
assets/js/homepage-content.json |
Homepage configuration + copy strings |
assets/js/data-loader.js |
Fetching, caching, filtering, tag helpers, collection resolvers |
assets/js/landing-controller.js |
Homepage rendering (incl. renderArtBleed art wall) |
assets/js/entry-controller.js |
Entry-page rendering — columns / flow / gallery layouts, per-collection art-walls, unified lightbox pool |
assets/js/filter-controller.js |
Section-page filter UI — per-group dropdowns, any/all, content-type selector |
assets/js/section-controller.js |
Section-page pool loading + structured filtering (entries + collections as tiles) |
assets/js/collection-controller.js |
Collection-page rendering + breadcrumb |
assets/scripts/validate.py |
Schema validator (entries v6.1 + collections v6.1; flags retired fields) |
assets/scripts/new_project.py |
Skeleton generator (--type {entry,collection}) |
assets/scripts/cdn_cleanup.py |
R2 orphan-detection report (no deletion) |
assets/docs/AUGUST_STYLE.md |
Living architecture / schema reference |
assets/docs/ENTRY_SOP.md |
Content-authoring SOP (entries + collections) |
assets/docs/tags.json |
Tag registry (role / skill / product / company) |
The canonical preview surface is the Vercel dev deploy (the dev branch, at design-360-git-dev-…vercel.app) — it reproduces production routing, rewrites, and headers. Review there: commit → push dev → open the dev URL.
A local static server works for quick checks, but its routing differs from Vercel (no pretty-slug rewrites), so treat it as a rough preview, not the source of truth:
python3 -m http.server 5500 --bind 127.0.0.1Locally, entry and collection pages take a ?path= parameter; production uses pretty-slug routing (/{slug}/ for entries, /{entry}/{coll}/ for collections):
http://localhost:5500/ # Homepage
http://localhost:5500/section.html?tags=web-developer # Section / filter
http://localhost:5500/entry.html?path=illustration-art-deco # Entry (gallery layout)
http://localhost:5500/collection.html?path=illustration-art-deco/animals # Collection (nested)
See assets/docs/ENTRY_SOP.md for the full procedure. Quick reference:
python3 assets/scripts/new_project.py --type {entry|collection}— generate a skeleton.- Upload media — images via
POST /api/upload(Cloudinary resize → R2), video viaaws s3 syncagainst R2 with ther2profile. - Fill out the JSON fields using CDN URLs.
- Move the file to its directory and validate:
python3 assets/scripts/validate.py. - Regenerate the manifest:
python3 generate_manifest.py.
python3 generate_manifest.pyOutputs:
assets/js/manifest.json— a slug-to-file map (entries,collections). Collections are keyed by their nested path<entry>/<coll>._pages/{slug}.html(entries) and_pages/{entry}/{coll}.html(collections) — each with pre-rendered SEO meta tags.
Stale _pages/*.html files for slugs no longer in the manifest are cleaned up automatically.