Bait And Switch Pricing – A Bluehost Retrospective

Now that I’ve finally finished migrating my WordPress blog off of Bluehost, here are some things about it.

The Good

As far as WordPress hosts go, it was fine. The only technical issue I had was that the caching was a bit aggressive, so that edits to posts didn’t show up immediately. This might actually have been an advantage if I had a high traffic blog.

The onboarding support and regular support were different. Onboarding support was via live chat, and very attentive. Regular support was a 24-hour turnaround ticketing or email system. I only needed it once (the caching issue I mentioned).

Offboarding support (cancelling the account) was also pretty painless. This was also via live chat. I was expecting a hassle, because when I looked up “how to cancel bluehost account” on the web, it sounded like I’d need to make a phone call and provide a lot of PII. They did ask for enough info to verify that I was the owner of the account, but that was it. There wasn’t any hard sell or attempt to change my mind, but this might have partially been because I told them I’d already migrated everything to the new server (which I had).

They also did provide a partial refund for the unused time (billing is annual, and I cancelled 2/3 of the way through the year), though it may be a few days before it posts to my account.

The Bad

Other than the crapware marketing plugins that they install (which, again, may have been useful if I was trying to run, or grow, a big site), the main negative was the pricing, and the difference of the actual pricing from the expected pricing.

When I first found Bluehost, the advertised price was something like $3.75 a month. This sounded like a great deal for shared WP hosting — my previous host was $5. When I signed up, I did opt for things like backup and security, which brought the price up to like $10 a month, billed annually. Okay, fine. So, about $120 a year. In retrospect, I should have considered this discrepancy a red flag.

A year later, I was billed $250 for the renewal. I don’t remember getting a satisfactory explanation for the increase, so I planned to migrate off. Of course, I didn’t get around to this for a while, they increased it again to $290, and the final renewal was over $300! Looking at my receipts, it was $317.74 to be exact, or $26.48 a month.

$26 a month for what was supposed to be cheap WordPress shared hosting! A service that’s usually around $5 a month. Their website, as of 3/1/2022, advertises the price of WordPress hosting at $2.95 a month.

The Migration

So about 6 months ago I imported my posts into a new $5/month Linode instance that I’m administering myself (plus $2 for backups), got sidetracked, and then finally finished with the SSL and domain registration stuff the other day.

On the bright side, in the process of migrating, I tweaked the appearance a bit, added some custom CSS, and I’m fairly pleased with how it looks, at least on desktop browsers.

Slicing and Splicing in JavaScript and Python

This post covers some of the syntax and parameters for doing array or list slices and splices in Python and JavaScript — in other words, working with sections of lists or arrays, instead of the whole list/array — and some of the similarities and differences between them.  This is intended as a map between the two languages, rather than a comparison of their strengths and weaknesses.

JavaScript arrays and Python Lists

Lists in Python and arrays in JavaScript are built-in data structures that are used for mostly equivalent things.  They’re both loosely typed sequences of items, indexed by sequential integers starting at 0, with the ability to add or remove items at any point (in other words, they’re not a fixed length).

The differences between the structures they allow, such as that JavaScript arrays can be sparse, while Python’s lists don’t allow this, are beyond the scope of this post.  As we’ll see, there are some differences in the syntax used for splicing and splicing, but also a lot of similarities in how slicing and splicing work in the two languages.

Slicing and Splicing

A slice is a section of an array or list.  Usually it’s a contiguous section (but see the step parameter for an exception to that rule).  When you use the syntax or method calls provided by the language to get a slice of an array, it’s basically a new array or list that’s shallow copy of that section of the original array/list.

Splicing is inserting a sequence of items in an array or list, possibly replacing a given section (i.e. replacing a slice), but possibly just inserting them between existing items.

For both of these features, JavaScript uses built-in methods of the Array class, while Python uses special syntax.

The same slicing syntax works in the same way for strings in both languages, but splicing doesn’t: both languages treat strings as immutable.

Slice Syntax in Python

In Python, to get a slice of a list, you use the following syntax: arrayName[start:stop:step].  Say it with me now: “start stop step”.  I find that the alliteration makes this easy to remember this way, and slice syntax also uses the same parameters as Python’s range() function.

start: The index of the first item to include in the slice.

