Wednesday, November 18, 2015

Making Punch Cards Better

Nearly three years ago I started thinking about Punch Cards.

Well, I'd been annoyed by them for decades; actually. I'd often lamented about how it seemed everyone conspired to put a card in my pocket. For a few years now I have had an absolute policy of not enrolling in any loyalty or rewards programs or carrying any of their cards.  I'd decided it was time to rebel; someone had to tell them enough is enough.  Ridiculous; I know, but I like to get carried away.

Of course; passion of any kind can be a big wind behind anyone's back and over the years, partly to amuse myself, I came up with a few schemes and ideas that would allow businesses to enroll me in a loyalty program without making me carry yet another card around.

The problems with most of my ideas were either related to expense (e.g. custom hardware) or adoption (e.g. making customers install special software on their phone or training them to do some special action like scan a bar code). Although I didn't know it at the time; my focus on reducing the number of cards in my pocket was causing me to not give enough attention to the needs of businesses.  I was ignoring the fact that businesses really like giving something to their customers that they'll carry around has their branding and message and that encourages them to come back. I was mainly thinking about what worked for me, as a customer, and not spending enough time thinking about what worked for the business (or even other customers).

Mobile software has similar advantages for businesses that cards do; and more (and less).  A mobile application can, for instance, give a customer a timely alert that draws the customer back into making further purchases. But it's also true that there are significant number of customers who are reticent about installing and using mobile applications (and scanning QR Codes or using NFC, etc). For instance; Punchd said this about one of their showcase customers:
SLO Donut Company currently uses an equal amount of paper and virtual punch cards. Pickering said the only drawback is that “there are anti-smartphone people out there who can’t use the app.”
Some people just aren't that technology savvy or comfortable.  It often takes time for people to adopt new technologies.

And then I realized there was a very simple solution to the issue of cards vs. mobile: do both.

By both I mean that businesses could hand out cards; identical to the cards they always have; except that the cards would have a QR Code on them and instead of being punched with a hole puncher or dabber; they would be punched with a mobile device (QR Code scanner).

And... technology savvy customers could scan their own card and convert to using a web/mobile app if they choose to.

By having the employee do the punching, it means the customer has the option to use their punch card, just like they always have, if they want to (i.e. the customer hands their card to an employee, who punches it and hands it back).  But by providing the customer a path to switching to a mobile version of the card; everyone is happy.

But that's still not enough

I have no idea who it was who said a product has to be ten times better than the existing product before it can be called an Innovation.  But making cards that you could punch with a mobile device instead of with a hole puncher, by itself, doesn't make a punch card better.  In fact; it makes it worse because at least the traditional punch card has holes or stamps that you can count.  There are some small benefits with "registered cards" for customers (e.g. customers can lose a card and not lose their punches) but the biggest single benefit is to the technology savvy customer; who doesn't have to carry another card around.

