Friday, August 29, 2014

I'm Not Spam

Open mail relays are no joke. Except when I joke about them in my comics. But seriously, you have to be careful if you run a server to prevent spammers or botnets from using you to spam people.

When I installed postfix it listened publicly on port 25 by default. While it probably wouldn't let anyone log in to send an email, that still seemed a bit dangerous to me. I don't need somebody running a port scanner on me, seeing that I run a mail server, and then attacking me constantly trying to break in.

I had to change the following line in /etc/postfix/
inet_interfaces =
Then I had to restart postfix.

# /etc/init.d/postfix restart

It was the last line in my file. It just tells postfix to listen only on localhost and not on my public IP address. You can't connect to my server on port 25 from anywhere but my server.

SPAM and Eggs
Spam, spam, spam, spam, spam, spam, and eggs.
Alas, it did me no good. For some reason, is still listed as a spam site on Barracuda Central. Unfortunately, this means that many networks will block access to my comics because they assume that I am some kind of villain.

You can tell if your site is in some "bad" category by using their lookup tool. You can even request to change your category if it is wrong. I did this, but haven't gotten any response yet.

They have other informative sections of their site, where you can look at global spam statistics in graphs. The United States sends quite a bit more spam than the rest of the world, and penny stocks are an extremely popular spam subject.

While I certainly don't appreciate being labeled a spammer, the history of why unwanted email is called spam is quite interesting. It really traces back to a Monty Python sketch from 1970. In it, a couple is in a diner where every item on the menu contains Spam. The word spam is used so many times in the sketch, and Monty Python sketches are so popular among early adopters of the Internet, that people used to annoy each other in things like Usenet groups and MUDs by typing the word spam repeatedly. Just like in the sketch, the overuse of the word made it undesirable and over time the word became associated with unwanted stuff on your network, primarily email. comic for August 29, 2014

Wednesday, August 27, 2014

Try a Local Fair

Many years ago, like in maybe 2002 or something, I made a website for the local fair in Juniata County, Pennsylvania. Not a big deal, right? Well, this one was different.

At many fairs, people enter all kinds of contests for stuff they grow in their gardens. Biggest pumpkin. Best sweet corn. Reddest tomatoes. Chocolate chip cookies, cakes, maple syrup, a million other things too. In the last century, they had to fill out paper entry forms for all these things and mail them in.

My website was revolutionary in that it allowed online entry of all these things. And not just some form that looked like the paper form where you had to type in all the contest names - it was backed by a complex database of thousands of individual item classes that could be entered in competition. You could browse the list of available contest classes, click on the ones you wanted to enter, fill out your name and contact information, and when you showed up with your pickles they had a label all printed out and ready for you.

Vegetable contest entries at the Juniata County Fair
The system also printed forms for the judges to use, allowed the judges to enter the winners, and then printed all the checks for the contestants. It saved hundreds of hours of labor for the fair employees.

It was back around the turn of the century, so the technology used was pretty awful by today's standards. It was ASP and Microsoft SQL Server. Yeah, it makes me kinda sick now.

I did it all for free. I always liked the fair. I don't really know why. I grew up in a rural area and went to the fair every year, but I was never interested in farming or even gardening. I was very interested in funnel cakes, however.

Don't worry, I re-wrote the whole system twice since then. It's Java now, with ReST web services, and uses MySQL as the backend database. I might re-do it again someday, when the present technology becomes un-maintainable.

Why am I talking about this today? Well, this time of year is fair season. The Juniata County Fair starts this weekend. Other nearby fairs have either just completed or start soon too. If you are reading this from anywhere in the United States, odds are that there will be a fair somewhat close to you around this time of year. You should totally go.

Funnel cakes are awesome. comic for August 27, 2014

Monday, August 25, 2014

PhantomJS for Image Capture

I don't know if you've ever noticed, but when you share one of my comics on Twitter, Facebook, or Pinterest (hint: you should be sharing my comic) it is able to grab a preview of the comic that is a static image. This meta tags on the page tell the social media sites which image to use:

<meta property="og:image" content="">