stop: The index of the first item to *not* include in the slice.

step: The number of items from one included item to the next.  This defaults to 1.  If it’s 2, you’re taking every other item.

Here’s an example:

>> arr = ["red", "green", "blue", "alpha"]
>>> print(arr[1:3:1])
['green', 'blue']

We start with item 1 (remember, lists are zero-based), and stop right before item 3.  So our slice includes items 1 (“green”) and 2 (“blue”).

Since we’re using a step of 1, we can omit that parameter:

>> arr = ["red", "green", "blue", "alpha"]
>>> print(arr[1:3])
['green', 'blue']

The slice() Method in JavaScript

To get a slice of an array, you use the slice() method.  It uses start and end parameters, which function the same as start and stop in Python slices:

>> let arr = ["red", "green", "blue", "alpha"]
>> arr
Array(4) [ "red", "green", "blue", "alpha" ]
>> arr.slice(1, 3)
Array [ "green", "blue" ]

Splicing in Python

As with slices, splicing in Python is done with special syntax.  It’s the same syntax as slices, except you add = afterward to replace the slice with a new sequence:

>>> arr
['red', 'green', 'blue', 'alpha']
>>> arr[1:3] = ["orange", "yellow", "cyan"]
>>> arr
['red', 'orange', 'yellow', 'cyan', 'alpha']

A Python programmer might not usually use the term “splicing”, since there’s no splice() method.  The official Python documentation just calls it “assigning to a slice”.

The splice() method in JavaScript

In JavaScript you splice by calling the array’s splice() method.  This method has a start parameter, which works the same as for slicing.  However, instead of an end parameter it uses deleteCount: the number of items to remove.  The remaining parameters are the items to insert in place of what was removed (and there can be any number of these).

>> let arr = ["red", "yellow", "blue"]
>> arr.splice(1, 1, "green", "orange")
Array [ "yellow" ]  // splice() returns a sequence of the removed items.
>> arr
Array(4)[ "red", "green", "orange", "blue" ]

You can also insert the elements of an array into another array using the spread () operator (similar to * in Python):

>> let arr = [8, 6, 0, 9]
>> let arr2 = [7, 5, 3]
>> arr.splice(2, 0, ...arr2)
Array []
>> arr
Array(7) [ 8, 6, 7, 5, 3, 0, 9 ]

Deleting Ranges

In Python you can delete a range within a list by assigning an empty list to a slice.

>>> arr = ['red', 'orange', 'yellow', 'cyan', 'alpha']
>>> arr[1:4] = []
>>> arr
['red', 'alpha']

You can also use the del keyword:

>>> arr = ['red', 'orange', 'yellow', 'cyan', 'alpha']
>>> del arr[1:4]
>>> arr
['red', 'alpha']

In JavaScript, you remove a range by leaving off the item parameters in the call to splice().

>> arr
Array(4)[ "red", "green", "orange", "blue" ]
>> arr.splice(0, 2) 
Array [ "red", "green" ]
>> arr
Array [ "orange", "blue" ]

Clearing Arrays and Lists

This a bit of a digression, but I decided to look into JavaScript’s slicing/splicing behavior after I learned about splicing being one of the recommended ways to clear an array in JavaScript.

In Python, you can clear a list with just arr.clear(), but there’s no such method in JavaScript.

The most succinct way to empty out a JS array is:

arr.length = 0

But you can also use the splice method:

arr.splice(0, arr.length)

This replaces a section of the array beginning at the beginning, ending at the end, with nothing, since no additional items are provided.

You can do something equivalent in Python as well, using its splice syntax:

arr[:] = []

In most of these cases, adding a comment would probably be helpful, as it’s not immediately clear that emptying out the array is the intent.

The Step Parameter

The step parameter has an additional use, in that, when it’s negative, the slice goes in reverse.

When using the step parameter for splicing, there are some instances where it won’t work, like replacing one slice with a slice of a different size (including an empty one):

>> arr = list(range(10))
>>> arr
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> arr[1::2] = []
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: attempt to assign sequence of size 0 to extended slice of size 5

But you can use del to remove such a slice, even when the step parameter is used:

>> arr
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del arr[1::2]
>>> arr
[0, 2, 4, 6, 8]

JavaScript’s slice and splice methods do not have a step parameter.

Negative Indices

