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
Turns out, the big names have great taste.
My work has been recognised by some of the industryʼs top players.Awwwards
- Honorable Mention
Awwwards
- Honorable Mention
- Nominated for 2022 E-commerce Site of the Year
Typewolf
- Site of the Day
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
}
)
I write about design, development, and the web.
Here are a few of my favourite articles.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?
Why there are no examples of my work on this site.
A heartfelt apology to users with accessibility needs.