Dynamically Created Image of a Comic
Since the comics are actually SVGs positioned inside <div> tags, it is necessary to create these images somehow. Early on, I created some manually but I really needed an automated method that was integrated with the online editor. Every time I create or update a comic it needs to automatically generate new images.

The solution I came up with was integrating PhantomJS with my web application. You can do a lot of cool stuff with PhantomJS, like automated testing of web applications, but I am just using it for its image capture capabilities right now.

To make an image out of a web page, you just need to make a JavaScript file to control PhantomJS. Here is an example similar to what I use.

var page = require('webpage').create();
page.viewportSize = { width: 1202, height: 5000 };'', function() {
    page.clipRect = page.evaluate(function() {
        var areaRect = document.getElementById('comicArea').getBoundingClientRect();
        var cellRect = document.getElementById('cell-0').getBoundingClientRect();
        var r = JSON.parse(JSON.stringify(areaRect));
        r.height += ( * 2); = 0;
        r.left = cellRect.left - 102;
        r.right = cellRect.right + 102;
        r.width = cellRect.width + 204;
        return r;

Line 1 is just pretty standard, to make a page object. You can read more about the different modules in the PhantomJS documentation.

One line 2, I set the viewport size. This controls how wide my "virtual" web browser client will be. Remember that my comics resize themselves based on the client, so I want to use the maximum size in order to make the rendered image very large. The social media sites almost always shrink the images down and starting with the largest size will result in the cleanest picture in the end.

Line 3 is where I make the call to open the page. When the page is opened, the callback function is called. That's where the good stuff happens.

When you want to make an image out of a web page you can either do the whole thing or just part of the page. If you want to do a partial page, you need to set the clipRect property of the page object before calling render. That's what I'm doing starting at line 4, with the call to page.evaluate.

When calling evaluate on the page object, you pass in a function that should be executed as JavaScript in the context of the page. It would be the same as if the page had JavaScript in a script block.

To make the clip rectangle that I want, I need to get the dimensions of 2 parts of the page - the comicArea div and the div of the first cell. I get those on lines 5 and 6, by simply getting the elements by id and then calling their getBoundingClientRect methods. However, the rectangle I want to return is neither of these exactly. I need to make some adjustments.

On line 7 I do something that you might find a bit strange. I create a new variable r by parsing the JSON returned from a stringifying the areaRect object. Why am I doing this? Because the rectangles returned from the calls to getBoundingClientRect are immutable and I want to make changes. By dumping the object to a JSON string and then reading that string into a new object, I get a mutable copy.

Lines 8 through 12 are the adjustments to the clip rectangle I want to return. I change the top and height of the rectangle around the comicArea and then adjust the left, right, and width values using the corresponding values from the cell as a starting point.

Try it yourself and you should get an image of a single cell of one of my comics. Or change some stuff around and try getting images of other pages.

The complete solution in my web application is slightly more complicated, because I have to dynamically generate the JavaScript for PhantomJS, call PhantomJS from Node, read the resulting image into the database, and then cleanup the temporary files. But we can talk about that some other time... comic for August 25, 2014

Friday, August 22, 2014

Eggs from Real Chickens

I like to get my eggs from real chickens. I tell my kids that the eggs in the grocery store come from fake chickens. Like robot chickens or something.

The Chicken Machine
When I was a kid, there was this thing in the grocery store that was full of plastic eggs and had a plastic chicken inside, sitting on top of the eggs. If you put in a quarter, the chicken would move a little and one of the eggs would fall out. Inside was some silly toy prize. That's pretty much what I think of when I think about eggs and the grocery store.

But seriously, I think eggs from chickens that I know taste better than eggs from chickens that I do not know. I am fortunate (well, sometimes I think so) to live in a rural area where I can get to know the chickens that give me eggs. Sometimes I can even feed the chickens that feed me.

Why do these eggs taste better? I don't know. Maybe it's because they are fresher. Maybe it's because of the chicken's diet. These "free range" chickens tend to eat whatever bugs they can peck up out of the yard. Maybe it's the breed of chicken, or the color of the egg shells. The eggs I buy locally are generally brown. I've also noticed that the eggs from local chickens are harder to crack. Tougher shells I guess.

I feed them, they feed me
Once I determined that real eggs from real chickens were better than factory-made eggs, I started to wonder about other agricultural products.

While it is generally accepted that we have access to better nutrition today than we did 100 years ago, that may be true only in the variety of foods to which we have access. There is quite a bit of evidence that individual foods are becoming less nutritious over time. This is most likely due to modern crops being genetically modified for better yields. Bigger crops, but still the same amount of nutrients to go around.

Have a look at these articles, though, and see what you think.

Industrially Farmed Foods Have Lower Nutritional Content

Dirt Poor: Have Fruits and Vegetables Become Less Nutritious?

Breeding the Nutrition Out of Our Food comic for August 22, 2014

Wednesday, August 20, 2014

An Interview Question

Here's a great interview question, well, assuming that you're interviewing a software engineer.

What is the easiest way to determine if a given string is the rotation of another string?

First, I guess there could be some question of what rotation of a string even is. If you were to take letters off the end of the string and stick them on the beginning one at a time, you would be making a rotation of the string. For example, the following are all rotations of the string "obfuscate"
There are lots of ways you could try to determine if one string is a rotation of another. You could easily rule out some strings by checking to make sure the lengths are the same first. Then you could maybe start at the first character and attempt a character-by-character comparison and move your pointer back to the front if you reach the end...or some other complicated algorithm.

But there's a really easy way to tell if a string is a rotation of another string. Engineers who value the simplest way to solve a problem will do it this way.

Stick one of the strings together with a copy of itself and then see if the other string appears inside that new string. Like this, using the examples above:
You still need a sanity check for length, because you can find "ob" inside of "fuscateobfuscateob" but that doesn't make it a rotation.

Here it is as a JavaScript function.

function isRotation(a, b) {
    return (a.length === b.length) && ((a + a).indexOf(b) !== -1)

That's all there is to it. You can do it in one line. I think it's a good question. comic for August 20, 2014

Monday, August 18, 2014

CSS Speech Bubbles

I'd like to take a moment to talk about my CSS speech bubbles. My comic uses CSS rules for making <p> tags render as speech bubbles for the frogs. If you view the page source, you'll see that there are no tables or images involved - just pure CSS.

There are many examples out there on the Internet about how to achieve this effect. I based mine on this one: Bubbler - CSS Speech Bubble Generator. Of all the examples I saw, I just like this one the best.

There was one minor issue I came across though. The frogs are in different places in each cell, but because the bubble stems are positioned using the CSS psuedo-elements ::before and ::after, the values that determine the stem location cannot be set via JavaScript.

I decided to solve this issue by removing the stem position from the .bubble::before and .bubble::after classes in the CSS, and creating another set of classes just for the stem positions.

I called these new classes bubble25, bubble50, and bubble75. You can probably guess that the number represents the percentage used for the left attribute in the stem position.

So now, if I want to create a speech bubble with the stem on the right side (75% of the bubble width) I create a tag like this:

<p class="bubble bubble75">here is some content for the bubble</p>

While I can't position the stems at every possible position this way, having the 3 different options has proven sufficient so far. This is a good example of a design trade-off. I could create a hundred different classes and never use 90% of them, or I could create just 3 and get a good-enough position 99% of the time.

Here's a snippet of my CSS that shows more clearly what I'm talking about.

.bubble {
 position: absolute;
 width: 44%;
 padding: 2%;
 text-align: center;
 background: #FFFFFF;
 border: #000000 solid 3px;
 font-family: 'Sniglet', sans-serif;
 line-height: initial;
 color: #000000;
 box-sizing: content-box;
 -moz-box-sizing: content-box;
 -webkit-box-sizing: content-box;
 -webkit-border-radius: 20px;
 -moz-border-radius: 20px;
 border-radius: 20px;

.bubble:after {
 content: '';
 position: absolute;
 border-style: solid;
 border-color: #FFFFFF transparent;
 display: none;
 width: 0;
 z-index: 1;
 box-sizing: initial;
 -moz-box-sizing: initial;
 -webkit-box-sizing: initial;
 border-width: 21px 7px 0;
 bottom: -21px;
 margin-left: -12px;

.bubble:before {
 content: '';
 position: absolute;
 border-style: solid;
 border-color: #000000 transparent;
 display: none;
 width: 0;
 z-index: 0;
 box-sizing: initial;
 -moz-box-sizing: initial;
 -webkit-box-sizing: initial;
 border-width: 25px 9px 0;
 bottom: -27px;
 margin-left: -14px;

.bubble25:after {
 left: 25%;
 display: block;

.bubble25:before {
 left: 25%;
 display: block;

.bubble50:after {
 display: block;
 left: 50%;

.bubble50:before {
 display: block;
 left: 50%;

.bubble75:after {
 display: block;
 left: 75%;

.bubble75:before {
 display: block;
 left: 75%;

Make sure you check out John Clifford's Bubbler to make your own bubble CSS. Speech bubbles can be a welcome addition to any web site. comic for August 18, 2014

Friday, August 15, 2014

Bad Requirements Create Bad Systems

One of the most important lessons I've learned as a software engineer is the importance of good requirements. Requirements definition is absolutely the most important part of a software development project. You can have the best engineers in the world but if your requirements are terrible your system will be terrible.

You might find other lists on the Internet of things that make requirements "good." I'm going to give you a list of what makes requirements bad.

Don't ever write requirements like these...

  • Bad Requirements are Ambiguous. If you tell me, "The system should be able to be dropped from a height of 8 feet onto a wooden floor without breaking it," I will build you a system that will not break the floor when it falls onto it. The system, however, will shatter into a thousand pieces.
  • Bad Requirements are Subjective. If you tell me, "The system should use pretty colors," I am not going to build your system. If you tell me, "The system should be fast," I am not going to build your system.
  • Bad Requirements are not Testable. If you tell me, "The system should never crash on a Saturday afternoon," I'm going to ask you to watch it every Saturday to make sure it doesn't.

All of the examples I just gave could be rewritten to be good requirements. It just takes a little more thought.
  • The system shall not break when dropped from a height of 8 or more feet onto a surface with a Brinell hardness of 1.6 HBS 10/100 or greater.
  • The system should use only pink and purple as display colors for text.
  • The system should process inputs at a rate of 3 per second.
  • The system should have a mean time between failures of 87 days.
Remember, you can't build the right system from the wrong requirements. comic for August 15, 2014

Wednesday, August 13, 2014

Just How Mobile?

I'm sure you're familiar with the Native App vs. HTML5 debate. There's a lot to be said in that one. As a developer, I would much rather make a single HTML5 web app instead of native apps for both iOS and Android.

However, I know that such a course of action would very likely be a bad idea. Don't get me wrong, I love HTML5. But I've seen very few cases where an HTML5 solution is on par with a native app solution. There are some exceptions, and it very much depends on what your particular app is doing.

But HTML5 is getting better. I fully except that someday mobile apps will do things in mobile browsers that are quite impressive. That day is not today.

One place in particular that shows the mobile web's shortcomings is in games. There are many examples of good HTML5 games that are playable on the desktop browsers, but I have trouble finding good ones that play well on mobile browsers. Even very simple games just get awkward on mobile devices.

So if you decide to go native, you'll have to make apps for both iOS and Android. But what if you decide that your particular app is simple enough that you can do it in HTML5? You might want to consider making two web apps, one for desktop and one for mobile. Oh, "that's silly" you say. Everyone knows that you just use responsive design and make one web app.

Or do you?

I read this article, in which the author makes the case that your mobile use cases might be so different from your desktop use cases that it makes more sense to make a second web application just for mobile users. I'm certainly a big fan of understanding your requirements (and use cases) well before developing your software.

Here's the article. Give it a read.

Is responsive design killing mobile?

Think about it. comic for August 13, 2014

Monday, August 11, 2014

Gopher, Anyone?

Does anyone remember Gopher?


I guess probably not. There used to be this document access protocol called Gopher that people used instead of the Hypertext Transfer Protocol (a.k.a. HTTP) that we know and love today. Well, maybe "instead of" isn't the right phrase. We used it alongside HTTP. It was like this other kind of thing you could do with the Internet. It's mostly forgotten now. We almost exclusively use HTTP to access documents over the Internet these days.

It's worth talking about because we often think of the Web (the entity created by HTTP) and the Internet as the same thing. The proliferation of ReST Web Services today as a means of opening up almost any kind of service imaginable over HTTP is increasing the confusion. Maybe because I used to do networking over a 2400 bps modem on a Tandy 1000 computer I don't see the Internet that way.

Tandy1000HX tweaked.jpg
"Tandy 1000HX tweaked"
The Internet is a wide area network of computers and computer-like devices using TCP/IP. HTTP is a protocol on top of that, just like Gopher, FTP, SSH, and a thousand others. Maybe someday HTTP won't be so dominant and some other protocol will find more widespread appeal.

And here's another thought. No one probably remembers using Lynx either. I used that to browse the web in college. Good times. Good times...

I sure could gopher a giant chocolate chip cookie right now. comic for August 11, 2014

Image Credits:
"Tandy1000HX tweaked" by Jesster79 (Non-creditable tweaks by Ubcule) - This file was derived from: Tandy1000HX.jpg . Licensed under CC BY-SA 3.0 via Wikimedia Commons.

Friday, August 8, 2014

JavaScript Colors: RGB to HEX

In my web comic editor, I needed to be able to both read and set the background color values on HTML elements. Since I've been working with colors as hex codes since, umm, forever, I thought it would be good to work with the colors that way. But to my dismay, I found that you can set the color of an element using a hex string (e.g. #ff0000), an RGB string (e.g. rgb(255,0,0)), or an HSL string (e.g. hsl(360,100%,50%)) but when you read the value it is always in RGB format. Here's an example:

<!doctype html>

<html lang="en">
  <meta charset="utf-8">
  <title>Color Test</title>


<div id="hextest" style="background-color: #FF0000; width: 100px; height: 100px;"></div>

<div id="rgbtest" style="background-color: rgb(0,255,0); width: 100px; height: 100px;"></div>

<div id="hsltest" style="background-color: hsl(240,100%,50%); width: 100px; height: 100px;"></div>



var a = document.getElementById('hextest').style.backgroundColor;
var b = document.getElementById('rgbtest').style.backgroundColor;
var c = document.getElementById('hsltest').style.backgroundColor;




Maybe you've had this problem yourself. Fortunately, I found a handy JavaScript function that will convert RGB strings back into hex. It is a very compact and interesting function. Let's take a look at it.

function rgb2hex(rgb) {
 function hex(x) {
  return ("0" + parseInt(x).toString(16)).slice(-2);
 var r = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
 var h = '#' + hex(r[1]) + hex(r[2]) + hex(r[3]);
 return h.toUpperCase();


There's a lot of good JavaScript to learn about here. First, you may notice that the function has another function declared inside of it. The inner function, hex, is essentially private to the rgb2hex function the same as a variable definition would be. It's like an anonymous inner function that isn't so anonymous. So what's the hex function doing?

It assumes, first of all, that you give it a String representation of an integer. Like "35" or something. The parseInt() function turns that into a JavaScript Number so that you can call toString(16) on it. What does that do, you ask? The parameter "16" is the radix. Without going into too much detail about what a radix is, just know that using radix 2 will give you the number's value in binary, radix 8 will give you the number's value in octal, and radix 16 gives you the number's value in hexadecimal. So calling toString(16) on a Number object with value 28 will give you the String "1c". But what's up with the fact that "0" is prepended to that value and then the whole thing is sliced with a slice parameter of -2? That's just a simple way of making sure that the return value of the function is always 2 characters and is left-padded with zeros. If the input to the function is less than 16, then the toString(16) call will return a String that is only 1 character long. But if the input is greater than 15, the result will be at least 2 characters. So if we always put a "0" in front and then just return the last two characters of the String, it will ensure that the hex value is 0-padded. Using slice() with a negative value as the parameter is a trick that returns the characters from the end of the String instead of the beginning. For example, "whatnot".slice(-3) returns "not".

Wow, that's quite a lot going on in that one line of JavaScript. The rest of the rgb2hex function isn't quite so tricky. Line 6 just uses a regular expression to find the 3 numbers in the RGB string. For example, with an input String of "rgb(100,200,150)" the value for r will be an array containing the values "100", "200", and "150". Line 7 just uses those three values as parameters to the hex function and sticks the "#" on the front. The last line returns the complete value in all upper case, since I like #FFFFFF better than #ffffff.

Anyway, I hope this function is as helpful to you as it was to me. comic for August 8, 2014

Wednesday, August 6, 2014

Your Uncoolness is the Coolest

When I was in fourth grade, my vision had deteriorated to the point that I could not adequately function in the classroom. I suppose it was inevitable, since heredity had given me a disadvantage in the vision department. But when I was 10 years old, having to wear glasses was terrible. They weren't anything close to the stylish frames I own today. They were thick, dark, plastic frames that looked terrible by today's standards. Or did they...

Now that we've delved into the pain of my childhood, let's talk about my oldest daughter. At 14, she still had perfect vision but chose to start wearing fake glasses just for the look. And by look, of course I mean style. I wear glasses for the look too, but in my case that means so that I can actually look at things (I can't wear contacts or have eye surgery, but that's a story for another day). Back to my daughter...the glasses she wants to wear are the dark plastic type frames that I once despised. What happened?

I won't embarrass her by posting her picture here, but she actually looks really good in the glasses. She even had her school picture taken wearing them last year. When I was her age, all I dreamed about was being able to get my picture taken without my glasses.

Here's my theory on what has happened. Somehow, over time, kids bought the notion that being yourself and being different were good things. It's cool to be different. So now, everyone wants to be different. And they become more different by assuming the qualities that have historically caused other children to be labeled as different. It's some kind of coolness paradox. If you do the things that make you uncool you achieve coolness.

Personally, I think they're missing the point. You should be the best you that you can be. If you wear glasses, great. Pick some that work with your face and be confident and happy. If you love to write software and be alone in your room, great. Write some really fun apps for phones. If you love to play soccer, great. Go run and kick things and whatnot. Maybe you don't know what makes you different and special yet. That's fine too, just try different activities and you'll discover something. Don't rule anything out, unless of course it's illegal and/or dangerous. I found that same year I got my glasses that I loved programming. I did it every day from then on and didn't really care if I was cool or not.

Remember, you are different enough just by the fact that you are yourself. Unless, of course, you are an identical twin. comic for August 6, 2014

Monday, August 4, 2014

Presentation Matters

How much does presentation matter to you? I'm not talking about PowerPoint. Do you favor style over substance? I think about this sometimes. I think about it when I consider if I should buy new wheels (style) or a new throttle body (substance) for my Mustang. I think about it when I consider if I should spend my time building a better user interface or adding more features.

I also think about this: years ago, and I can't find the source anymore, I read an interesting article about the qualities different socioeconomic classes in America look for in food. There are apparently big differences.

According to my long-forgotten source, the primary concern among the lower class is the quantity of food. They want to make sure that there is enough. Enough for themselves and enough to feed their family. If you go to their house for dinner, they'll ask you if you got enough to eat. I suppose this makes sense.

The middle class will be concerned with how the food tastes. They probably feel secure that they'll have enough to eat, so they want it to taste good. If they make dinner for you, they'll probably ask you how you like the taste. Flavor is important. Sure, I get that. I like my food to taste good. Who doesn't?

Bica do Sapato, Lisbon
Now, the upper class. As this theory goes, they are most concerned with how the food is presented. If you are upper class, maybe you understand this. If you are a simple country boy from the hills of Appalachia like I am, you probably don't get it. I've been to some fancy restaurants (see below), where I think upper class people must like to eat because the food is very nicely displayed on the plate. I almost feel bad eating it sometimes because I think somebody in the kitchen must have put a lot of work into making the dish look so nice. The portion size is small (only lower class people care about portion size) and the flavor is just alright (only middle class people care about taste) but the presentation is excellent. And it costs a lot.

Now you'll think about this sometimes, too. Like the next time you order a pizza.

Some Fancy Restaurants at which I have Dined

  • I proposed to my wife at the Valley Green Inn in Philadelphia. Excellent presentation. Try the duck.
  • Morton's The Steakhouse in San Francisco. It's a chain, I guess. But a very classy chain. I was under-dressed.
  • The Monet Room at the William Penn Inn in Gwynedd was very nice. I was not under-dressed.
  • We ate at the California Grill at Disney World once. We did it just to get the good view for the Magic Kingdom fireworks, but they have a Warm Octopus Salad. How fancy is that? comic for August 4, 2014

Food Image Credits:

"Bica do Sapato, Lisbon (5834390296)" by Ewan Munro from London, UK - Bica do Sapato, Lisbon. Uploaded by tm. Licensed under CC BY-SA 2.0 via Wikimedia Commons.

Friday, August 1, 2014

Web Comic Launch

Today I launch my web comic, which I have so creatively titled I've wanted to do a new comic for years, but I was waiting for the right inspiration. Unless you live in central Pennsylvania (inside this area) you probably don't know that 20 years ago I published a comic about a frog in a local newspaper. But newspapers are dead. Print comics are dead. My comic was barely alive in the first place. What's changed?

Web Comics Made with 100% Real Web

If you've ever read a comic on your computer or mobile device, you may have noticed that most of them are pretty much just print comics converted to a JPG or PNG and stuck on a web page. Most are probably made by talented artists, working with some artisty tools, maybe on a computer but maybe still on paper. Anyone who's ever seen my work knows that I am not a talented artist. But I am pretty good with the technologies that make web sites. And I have been drawing a frog so for so long that it looks like an almost legitimate frog.

So I decided to make a web comic that was made with real web technologies. I designed it to be optimized for mobile devices, and easy to share with your friends. Here's a breakdown of what it's made of.

  • HTML + CSS: The comics are just HTML markup and CSS styling. I didn't draw a box and then draw some stuff in it, I style a <div> to have a border and then position individual <img> objects in it to arrange the scene. Even the speech balloons are just <p> tags with appropriate styles applied.
  • SVG: I take responsive design to a whole other level. When you look at the comics on your phone, not only is the page header and navigation being restyled to fit the smaller screen, the comic images themselves are scaled down as well. Have a retina display? Make the page as big as you want, my frogs won't get all pixelated on you. And with Gzip compression, most of the images are actually smaller downloads than a large PNG would be.
  • JavaScript: In both the client and server, I'm using JavaScript to make things better. In your browser, jQuery lets me animate parts of the comic scenes easily. I can make things change when you click (or touch) the characters in the cells. These can be part of the jokes or just for silly fun. The back-end is made with Node and the Express framework. The part that you don't see is an editor that combines client-side goodness with REST web services to enable me to take the ideas from my head and put them in the comics with ease.
Comic Editor

Why Would Someone Do This?

Are there advantages to this approach to a web comic? I think there are many.

First of all, using web technologies to make web comics really uses the medium to its fullest. Web sites don't just look like newspaper pages (at least not anymore) so why should comics? We have all these great features in our web browsers and we are barely using them. I want to use them just a little bit more.

Second, comics made this way are mobile-friendly. They read top-to-bottom and don't require you to pinch or swipe or poke or jab or jump or anything. If you've ever tried to read a "normal" comic on a mobile phone in it's typical configuration - a.k.a. portrait mode - you know what I'm taking about. My comics scale to your device without giving up image quality and can be enjoyed with just your thumb on the phone.

Also, the HTML content can be read by the crawlers and even translators. Like I mentioned, the words my frogs speak are just normal HTML paragraph tags. The contents of my comics will be picked up by the search engine spiders and make it easier to find my stuff. And a tool like Google Translate can show you the comics in lots of other languages. I'm not sure if the jokes make sense in Swedish, but the words might!

Take My Code, Please

As always, my code is open source and on GitHub. My frogs and jokes aren't, but you probably don't want them anyway. comic for August 1, 2014