Skip to ContentSkip to Navigation
⠋
  • Writing
  • Open source
  • Follow me

Featured case studies

  • Joonbyrd
    +
    +

    Feel the joy

    JoonbyrdFull-stack development
  • Redistribute Magazine
    +
    +

    On the Record

    Redistribute MagazineDesign, development, branding
  • Vixen Fitness
    +
    +

    Bringing foxy back

    Vixen FitnessDesign, development, branding

Hi, I'm James.
I make websites.

Iʼm currently leading the talented dev team at Superrb where I build awesome websites and interactive experiences like these

  • Joonbyrd
    +
    +

    Feel the joy

    JoonbyrdFull-stack development
  • Redistribute Magazine
    +
    +

    On the Record

    Redistribute MagazineDesign, development, branding
  • Vixen Fitness
    +
    +

    Bringing foxy back

    Vixen FitnessDesign, development, branding

Turns out, the big names have great taste.

My work has been recognised by some of the industryʼs top players.
  1. Awwwards

    • Honorable Mention
  2. Awwwards

    • Honorable Mention
    • Nominated for 2022 E-commerce Site of the Year

    Typewolf

    • Site of the Day
  3. Awwwards

    • Site of the Day
    • Developer Award
    • Nominated for 2019 Developer Site of the Year

    Creative Pool

    • Nominated for 2019 Annual Web Shortlist

    CSS Design Awards

    • Site of the Day
    • Best Innovation
    • Best UI Design
    • Best UX Design

Some things are too good to keep to myself

I build open source tools, because good ideas are better when shared.
/**
 * LiveNodeList
 *
 * An alternative to document.querySelector
 * that monitors the DOM for changes
 */
import { LiveNodeList } from 'live-node-list'

const items = new LiveNodeList('.my-selector')

// Attach an event listener to each item
// in the node list. The listener will automatically
// be attached to any new elements matching the
// selector when they are added to the DOM
items.addEventListener('click', (e) => {
  alert(`Clicked! ${e.target.id}`)
})

// Listen for changes to the node list
// (e.g. when elements are added or removed)
// and apply updates to the new items
items.on('update', (newItems, oldItems) => {
  newItems.forEach((item) => {
    item.setAttribute('aria-label', 'New item')
  })
})
/**
 * Consumer
 *
 * A simple and lightweight tool to create an
 * ORM-like consumer for any JSON REST API
 */
import consume from 'api-consumer'

const api = consume('https://api.example.com')

// GET https://api.example.com/users/123/comments
const userComments = await api.users[123].comments.all()

// POST https://api.example.com/users
const user = await api.users.create({
  name: 'Joe Bloggs',
  email: 'joe@bloggs.com'
})

// GET https://api.example.com/users/1
const user = await api.users.find(1)
user.name = 'Joseph Bloggs'

// PUT https://api.example.com/users/1
await user.save()
/**
 * ZUnit
 *
 * A powerful testing framework and
 * assertion library for ZSH projects
 */
#!/usr/bin/env zunit

@test 'App should run' {
  run my-app

  assert $state equals 0
  assert "$output" same_as "It worked!"
}

@test 'App fails with invalid arg' {
  run my-app --invalid-arg

  assert $state equals 127
  assert "$output" same_as "Invalid argument: --invalid-arg"
}

/**
 * Async
 *
 * A simple PHP library for creating asynchronous processes,
 * and handling inter-process communication via sockets.
 */
<?php

use Superrb\Async\Handler;

// Create the handler process
$handler = new Handler(function(int $i): bool {
  $this->send('Hi from process '.$i);
  return true;
});

// Launch multiple processes concurrently
for ($i = 0; $i < 10; $i++) {
  $handler->run($i);
}

// Wait for completion, and check output of each process
$handler->waitAll();
foreach($handler->getMessages() as $message) {
  log($message);
}
/**
 * Phillip
 *
 * Meet Phillip, the newest member of your test team.
 * A PHP testing framework for the 21st century
 */
<?php

test('Does 1 equal 1', function($t) {
  if (1 === 1) {
    return $t->pass();
  }

  return $t->fail('1 does not equal 1');
});

/**
 * Magic Roundabout
 *
 * A tiny JavaScript carousel.
 * 
 */
const magicRoundabout = new MagicRoundabout(
  '#your-element',
  {
    auto: false,
    buttons: true,
    center: false,
    click: true,
    delay: 10000,
    draggable: false,
    keys: true,
    limit: true,
    loop: true,
    onChange: (slideshow) => {},
    touch: true,
    scroll: true,
    vertical: false
  }
)
View more on Github

I write about design, development, and the web.

Here are a few of my favourite articles.
  • The view from above

    The view from above

    10th June 2014

    I long for the days when photos sparked imagination, not like today’s fleeting digital images. Can we aim to steer technology towards enhancing life, rather than just providing more online distractions?

  • Selling yourself

    6th November 2017

    Why there are no examples of my work on this site.

  • Getting it wrong

    26th April 2017

    A heartfelt apology to users with accessibility needs.

View all articles

Need help?

  • Writing
  • Open Source
  • Follow me
    • 2017
    • 2015
©2025 molovo. All rights reserved.