Negative indices used in the start and stop parameter are essentially aliases for the positive indices.  For example, if the index is -1, it means the item at length – 1.


>>> arr
[1, 2, 3, 4, 5]
>>> arr[2:4]
[3, 4]
>>> arr[-3:-1]
[3, 4]

Beware that using a negative step parameter means it will start *after* the index specified by the start parameter and stop *before* the index specified by the stop parameter.

>>> arr
[1, 2, 3, 4, 5]
>>> arr[-1:-3:-1]
[5, 4]

For splicing in Python, negative indices work the same as with slicing.


>> arr
Array(5) [ 1, 2, 3, 4, 5 ]
>> arr.slice(2, 4)
Array [ 3, 4 ]
>> arr.slice(-3, -1)
Array [ 3, 4 ]

When splicing in JavaScript, negative values for the start parameter works the same as with slicing, but negative values for the deleteCount parameter are treated as 0:

>> arr
Array(5) [ 1, 2, 3, 4, 5 ]
>> arr.splice(-1, -1, 4.5)
Array []
>> arr
Array(6) [ 1, 2, 3, 4, 4.5, 5 ]
>> arr.splice(-1, 0, 4.75)
Array []
>> arr
Array(7) [ 1, 2, 3, 4, 4.5, 4.75, 5 ]

Further Reading

Git-Bisect is a Great Debugging Tool, Even If You’re Not Using It

git-bisect is a command within Git.  It checks out commits so that you can find the one where a problem was introduced.  As the title says, it’s a great debugging tool– even if you’re not using it!

How Bisect Works

See, what it does is a binary search. Cut the sequence of commits in half and test the commit between them. Does that commit have the problem? If so, it started in that commit or earlier. Otherwise, it started in the later half. Repeat.

When the list is only one commit long, there’s your culprit. gif: an x-wing appears in a targeting screen, is centered between crosshairs. shot of vader at the controls. caption: i have you now.

Why is this genius? Well, binary search runs in a logarithmic number of steps. If you have 100 commits, the most you’ll have to test is (log base 2 of 100) 7.

1000 commits? 10.

1,000,000 commits? 20


But What if I’m Not Using Git?

Well, what if you’re using Subversion or some other VCS without bisect? While bisect semi-automates the process for you (or totally if you have the right kind of test script) this isn’t a hard algorithm to do by hand.

Manually checking out and testing 20 commits (or revisions, or whatever they’re called in your mesozoic VCS) is going to be annoying, but probably something you can do in an hour or so, especially if management is mad at you because stuff’s broken, and no one knows why.

Not Just for Finding the Bad Commit

And this goes for other things besides a list of commits (log2 of a billion is just 30, by the way. Thirty!). Let’s say you have a set of programs that more or less act as a data pipeline. And something’s going wrong in that pipeline.

Check the bit in the middle.

Is the middle program misbehaving in a way that would indicate your problem? If yes, the problem’s there or upstream, else it’s downstream. Take that half and look at the one in the middle. Repeat until your suspect list has only the culprit.

You can do this for anything where you can divide the search space into groups, and test each group as a whole. If you just have the source code of a program, maybe you can divide it into subsystems, sub-sub-systems, modules, lines, etc.

Does It Always Work?

Sometimes there are confounding factors. Maybe there are complex interactions between components of the pipeline. Maybe the test is non-deterministic. At this point, you’ll have to use logic, intuition, and your knowledge of the system to approximate the process.

Still, this technique can help in a *lot* of cases. It’s what makes me really good at debugging. Maybe too good, as far as my career is concerned, so that’s something to keep in mind if you don’t want to always be fixing others’ shit.

Anyway, now you have my superpower too.


This was originally a Twitter thread.  Credit goes to Steve Yegge for saying effectively this in a blog post way back when (I don’t remember what post it was; it wasn’t specifically about debugging), and Julia Evans‘ recent debugging tweets that got me thinking about how I debug.

Options for Pausing the Animation of GIFs

A shiny yellow-green pause button with rounded corners.

GIFs used to be just atmospheric decoration.  Now they’re usually short, looping, silent videos.  As videos, it’s not very nice to have them play automatically.  It’s much better when they have a play button and only start playing when that’s clicked or tapped.