Of course; the obvious feature for businesses is logging and reporting.  One of the two biggest issues with the traditional punch card is that they largely go untracked.  Possibly businesses are keeping track of redemptions, but probably not in a very detailed way.  For the most part the cards just sort of go out into the wild and the business has no idea how many are being used, or how.  However, with a connection to a central database you get to track all kinds of information automatically, or with small interface changes, that will let businesses all kinds of new information.  And if you have employees stamping cards (or phones) with a mobile device you can use some of that data in real time (e.g. tell the employee the customer's name or vice versa).

The second big issue with traditional loyalty card systems is that they only reward one kind of customer behaviour:  The "Buy 10 Get One Free" behaviour.  But actually there are many behaviours that businesses would also like to encourage in their customers.  And here is the real innovation: an application can be used to reward several kinds of behaviours impossible with traditional punch card systems.

Encouraging Other Customer Behaviours

There are many other behaviours that can be rewarded with an application that can better track and respond to customer behaviour.  Many behaviours can be implemented in a way that requires no extra effort by the business, and others by adding small simple additions to a punching interface.

For instance; an application can know if the customer been back several days in a row,
if the customer brought new customers (friends/family), shared the a special or event on Facebook, etc (or reviewed on Yelp, etc) or returned to the business after a long absence.

Web/Mobile applications can easily provide custom customer behaviours to businesses though an API.  Which would enable businesses to, say, give customers punches for making purchases online, or through their POS.  Or provide rewards the same way.

There's actually no end to where you can go; with a little imagination

Businesses can offer rewards behalf of other businesses. 

If your business is one which it doesn't really have anything it can give away for free (e.g. a Realtor); that business could make a group purchase-priced deal to buy rewards offered by another business as a reward to their own customers for exhibiting the desired behaviour (e.g. buying houses).

Web/Mobile applications can become b2b loyalty program market places; where small business can work together to bring their customers back.

And what about employees?  

Punches can be a way of encouraging positive behaviours in employees also.  In the majority of business's employees are also customers.  Rewarding positive behaviours at work can be an excellent way to show recognition and increase employee loyalty too.  Showed up for work early?  Made a customer happy?

Is it Ten Times better?

If implemented right; Yes.  I think it could be.  Especially if small businesses can do it more cheaply than its costing them now.  It's sort of like going from typed print to hyper text (except with customer loyalty).

I've taken my best crack at making Punch Cards that are that much better.

QR Code Based Loyalty Cards

In a nut shell; this is what I've been building for the last 2+ years.  I've been working on other things too.  Contracting, other projects, some community work, and family.  But I've also put alot of time into building what I think is the Better Punch Card (version 2).  I've got all of the core functionality and I just went into early Beta with my first business Babes in the Woods.  I didn't have the resources to hire other people very much, and I've covered all of the expenses, but I had most of the right skills and I was willing to do the work.

It's a very early Beta.  This blog post will probably be one of the first links to the website.  If you are a business, and you don't mind printing your own cards; you can use the software for free.

The application will even provide free users with all of the print files, with instructs, ready for you to print either on your own [inkjet/laser] printer, if you use business card perforated sheets, or at your local print shop.  Or, if you require, for a variable offset printing.  Each card has a unique QR code, URL and registration number.

When employees scan the cards, they get a "Puncher", which defaults to "Punch" the card.  At this point, if the employee only means to punch the card, they are done.

If they are instead redeeming a reward for a customer, they click the appropriate reward toggle (this untoggles the punch, but if the employee wants to punch the card also, they can do so).

If customers scan their card (or enter the URL below the code), they get a "softcard" which includes a QR Code and an ad for the business (for specials and events).  At this point they can either bookmark the page (or better; save to desktop); or they can install a mobile app (still in development).  The can now use their mobile device as their card.

If the customer registers their cards they get access to several more features (e.g. card merge and punches for online behaviours (e.g. share on Facebook).  Customer are required to register if they use the share buttons on specials and events.

Even when customers do register; they are required to give only the barest of information (e.g. the application never asks for last names, and credit card information is never transmitted to my servers).
A customer can have more than one softcard (of course) but at no point when looking at the softcard for a business does that customer ever see an ad for any other business; including mine.

All this and more is free; although I am also offering a service for companies that want card design, card printing and loyalty program support; which I offer by subscription.  (I call it the Full Service Package)

Not all of the features are in place that I need to have for the "Full Release"; but there's enough there right now for businesses to start using immediately.  I've put a lot of time in on this project; and I'm very very happy with what I've been able to accomplish.  Try it out; I think you will be too.

Premium Customer Loyalty Program.  Free.

Tuesday, June 26, 2012

The Aphephobic Caterpillars

Image "Borrowed" from Shu (
Once upon a time, two aphephobic caterpillars met, walking along the same side of the same twig in opposite directions. Both caterpillars, not wanting to be touched, tried to crawl over the other and be the one on top. Instead they ended up crawling up each other, perpendicular to the twig on which they were both walking. Each realized their mistake immediately but panicked, not wanting to be the one who hesitated and have the other caterpillar crawl up and over themselves. Instead each crawled even faster trying to be the one who crawled on top of the other.

Of course, before long, they were no longer crawling along the twig at all and were only holding on to each other, and fell from the twig plummeting towards the ground and certain death.

Now you may not realize this but time goes by more slowly for caterpillars than it does for humans and the two caterpillars, clinging on to each other out of fright, had time to have a conversation with each other before they collided with the ground below them. The conversation went something like this:

The first caterpillar said: “It seems our own unreasonable fears will be the end of us both. How ironic that our last act is to grasp each other tightly when our fear of being touched is the cause of our predicament”.

To which the second caterpillar responded: “I’m sure this doesn’t have to be our last act. Deep inside us we both have the knowledge of flight. We have thus far lived a life of legs; but we are also creatures of wings. We have crawled but it is our destiny to fly. Maybe, just maybe, we can tap that part of our inner selves and soar!”.

Inspired both caterpillars released their hold of each other; and died alone.


Wednesday, November 16, 2011

Want to know when Google is adding your area to Street Level View?

Google doesn't tell many people when they are driving around in their cars, photographing an area for Street Level View (I understand Google notifies Women's Shelters and other organizations that are sensitive about exterior photographs of their buildings).  There are several obvious reasons for not being too public about their mapping intentions, of course.  Most importantly, I imagine, is that Google wants to keep their data from being "tampered" with by people who would make some special display specifically for the mapping vehicle (e.g. a "Hi Mom!" banner).

I, however, really want to know anyway.  I live on a small island that relies heavily on tourism.  Our island has many artists and artisans who work out of studios on their own properties.  I would like to have these artists have displays of their work on the ends of their driveways when Google conducts a street level view on our island (our island hasn't been covered yet, but I think it may be covered in the future).  I think it would be good for the locals, and for the tourists who look at our island online.

As I said, google doesn't broadcast where they intend to cover next; but they DO broadcast where they are currently.  Google has a special site that you can visit to see the current location of their vehicles, categorized by country:

As I said, the chart on this page shows the current location and only seems to list the city or general area they are currently mapping.  However, I think it's a start and below I'll show you how you can use this data to get an alert when Google starts mapping a new area in your country.

The method I'll describe here is a very simple one.  The country data on the page above is populated through an XML feed.  I decided to import the data into a Google Docs spreadsheet, and make the spreadsheet notify me via email if the cell that contains the data for my country changes.  The steps I took are as follows:

  1. Create a spreadsheet in Google Docs
  2. In the first cell on the default worksheet (A1) insert the following function:
  3. Select Tools > Notification Rules and add a new rule
  4. Click the checkbox next to "Any of these cells are changed: ".  In the cell range box, put the name of the cell that contains the data for your country (for my country, Canada, the cell was C7)
  5. Click the checkbox next to "Email - Right Away" under "Notify Me With..."
  6. Save your spreadsheet and exit!  
The spreadsheet doesn't poll the source every second, but you should get a notification shortly after the data changes (certainly within the hour).  Again, this isn't exactly "notice", but hopefully it will give you some warning.

If there is a lot of activity in your country (and you're getting too many notifications) you can use the FIND function to locate your area/city name within your country data and then base your notification on the result.

Tuesday, July 19, 2011


I've completed the initial version of a English US EAC compliant paper ballot generating API. It takes XML candidate/issue data and uses it to generate a PDF of a ballot that is 100% compliant with the US EAC ballot design standards.

The application is Opensource and you can find it on Google Code at the following address:

You can also use the hosted version on the (which is free of charge). The following is a sample ballot generated using the free API:

If you would like more information on how to use it/integrate it into your own applications, you can view the project wiki at the following location:

Wednesday, January 26, 2011

Making DIVs, using the CSS "Float Left" property, all have uniform heights; automatically adjusting to match the tallest DIV in the row

Update: I modified the code so it would work with the onresize() event

Would you believe that was the best title I could come up with? Honestly, if you can think of a better one shoot me a line - I want to hear from you.

Okay, I'm a big fan of Fluid Design. I love to design websites/webapps that flow into the space available, from giant screened desktops to mobile phones, and always look like they were designed with exactly that screen size in mind.

One thing that CSS-P brought to web design that didn't exist back when we were using invisible nested tables to do webpage layout is the ability to have rows with a dynamic number of "columns". So, for instance, if you are showing rows of thumbnails, you can have more thumbnails on a row if the browser window is wide, and fewer if the window is narrower.

The method for doing this is to cause items to "Float Left". Usually this means putting your content (say a thumbnail and label) into a DIV; and giving the DIV the CSS property of:


If the above were added to a page's stylesheet, the DIVs with the class "column" assigned to them would stack to the right of the item before it on the page, and if there are more than one item, each will stack to the right of the previous DIV until there is no more room on that line, and then the next will appear below that row, starting a new one.

If the DIVs do not share the same height, each row will have their tops all lined up, but will have bottoms extend downwards as much as they need to to accommodate the content they contain. And the next row, which also has all the tops lined up, will appear just below the bottom of the DIV, from the previous row, that was "tallest". Like this:

(So above the second row begins below the "tallest" item in the first row, which is c)

This isn't bad, but many designers would rather have some uniformity to the DIV sizes and have, say, the container backgrounds and borders fill in the spaces below a, b, and d.  We can do that, of course, by giving each container a specified height, instead of letting the content drive the height, but then we would have to make all the containers as tall as they are likely ever to get which isn't prefect either. 

Ideally, I think, it would be best to have all of the DIVs in a row be all be the same height, but only be as tall as is actually needed to accommodate the tallest DIV in the row.  Like this:

(in the above example, the DIVs in the first row are as tall as the tallest DIV in the row, which is c, and all the DIVs in the second row are as tall as f, which is the tallest DIV in that row)

Well, with a bit of jQuery you can have your DIVs all adjust their heights so they are uniform across the entire row. You will have to add jQuery to your page (see How jQuery Works) and add the following Javascript to your page:
var currentTallest = 0;
var currentRowStart = 0;
var rowDivs = new Array();

function setConformingHeight(el, newHeight) {
 // set the height to something new, but remember the original height in case things change"originalHeight", ("originalHeight") == undefined) ? (el.height()) : ("originalHeight")));

function getOriginalHeight(el) {
 // if the height has changed, send the originalHeight
 return ("originalHeight") == undefined) ? (el.height()) : ("originalHeight"));

function columnConform() {

 // find the tallest DIV in the row, and set the heights of all of the DIVs to match it.
 $('div.column').each(function(index) {

  if(currentRowStart != $(this).position().top) {

   // we just came to a new row.  Set all the heights on the completed row
   for(currentDiv = 0 ; currentDiv < rowDivs.length ; currentDiv++) setConformingHeight(rowDivs[currentDiv], currentTallest);

   // set the variables for the new row
   rowDivs.length = 0; // empty the array
   currentRowStart = $(this).position().top;
   currentTallest = getOriginalHeight($(this));

  } else {

   // another div on the current row.  Add it to the list and check if it's taller
   currentTallest = (currentTallest < getOriginalHeight($(this))) ? (getOriginalHeight($(this))) : (currentTallest);

  // do the last row
  for(currentDiv = 0 ; currentDiv < rowDivs.length ; currentDiv++) setConformingHeight(rowDivs[currentDiv], currentTallest);



$(window).resize(function() {

$(document).ready(function() {
(the above code assumes you have assigned the CSS class called "column" to each of the DIVs that are "Floating Left")

If you can read Javascript/jQuery, you will find the code comments will sufficiently explain how it works; but in a nutshell: The script goes through each DIV, determining which are on the same row by comparing the X value of the top of each container.  It keeps track of which is the tallest, and then sets the height for each DIV in the row based on that value.

Thursday, December 9, 2010

Link Tracking using jQuery and the Google Analytics Asynchronous Tracking Code

The Google Analytics tracking code is triggered when pages that include the code are called. But what about when users request PDFs and other documents that aren't web pages or click on links to external web pages on other websites?  How do we track these events also?

Here is some jQuery you can add to your pages that will allow you to track when people click on these links.  You will have to add the Google Analytics Asynchronous Tracking code (found here: and add jQuery to your page (see here: to use this code:



  href = ($(this).attr('href') == undefined) ? ('') : ($(this).attr('href'));
  href_lower = href.toLowerCase();
  if(href_lower.substr(-3) == "pdf" || href_lower.substr(-3) == "xls" || href_lower.substr(-3) == "doc") {
   _gaq.push(['_trackEvent', 'document', 'download', href_lower.substr(-3), $(this).text()]);
   _gaq.push(['_trackPageview', href]);
  if(href_lower.substr(0, 4).toLowerCase() == "http") {
   _gaq.push(['_trackEvent', 'external_link', 'open', 'href', $(this).text()]);
   _gaq.push(['_trackPageview', href]);
  if ($(this).attr('target') != undefined && $(this).attr('target').toLowerCase() != '_blank' && href_lower.substr(0,10) != "javascript") {
   setTimeout(function() { location.href = href; }, 200);
   return false;



So, the example above will track clicks on internal links opening files with the extensions pdf, xls, and doc and will categorize them in your Google Analytics event tracking reports as "documents".  It will also track outgoing links (links with URLs beginning with "http") and categorize them as "external_links".

If you need more categories, add more if statements.  In the method above I create one click event that is attached to every link on the page, rather than just attaching specific events to the links that require them.  If a link satisfies more than one criteria (e.g. it's a link both to a PDF, and it's on an external website) then both events will be created.  In the end I thought this approach was probably more customizable and efficient overall, especially if there are several event categories.

The setTimeout bit is to allow links that replace the page content (i.e. regular links) a moment before calling the page; so that the GA function has time to execute before opening the page. Be careful when using plugins like Fancybox; add links to such plugins to the exception list in the condition wrapped around the setTimeout function as these functions remove the href attribute and the combination of that and the setTimeout can cause unpredictable behaviour.

You may want to track events for clicks made by specific links like the nav or featured content.  You can achieve this using a similar method:

$("#main_menu a").mouseup(function(){
if($(this).attr('href').toLowerCase() != "javascript:;" && $(this).attr('href') != "#") {
_gaq.push(['_trackEvent', 'main_menu', 'open', $(this).attr('href')]);

Change the ID in the yellow to whatever you call the container that your menu is kept in.  I added the check for JavaScript:; and # just in case there are links that don't open pages (e.g. links that reveal further options).

Leave comments if you have other suggestions for mixing jQuery and GA for better tracking.

Wednesday, December 8, 2010

Google Nexus S Fun

I happened to catch the second clue in the Google Nexus (@googlenexus) Nexus S contest about 20 minutes or so after they posted it. I didn't get the first puzzle; I thought I did but I was wrong. This one I was on the right track right away. The clue was:

Puzzle Challenge #2: There is more than meets the eye at You'll find the answer in a tweet.

So the FIRST thing I did when I went to the page was look at the source code. At the very bottom of the page I found this:

                               I                I                               
                                I      ++      I                                
                            IIII   IIIIIIIIII  IIIII                            
                    IIII  IIIIIIIIIIIIIIIIIIIIIIIIIIII  IIII                    
                   IIIIII IIIIIIIIIIIIIIIIIIIIIIIIIIII IIIIII                   
                   IIIIII IIIIIIIIIIIIIIIIIIIIIIIIIIII IIIIII                   
                   IIIIII 0110100001110100011101000111 IIIIII                   
                   IIIIII 0000001110100010111100101111 IIIIII                   
                   IIIIII 0110011101101111011011110010 IIIIII                   
                   IIIIII 1110011001110110110000101111 IIIIII                   
                   IIIIII 0110101001010000010011110100 IIIIII                   
                   IIIIII 100101101100IIIIIIIIIIIIIIII IIIIII                   
                   IIIIII IIIIIIIIIIIIIIIIIIIIIIIIIIII IIIIII                   
                   IIIIII IIIIIIIIIIIIIIIIIIIIIIIIIIII IIIIII                   
                   +IIII  IIIIIIIIIIIIIIIIIIIIIIIIIIII  IIII:                   
                                IIIIII    IIIIII                                
                                IIIIII    IIIIII                                
                                IIIIII    IIIIII                                
                                IIIIII    IIIIII                                
                                IIIIII    IIIIII                                
                                IIIII,    IIIIII                                
                                  I:        ~I                     

I noticed the binary around the android's belly so I translated it into ascii text and got the following URL:

I went to the URL and was faced with a puzzle with a timer. The puzzle involved rearranging letters, by swapping two neighbours, in a string so they were in the "correct" order. You could see how many were correct at the bottom of the page. It was pretty simple and I got the solution before the timer ran out (it took me a few seconds to recognize I was being timed).

Clicking the Tweet your victory! button opened a web tweet box with the message:

@googlenexus Run, run, as fast as you can! You can't catch me, I'm the Gingerbread Android!

I hope it's not against the rules to post the solution; I looked quickly through the legalese and didn't see anything forbidding it.