Gear & devices

Setup

The devices and everyday electronics I actually reach for. An ever changing list refined over time.

Under the hood

How this site is built

No framework, no CMS — just vanilla JavaScript, a handful of serverless functions, and a design system I hand-wrote. Here's the whole stack, from the browser you're reading this in to the databases behind the gated pages.

  1. Browser Vanilla JS + Vite bundle
  2. Vercel Static dist/ + /api/*
  3. Data & media Supabase · R2 · Apps Script
  • Frontend

    Vanilla JS

    No framework. Each page is a side-effect-only ES module, loaded by its own HTML file.

    • JavaScript
    • ES modules
    • No framework
  • Build

    Vite

    A multi-entry bundle — every page has its own entry point, all compiled to dist/.

    • Vite
    • Rollup
    • Multi-entry
  • Styling

    Hand-written CSS

    One design-token system. Light and dark themes swap through a single data-theme attribute.

    • CSS
    • Custom properties
    • No Tailwind
  • Auth

    Supabase Auth

    JWT sessions. Two permission levels live in app_metadata so users can't self-edit them.

    • Supabase
    • JWT
    • app_metadata
  • Data

    Postgres

    Food spots, travel, ratings and comments — every table row-level-security gated to who may read it.

    • Postgres
    • Row-level security
  • Media

    Cloudflare R2

    Private photos in an S3-compatible bucket, served to authorised users through short-lived presigned URLs.

    • R2
    • S3 API
    • Presigned URLs
  • Serverless

    Vercel Functions

    The /api/* routes — admin actions, URL signing, mailing-list broadcasts — each a small Node handler.

    • Node
    • Serverless
  • Hosting

    Vercel Edge

    Static files on the CDN, clean-URL rewrites, permanent redirects and HSTS defined in vercel.json.

    • CDN
    • Edge
    • HSTS

The interesting bit

The permission-gating trick

The gated “Life & places” pages aren't protected by hiding a link. Access lives in the JWT's app_metadata — never user_metadata, which a signed-in user could edit themselves. The subtlety: the user object's app_metadata can lag the token, so on sign-in the session is enriched from both and the JWT is refreshed if they diverge. Once verified, the whole gate is CSS — a class on the root element reveals content with no JavaScript in the render path.

The whole thing is open on GitHub.

Back to home