So I recently went down a rabbit hole of ways to make gifs pause by default and only play when interacted with.  This post is intended to document some of the things I found.

So, what did I find?

How to Pause a GIF

Strictly speaking, you can’t.

Let me explain what I mean by this.  As far as I can tell, there’s no programmatic way to pause a GIF and have it still be the same GIF file in an ‘img’ tag in the browser.  You can hide it and replace it with a static image.  Or you can replace it with some other kind of animating element (such as a canvas, or CSS animation).  But there are libraries or code snippets available for doing these things.  Here are a few of them.

1. The Thing That Works Right But is Not a GIF

One of the first things I stumbled across was this CodePen.  This works right from a user point of view, in that it allows you to pause the animation, and resume from where you left off, whereas most of the other methods let you stop it, but only restart animating from the beginning.  However, as the section heading says, it’s not an animated GIF.  The image (a PNG file) contains a series of frames side by side, and the CodePen uses CSS animations to switch from frame to frame.  You can pause the animation by setting the animation-play-state property to ‘paused’, but, alas, this property doesn’t work on GIFs.

animation frames side by side

2. The One With The Simplest Code

CSS PLAY has a demo that accomplishes play/stop in a small amount of code (about 8 lines of CSS, which would probably be about 20 with more line breaks for readability), and doesn’t do DOM manipulation, relying on the :active and :hover pseudo-classes to show or hide the animated GIF by setting the visibility to hidden or visible.  An element (in this case an ‘a’ tag) that encloses the ‘img’ tag has its background image set to the static image that’s displayed while the GIF isn’t animating.

One downside is that this method requires providing a separate static image for the stopped state (the next two methods are libraries that generate the static image for you by programmatically taking a screen shot of the gif).

The code for CSS PLAY’s implementation of this concept is copyrighted and not released under an open source license.  The code requires a donation for use and special permission for commercial use.  See the Copyright section of its demo page for details.

3. Gifffer

Gifffer (three f’s) is a JavaScript library with a small minified footprint.  It requires using a custom attribute instead of ‘src’ on the ‘img’ tags and then calling the Gifffer() function in your window.onload().  It works by DOM manipulation, programmatically replacing the ‘img’ element with a div inside a button, and then putting the img back when you want it to animate.

I assume that you would have to call Gifffer() again if you add additional GIFs to the page after it’s loaded.

4. Freezeframe.js

Freezeframe.js works similarly to Gifffer but is more full featured.  It appears to have a fade effect when transitioning between the animating and stopped states, which looks nice.   It doesn’t require jQuery but is available as a jQuery plugin.

The DOM manipulation for Freezeframe.js and Gifffer might conflict with the DOM manipulation done by React or similar frameworks.

5. react-gif-player

Speaking of React, react-gif-player is a GIF player for React.  It encloses the img element within divs and changes the img’s src attribute when clicked.  You do have to provide a separate still image file for it to switch to when paused.

I didn’t find any GIF player libraries for Angular or Vue.

6. gif-player

One that does allow pausing at a specific frame is gif-player (no relation to react-gif-player).   It uses the WebComponent API to provide a custom ‘gif-player’ element, which means it should be compatible with React or other modern frameworks.  The user experience isn’t as simple as clicking on a play button though.  It starts in a default state (you can either set it to autoplay or be paused by default), and then pauses when you mouse over it or touch it, and fast forwards/rewinds by moving the mouse/finger left and right across the image.

It achieves this by processing the GIF using the omggif.js library in the browser and then rendering each frame to a ‘canvas’ element.  While this does replace the img tag, it demonstrates that it’s possible to have a pausable client-side GIF player without any server-side shennanigans.

7. A note on the <video> tag

HTML5 has a ‘video’ tag that allows playback and pausing. However, GIFs (which would be a media type of “image/gif” in the ‘source’ tag)  aren’t supported.  A tutorial on HTML5 video says to convert them from GIF to a video format using ffmpeg, which can be run from the command line.  This might be a good approach if you can pre-process all your GIFs or run a background process on the server.

Which one wins?

From trying the demos of these libraries, Freezeframe.js seemed to be the best experience from the user’s point of view.   This will probably be your best bet unless you’re using React, in which case react-gif-player was pretty smooth.

This post was adapted from this twitter thread.  If you liked this, consider following me over there.  Have you made GIFs pausable for the user?  What approach worked for you?

