000 Days
00 Hours
00 Mins

Race morning until the LA Marathon!!

Community

Share this story

If Kevin’s story is worth 90 seconds of someone else’s time, sharing it is the easiest way to help.

Follow along

Training updates on Instagram & Facebook

Behind-the-scenes miles, race-day countdowns, and everything in between — I post it all as I train toward March 7, 2027.

For the engineers

Behind the code

Janitor in the daytime, junior software engineer by nighttime. I built and still run this entire site myself — no agency, no template. Powered by plain HTML/CSS/JS, a Neon Postgres database, serverless Vercel functions, and Cursor AI as my daily development partner, I’ve grown this project from a single long scroll into a structured, multi-page web app. The source is public if you want to poke around.

The stack How this site is actually built
flowchart TD
    Browser["Visitor's browser"]
    Static["Vercel static hosting\nindex.html · styles/*.css · js/*.js"]
    ThirdParty["Third-party embeds\nGoogle Maps/MapLibre · SociableKIT · Mermaid.js"]
    API["Vercel serverless functions\napi/*.js"]
    DB[("Neon Postgres\ntraining · weigh-ins · milestones · funds · comments · live track")]
    GitHub["GitHub repo (public)"]
    Deploy["Vercel auto-deploy"]

    Browser -->|"GET /"| Static
    Browser -->|"loads"| ThirdParty
    Browser -->|"fetch('/api/...')"| API
    API -->|"SQL over DATABASE_URL"| DB
    GitHub -->|"git push to main"| Deploy --> Static
            

Diagram renders when you expand this panel.

The frontend is deliberately boring: hand-written HTML/CSS/JS, no build step, no bundler. Every chart on this page — the weigh-ins, the seizure-frequency bars — is plain SVG built and animated by app.js, with no charting library. The backend is a set of small Vercel serverless functions in api/, each one reading or writing a Neon Postgres database (training runs, weigh-ins, milestones, fundraising totals, comments, race-day GPS).

Every change ships the same way: a feature branch, a pull request with its own live Vercel preview URL, then a squash merge to main that Vercel auto-deploys straight to this domain — usually within a minute or two of merging.

View the source on GitHub →

The history From one page to five
flowchart LR
    subgraph Before["Before"]
        Mono["index.html\n(one long page)\nhero · story · recovery ·\nmarathon · community"]
    end
    subgraph After["After — 5 real pages"]
        Home["/"]
        Journey["/journey"]
        Recovery["/recovery"]
        Marathon["/marathon"]
        Community["/community"]
    end
    Partials["partials/*.html\nshared nav · footer ·\nrace clock · entry warning"]
    Build["scripts/build.js\nsplices in shared partials"]

    Mono -->|"sliced by exact\nline range"| Build
    Partials --> Build
    Build --> Home
    Build --> Journey
    Build --> Recovery
    Build --> Marathon
    Build --> Community
            

Diagram renders when you expand this panel.

This site used to be one really long index.html — hero, story, recovery, marathon, and community all on a single scroll, stitched together with #anchor links. It worked, but every new section made that one file (and one page load) heavier, and the nav's dropdown links were really just scrolling the same page rather than going anywhere.

So I split it into 5 real pages — /, /journey, /recovery, /marathon, /community — each its own static HTML file. The nav, footer, race-clock countdown, and entry warning still live in exactly one place each, in partials/*.html; a small build script (scripts/build.js) splices them into every page via <!-- INCLUDE:partials/xxx.html --> markers before anything deploys, so there's still only one copy of the header to update, not five. Every paragraph that moved pages was sliced out of the old file by its exact line range rather than retyped, so nothing changed by accident along the way.

Old bare #anchor links still work — hash fragments never reach the server, so that redirect runs client-side off the homepage instead, matching each old anchor to its new page.

The roadmap Everything shipped so far
flowchart TD
    Early["PRs #63-99 - early iteration\nElectric Purple rebrand - live Strava-synced\ntraining data - one page split into five"]

    subgraph Phase1["Phase 1 (PRs #100-106) - Cleanup & conversion"]
        P1a["Clean URLs: /journey - /recovery"]
        P1b["Non-blocking sensory banner"]
        P1c["48px touch targets everywhere"]
        P1d["New /resources page"]
        P1e["Homepage section reorder"]
        P1f["501(c)(3) + $/mile donate CTAs"]
    end

    subgraph Phase2["Phase 2 (PRs #107-112) - Structure & SEO"]
        P2a["Shared breadcrumbs component"]
        P2b["Journey/Recovery/Marathon/Community\nsplit into hub + sub-pages"]
        P2c["Heading-hierarchy + meta description pass"]
    end

    subgraph Phase3["Phase 3 (PRs #113+) - Safety & redesign, in progress"]
        P3a["Flashing lightning removed,\nreduced-motion gaps closed"]
        P3b["About / Privacy pages"]
        P3c["Nav + hero rewrite - next"]
        P3d["Global CSS baseline - next"]
        P3e["Final consistency pass - next"]
    end

    Early --> Phase1 --> Phase2 --> Phase3
            

Diagram renders when you expand this panel.

Nothing above happened as one big rewrite — it’s been 50-plus small, reviewed pull requests, each one on its own branch with its own live Vercel preview before merging to main (see “The stack” above for exactly how). After an early stretch of fairly organic feature work — the Electric Purple rebrand, live Strava-synced training data, splitting one long page into five — work since has moved through three more deliberate passes: a cleanup pass (clean URLs, accessibility fixes, a new resources page), a structure-and-SEO pass (every section split into its own real sub-page, with breadcrumbs and a full heading-hierarchy audit), and the safety-and-redesign pass that’s in progress now.

As of this writing, Phase 3 is two pull requests in — removing the flashing-lightning effect and adding this very page’s About/Privacy footer links — with a nav rework, a global CSS baseline, and a final consistency pass still queued.

Donate