Tech of the Week: CSS

Welcome to the first installment of a weekly(ish) segment where I learn some things about a technology and then write some things about it. This week, I spent some time looking at CSS, and I’ll share some tricks and links with you.

CSS, or Cascading Stylesheets, is a language for telling the web browser how to lay out elements on a web page, and what they should look like.  This post isn’t intended as an introduction to CSS.  If you’re looking for that, Ali Spittel recently wrote a good post about it on the DEV forum.  If you’re just looking for information on layout, skip to the Resources section, where there are links to a tutorial on layout, as well as tutorials specifically on Flexbox and Grid.

Starting Off

The fist task I set myself was to just make a nice looking but not very complicated page.  The result looked like this:

A web page with rounded corners and drop shadows

The bottom half of a web page with rounded corners and drop shadows 

A few techniques that were used on this page are: rounded corners, drop shadows, and small caps text.  I’ll give more details on those in the next sections.

Rounded Corners

Rounded corners are accomplished with the border-radius CSS rule.  In this case, it was:

border-radius: 20px;

This property can be set for each corner individually.  You can see a few elements in Twitter’s UI that use this property: 

A screenshot of Twitter's web interface, showing its use of elements with rounded corners

  • The media preview in the tweet
  • The “What’s happening?” text field where you type tweets
  • The rounded left and right sides on the Tweet button
  • The circular user icons

If you’re using Firefox you can find out how rounded corners or some other effect is achieved by right-clicking the button or other rounded-cornered element, and clicking “Inspect Element”.  (Other browsers have similar capabilities.)  If you do that for a user icon in Twitter, you’ll see something like the following:

.avatar {
width: 48px;
height: 48px;
border-radius: 50%;

Note that sometimes percent is used and sometimes px.  A border-radius of 50% makes the element circular.

This MDN page has more info on border-radius.

Drop Shadows

Drop shadows are made using something like:

filter: drop-shadow(3px 3px 3px black);

I’m not sure why it’s a “filter”, but here are the MDN pages for the filter property and the drop-shadow filter value.  There’s also a property called box-shadow, which might just be the same thing without having to use it as a filter.

Small Caps and Uppercase

A couple things you can do with text using CSS are to uppercase it, using the text-transform property:

h1, h2 {
font-family: monospace;
text-transform: uppercase;

…or to make it into small caps using the font-variant property:

a {
text-decoration: none;
font-variant: small-caps;

There’s a tutorial on text styling linked from the Resources section.


CSS is good for styling, but what about layout?  CSS has a couple relatively new features called Flexbox and Grid, which might finally allow it to catch up with the ease of use of using HTML tables for layout.  Tables are supposed to be bad for accessibility reasons, because screen readers interpret them as “tabular data” (i.e. the stuff you’d put in a spreadsheet), and so you shouldn’t use them for layout (I *guess*).  This could have been solved easily if the HTML standard had added a single property telling screen readers to just treat it as a layout table instead of a data table.  Something like:

<table semantic="false">
  <!-- and so forth -->

<!-- the semantic="false" property
doesn't actually work because
they don't want you to be happy -->

But I digress.  Flexbox and Grid exist now.  I didn’t delve into them this week.  Which is probably why things got so frustrating after I started focusing on layout without them.

One thing I spent a lot of time trying to replicate was Facebook’s layout, which has a navbar on the top and another one on the side that stay in place as you scroll. 

The top navbar that stays in place is accomplished using position: fixed.

header {
position: fixed;

/* position: fixed has
to be used with one of:
top, bottom, left, right */
top: 0;

width: 100%;
margin: 0;
padding: 1rem;
height: 1rem;

But this will obscure some content.  So the solution is to make a div that’s the same size, *without* position: fixed, to go above the content.

.fakeHeader {
height: 1rem;
margin-top: 0;
padding: 1rem;

Adding a sidebar was more difficult, and I never got it right without it screwing up the content area in some way or another.  there are recipes for adding sidebars (one of which is linked in the Resources section), but they tend to assume the lack of a top navbar.  Whenever I got the sidebar looking right, the content area would always get messed up in some way.  I’m guessing the solution would be to just use Flexbox to define the horizontal areas of the hidden fake topnav and the area below it, and then use another Flexbox to partition the area below the topnav into sidebar and content areas. 

Resources and Further Reading

Here are some links that I came across in my studies.


That’s it for this week.  Next week(ish) I’m going to be looking at the inner workings of WordPress themes, which should let me apply some of this CSS knowledge to give this blog a custom look.   If you want to be notified of future posts, you can subscribe to the Tech of the Week tag via RSS, subscribe to my whole blog via RSS, or follow me on Twitter.  The latter two might contain stuff that’s not tech-related.  In fact, the Twitter one definitely does.  Or you can just sit and refresh the blog for a week (probably more like two, tbh) until a new post appears.  See you then.

Update: The weekly schedule is no longer a thing, and I haven’t, as of 10/12/2021, done a post on WordPress themes. See here for details.

Introducing “Tech of the Week”

Each week, starting this coming week, I’m going to delve into a different thing related to the Web or software development. I’ll write about the experience of using or learning it, and provide links where you can learn more. Often it will be a programming language or framework. Sometimes it might be a technique (such as refactoring) or methodology.

The first one will be CSS. Hopefully a week or so of studying and playing with it will improve my own CSS skills. In any case, we’ll know in a week. Look for that here around the 1st of February. If you want to be notified, you can follow me on Twitter.

Update: Yeah, this weekly thing didn’t happen. I was intending the “tech of the week” thing to be a side project, but it turned out the first (and only) Tech of the Week took me about.. a week. I’ve written some other technical blog posts since then, but they likely won’t be on a regular schedule or to have a catchy series title anymore, unless I can figure out a way to make a living off it.

WordPress is More Complicated Than I Remember

I’m finally setting my blog back up, after some issues with my old web host.  My not wanting to bother with those issues has caused it to be down for a while.  I’m using WordPress again, this time with Bluehost (which I’ve been mistakenly calling Blue Origin, which is a rocket company instead of a web host).

Bluehost was recommended as a host that wouldn’t require one to set things up oneself at the command line, and the process of setting up has been fairly painless.  I had a question about backups, which their onboarding support was able to answer (backups are part of the “prime” tier, but can be added to the lower tiers).  Blue host conveniently lets you chat on the website with a support person.  The backup tool is called CodeGuard.  I was about to say, “Note to self: set up backups”, but they’ve apparently already been running.

One confusing issue I ran into was with pointing my existing domain at the new host.  (This is something support could have helped me with, if I’d gone back to the chat, but I was stubborn.)  The default seemed to be to either register a new domain, or transfer registration of an existing domain to Bluehost.  I’m happy with the domain being registered at Namecheap, and it wasn’t immediately clear what I should choose for that.  It ended up being something like “add domain in cPanel”.  cPanel, I have learned, is some software for managing shared hosting.  Perhaps it’s the core around which something like Bluehost is built.

If I were registering the domain through Bluehost, I probably wouldn’t have had to think about cPanel though.

As far as WordPress itself, there seem to be a lot more settings one has to fiddle with, in order to make a site that’s (relatively) aesthetically pleasing.  Switching to one of the default themes helped though.  I initially tried OnePress, because, at least in one configuration, had a nice minimalist feel.  But when I couldn’t get it to stop advertising for itself on my home page, I gave up and went with the built in Twenty-Seventeen theme.  A bit of fiddling later, and I’m mostly happy with the site, though I think the header image is too big.  A lot of the clutter of settings goes away, though, when you add and log in as a non-admin user.

The reason I’m thinking and writing about this is because my dad had asked me for advice about making a website.  Short of coding everything from scratch, the main contenders were WordPress (plus some shared hosting provider) and Wix.  So I’m thinking about the usability of this process, not just for me, but for someone not familiar with web development.

So I wonder how obvious the seams are between the various pieces of software to someone not trained in the LAMP stack: Bluehost’s (or some other shared host’s) proprietary interface for managing the overall site or sites; one or more instances of WordPress; cPanel; the various other tools like phpMyAdmin under Bluehost’s “Advanced” tab.

I’m still waiting for the domain name itself to realize it’s not at my old host.  Domain changes are expected to take a while to propagate, though I don’t know why it should take so long.  (Chrome sees the right host though, hm..)  Once that takes effect, I will once again have a website, like a proper citizen of the web.