Griffins Jersey Contest – 90s Edition

The number of times I have “quit” the Grand Rapids Griffins’ annual jersey design contest is comical at this point.  Last year I even publicly announced that I was done.  Then they went and made this year’s edition 90s-themed and I was drawn right back in.

Two years ago the Griffins requested an 80s-themed “fauxback” jersey as part of their contest.  I loved it because it raised the question of what makes an 80s-themed jersey.

As I wrote at the time, there were many jersey design elements that became more prevalent in the 1980s, but it was really color that defined the decade.  The Griffins were dictating the colors for their contest, though, so I was really curious what they would deem “80s enough” to win.  In the end, and perhaps unsurprisingly, I strongly disagreed with their choice, which left me feeling like the question of what makes an 80s jersey was unanswered.

This year, with the challenge being to create a 90s-themed jersey, I believe there are much stronger trends to work with.

While the 1980s saw only two NHL teams relocate and only one go through a major rebranding, the 1990s saw seven expansion teams, three relocations resulting in new team identities, and a whole slew of redesigns.  Many of them used the same design elements and most of those changes have since been reverted, resulting in a set of looks that are uniquely 90s.

As such, when the Griffins announced the theme of this year’s contest, for me it wasn’t so much a matter of figuring out what they were asking for as it was figuring out how to make all of those design options work together.  An idea popped into my head almost fully-formed.  As such, despite my “retirement,” I was drawn back in.

As an aside…  I am writing this on August 2 for publish after the design contest is over.  I usually spend the entire design period tweaking, then write up my design thoughts and publish them along with my submission right at the deadline.  This year, as part of an attempt to put less effort in, I’m submitting my design on the second day of the contest and writing this up for publish after voting is over.


My completed submission for the Grand Rapids Griffins 90s Jersey design contest.

There are six distinctly 90s elements to this jersey.  I’ll note who used them in the NHL to show just how prevalent they were (I’m choosing the NHL because their identities are generally more stable and documented than minor leagues).

“Winged” Shoulders

From their inaugural season to the AHL’s league-wide redesign in 2009, the Griffins’ jerseys featured a shoulder design that was meant to represent the wings of their mascot.  I’ve brought them back here.  It’s something that’s unique to the team from the era in question.

By using this shoulder pattern, I was unable to take advantage of another trend of the 1990s: alternate logo shoulder patches.  They just don’t work on that background.

Diagonal Sleeve Stripes

Diagonal sleeve stripes were used in the NHL prior to the 1990s (specifically, by the Pittsburgh Penguins, Hartford Whalers, and Vancouver Canucks) but in the 90s there were nine teams that introduced them or brought them back, bucking the trend of standard straight stripes.  The aforementioned Penguins, Whalers, and Canucks all used them.  The expansion Mighty Ducks of Anaheim and Florida Panthers and the relocated Phoenix Coyotes did as well.  Redesigns or third jerseys for the Calgary Flames, New York Rangers, St. Louis Blues, and Washington Capitals all featured diagonal sleeve stripes.

Angled Hem Stripes

Most – but not all – teams who introduced diagonal sleeve stripes also paired them with a nonstandard angled set of stripes at the hem.  Anaheim, St. Louis, and Washington all went with an asymmetrical version of this element while Calgary and Pittsburgh chose to make a “V” shape of their stripes.  Additionally, the Colorado Avalanche featured a mountain-like design along their hem.

I really dislike the asymmetrical look – even if it’s iconic – and the gap in Pittsburgh’s design, so my concept uses something similar to what Calgary’s 1998 third jersey had.

Arched Nameplate

The vertically-arched nameplate was introduced to the NHL by the Detroit Red Wings in 1982.  In 1990 it was copied by the Rangers.  The Avalanche used it when they relocated in 1995 and the Panthers switched to it in 1998.

While this is hardly a widespread design element from the 90s, the fact that it quadrupled in use over the decade and that the Griffins are the farm team of the originators makes me comfortable including it in the design.

Rounded/Custom Numbers

In 1967, the Penguins made their debut wearing rounded numbers, dropping them after a single season.  The Rangers broke from tradition in 1976, switching to a completely different jersey that included rounded numbers, which only lasted until 1978.  The Red Wings switched to “fancy” numbers for the 1982-83 campaign before immediately switching back.  For the first 75 years of the NHL, those were the only times a team didn’t wear some form of block number on their jerseys.

Then the Tampa Bay Lightning came along.  After spending their inaugural campaign in a standard – though drop-shadowed – block font, in 1993 they italicized their numbers.  That same year, the NHL’s All-Star Game featured jerseys with rounded numbers rather than block.  One season later, the Flames had italicized numbers while the Lightning had moved on to a custom “paintbrush”-like font.  One more season later, seven teams had at least one jersey that didn’t use a block font.

By the summer of 1999, 14 teams were using a non-block font on at least one of their sweaters.

The Griffins debuted with a custom number (and name) font and I was highly tempted to go back to it.  In the end I chose to stick with a slightly-more-generic rounded font, similar to those used by Calgary, Nashville, Phoenix, the Carolina Hurricanes, and the San Jose Sharks.

Tampa Bay’s “paintbrush” numbers might be more memorable than any of them but I just didn’t think they fit the design.  My goal (whether it’s the request of the Griffins or not) is to make the most-90s jersey that still looks good, not just cram as much 90s stuff into a jersey as possible.

Angry Mascot Logo

My 90s-style Grand Rapids Griffins logo.

The last element of the jersey is the Angry Mascot Logo.  San Jose debuted represented by a Shark biting a hockey stick in half.  Florida’s first logo featured a Panther pouncing forward.  The New York Islanders rebranded to focus on a fisherman holding a hockey stick, staring angrily.

In the minor leagues the trend of “fierce” logos was even more visible.  Between the AHL and the IHL, no fewer than 13 teams introduced branding – of various qualities or rendering – featuring some combination of a snarling animal or fearsome creature between 1990 and 1999, including the Albany River Rats (1993), Carolina Monarchs (1995), Chicago Wolves (1994), Cincinnati Cyclones (1992 and 1993), Denver Grizzlies (1994), Hamilton Bulldogs (1996), Hartford Wolf-Pack (1997), Indianapolis Ice (1996), Kentucky Thoroughblades (1996), Lowell Lock Monsters (1998), Beast of New Haven (1996), Saint John Flames (1998), and Syracuse Crunch (1994).

I have to admit, I don’t like this trend.  The Griffins’ original logo does not meet my criteria for “fierce” and I find it to be a better logo than either their current mark or the one I’ve created here.  But to stick with the trend, I’ve swapped majesty for musculature.  That said, I do feel like, in doing so, it’s moved out of 90s and become a little more modern.


I submitted this jersey in red because I think it looks best.  That said, another perceived trend of the 1990s was “black for black’s sake,” seeing teams add black as a team color and introducing black jerseys simply because it was a color that sold well at the time.  With that in mind, I created a full jersey set that includes a black alternate.

My full jersey set for the Grand Rapids Griffins 90s Jersey contest

The funny thing (though I suppose how funny it is depends on how far this design goes in the contest) is that I’m not sure I actually even like this design.  It’s easily my least favorite submission to the Griffins’ contest over the years.  Given what they’re asking for, though, I think it nails it.


I called “black for black’s sake” a perceived trend because it simply didn’t happen in the NHL the way that it’s portrayed at times.  There are other elements that I ignored because, while everyone “knows” 90s design was all about them, it turns out the facts don’t match up with that.

The Calgary Flames added black as a trim color in 1995 with a black alternate jersey in 1998.  The Washington Capitals included black as a trim color in their 1995 redesign and added a black alternate in 1997.  The Philadelphia Flyers already had black trim and their 1997 alternate was black.  That’s three of 28 teams.

Teal?  That was just the San Jose Sharks, with the short-lived New York Islanders  “fisherman” jerseys using it as an accent.

Similarly, there’s Tampa Bay’s “paintbrush” crazy numbers.  Yeah, the Lightning used them for six seasons.  And the Mighty Ducks of Anaheim had a one-season alternate with a crazy number font.  But that’s it.  It didn’t take the league by storm.  It was really just one team and a quickly-abandoned alternate for another.

Finally, there’s asymmetrical striping.  I called it “iconic” above but it was just Anaheim and St. Louis who used it.  Two teams.  More teams used symmetrical angled stripes, but those don’t stick in our collective memories.  The Islanders’ fisherman jerseys were technically asymmetrical but not in the way we typically think of.

We’ll see how many of these perceived trends end up being applied to designs in the contest.


View All Images as Gallery

DetroitHockey.Net Logo Redesign 2019

Back in 2015, I wrote a bit about the history of the DetroitHockey.Net logo.  Over the last several weeks, I was forced to add a new entry to that record.

As I detailed at DH.N itself, the Detroit Tigers forced me to change the site’s logo, claiming that DetroitHockey.Net’s Old English D conflicted with their trademarked Old English D.  I don’t agree with this assessment but I also can’t fight it.

Thankfully, I had a little bit of a hint that the Tigers would do this, so I’d begun brainstorming ideas for a new logo, just in case.  With the Old English D expected to be unavailable to me, I started thinking about other symbols of Detroit.

I ruled out the Spirit of Detroit statue because I think it’s somewhat awkward to work with, plus Detroit City FC uses it in their badge and trading conflict with one team for another seemed like a bad idea.

From there, I started thinking about Detroit’s flag.  I’m a big fan of quartered flags – like those of Detroit, Maryland, Panama, and the Dominican Republic – and thought that I could follow the Baltimore Ravens’ example and use elements from the quartered flag in a shield.

The city of Detroit’s flag (Credit: Dyfsunctional [Public domain] via Wikimedia Commons)
With Detroit placing the city’s seal at the center of their flag, I thought that might be a good place to work in a set of crossed sticks, carrying that forward from my previous designs.  I simplified the elements of the flag, reducing 13 stars, three lions, and five fleurs-de-lis down to one each.

The first iteration on DetroitHockey.Net’s new logo.

The idea didn’t work as well as I had hoped.   None of the elements looked quite at place there.  The sticks didn’t fill the center circle well and said circle pushed the elements of each quarter into an awkward position.

I thought more about the quarters of Detroit’s flag and what they represented.  France, England, and the United States (with the latter taking up two of the four quarters), the three countries that have laid claim to Detroit.  The flag is basically about how they came together.  DetroitHockey.Net takes those things and adds a fourth: hockey.  So I decided to remove the center circle and break the shield up into five sections.  I also changed the colors so that the red from Detroit’s flag became “DetroitHockey.Net Red,” the yellow became more of a gold, and the blue darkened to match the red.

The second iteration on DetroitHockey.Net’s new logo.

This was closer to what I was trying to do, but still didn’t seem right to me, so I worked on tweaking how the shield was broken up.  Along the way I changed the size of the shield and the angle and number of the stripes.  I settled on seven stripes as it represented the seven Stanley Cup Championships won by the Red Wings at the time that the site that would become DetroitHockey.Net was founded in 1996.

The final changes were to switch to a slightly lighter shade of blue, then emphasize that blue.

Iterations three through ten on DetroitHockey.Net’s new logo.

For a site representing the Red Wings, the extra blue didn’t seem to be a fit, so I rolled back to my previous attempt and went with that.

The final design of DetroitHockey.Net’s new logo.

Given the importance of the number nine in Red Wings history, I feel it’s highly appropriate that my final design was the ninth iteration.

With so much gold in the new logo, I felt it was appropriate to bring that color into an updated version of the roundel mark I use for the site.

The roundel version of DetroitHockey.Net’s new logo

Finally, I sketched out a version of the logo that combines the shield and the roundel by taking the elements from the shield and placing them inside the inner circle of the roundel.

A possible DetroitHockey.Net alternate logo based off of the site’s new logo

I may never use that design but I’m keeping it around for now.

With the change, the new DetroitHockey.Net logo timeline is as follows:

DetroitHockey.Net’s updated logo history, from 2002 to 2005 to 2006 to 2014 to 2019.

The circumstances of the change have really clouded my own judgement on this one, but early reaction is positive.

As much as I loved the old logo and spent time building a brand around it, there was a lot of empty space and the black border tended to merge with the red a bit.  When designing merchandise, I found myself defaulting to monochrome versions of the logo to resolve that issue.

The new logo is more complicated and requires a little more explanation than I’d like but I really like how the combination of red, gold, and blue turned out.  I still have concerns about the red and gold evoking too much of a Detroit City FC feel but, to a certain extent, that’s like saying the old colors were too similar to the Chicago Blackhawks.  I’m not too worried about it.


View All Images as Gallery

Grand Rapids Griffins Alternate Jersey Concept 2018

I thought I’d retired from the Grand Rapids Griffins’ annual jersey design contest but, as this year’s edition rolled around, I realized that I wanted to revisit my 2016 entry and try to tweak a couple things that bothered me from it.

In 2016, I didn’t like that the crest on my submission and the shoulder logo both used the same griffin silhouette.  I thought it looked good but was just a little lazy.  Also, I had intended to enter a red jersey in the contest and was convinced to switch to black at the last minute, so I wanted to return to that idea.

Additionally, at the end of the 2014 contest there was a lesson that I had learned and promptly forgot: Minor league sports teams like logos that have their team name included.  I wanted to take that into consideration this time around.

Keeping all of that in mind, I created a new crest for the red version of my 2016 entry.

My initial logo concept for the Grand Rapids Griffins’ 2018 jersey design contest

I changed the shape of the shield just to change things up a little.  Across it is a banner reading “Griffins” in a vaguely old-German font that I think matches the heraldic crest.  Above that is a stalking griffin in silhouette – because the griffin in the shoulder logo is standing on two legs, I wanted this one to be down on all fours (though only three are really seen).

Like the griffin in the shoulder logo, this new griffin features a pair of homages to other logos.  The tail is the tail that was used in the Griffins’ original logo while the wing is based on the Winged Wheel logo of the Red Wings.

I put the new logo on the red jersey design from 2016 and I thought I was good.

My initial concept for a red jersey for the Grand Rapids Griffins’ 2018 jersey design contest

But then I kept thinking.

In 2016, the jersey I actually liked best was the white one, which I wasn’t allowed to submit because the Griffins specifically asked for a dark jersey.  This year, not only did they not make that requirement, but their promotional calendar has dates for fan-designed jerseys to be worn both before and after New Years’; which is when the American Hockey League switches from wearing white jerseys at home to dark ones.

I decided to switch to the white jersey.  Which immediately gave me trouble with the crest again.

I designed the new crest to work best on red.  It’s primarily black with a heavy white outline.  That outline disappears on a white jersey, so what to do?

I tried out several different color swaps.  Added outlines.  Took the opportunity to play with the sleeve numbers a little, then immediately abandon that idea.

In the end, I decided to go with a logo that mixed elements from my previous submissions with some of my new ideas.

My final logo concept for the Grand Rapids Griffins’ 2018 jersey design contest

The extra outline on the shield helps set it off on the white jersey while not outlining the “banner” keeps the logo from getting too heavy.

My final white jersey concepts for the Grand Rapids Griffins’ 2018 jersey design contest

And those are combined with the previously-created shoulder logo, which sits opposite the logo of the Griffins’ parent club, the Detroit Red Wings.

My alternate/shoulder logo concept for the Grand Rapids Griffins’ 2018 jersey design contest

One final note is that I use a template that doesn’t allow for a laced collar to be displayed.  I would expect one to be used, as shown here.

My final white jersey concepts for the Grand Rapids Griffins’ 2018 jersey design contest, in a template showing how it would look on a player.

View All Images as Gallery

Kalamazoo Wings Jersey Concept 2018

Aside from the Grand Rapids Griffins, I don’t normally enter jersey design contests.  In general, I don’t like “fan designed” jersey contests because rarely does an actual fan of the team win.

I made an exception this year, though, for the Kalamazoo Wings.  I can’t rightfully call myself a Kalamazoo fan given their rivalry with the Toledo Walleye, who are affiliated with the Grand Rapids Griffins and Detroit Red Wings, who I actually am a fan of.

The K-Wings do hold a special place in nostalgia for me, though.  They were the visitors in the first hockey game I ever went to, a Thanksgiving game against the Muskegon Lumberjacks when both teams were in the old International Hockey League and I was about five years old.

So I decided to give the Wings’ contest a shot and, in all honesty, it didn’t go in the direction I expected it to at all.

My initial thought, since all existing K-Wings marks were off-limits, was to turn to mythology.  “Hermes had a winged helmet, didn’t he?”  In addition to winged sandals, yes he did.  So I set about trying to do something like that.

In my head, I was thinking something kind of like the old Ottawa Senators logo, with the team name in a partial roundel broken by the wings of the helmet.  It turns out that “Kalamazoo Wings” is a really hard name to work with without getting letters upside down at some point on the circle.

Also, in all of the logos I’ve worked on in the past, I’ve never tried to draw a face.  As I continued working, I got closer to what I wanted.  I abandoned Hermes and his traveller’s cap and went for a more Nordic full helmet, but the contest deadline came up quickly and I had a conference and a short vacation planned.  It just wasn’t going to come together.

Kalamazoo Wings logo concepts based on Hermes’ winged helmet.

In parallel with the helmet-based logo, I was putting together an alternate logo based around a K in a shield with wings.  I liked the general shape right from the start but tried a variety of fonts for the K, colors, etc.  That process actually went well, and partway through I realized that I had a couple different options I could run with.

Concepts for a Kalamazoo Wings alternate logo, with a K in a winged shield

As I ran out of time, I decided that I needed to switch gears. I had a solid logo in my intended alternate and I had a tertiary that could become a secondary in a pinch (which I was in).  I had a jersey design already in place from a previous Griffins contest so I pulled the plug on continued sketching and called it done.

My 2018 Kalamazoo Wings jersey concept

The crest, as previously mentioned, is a K inside a shield with wings.  In gold and blue, I think it has a bit of an art deco vibe.

That shield is repeated without the wings on the shoulder logo, where it’s adorned with a pair of crossed hockey sticks and the letters KWHC – Kalamazoo Wings Hockey Club.

The jersey itself features a red body and blue sleeves.  I wanted to do contrasting-color sleeves as they give the jersey the “winged” look of the Detroit Red Wings’ away jersey, something I think is appropriate for another team named the Wings.

It’s not what I was aiming for but I think it looks good.  It’s probably not different enough from the K-Wings current design to win the contest, which is why I’m disappointed that I ran out of time to work on the winged helmet idea, which I think was pretty unique.

We’ll see how it goes, though.


View All Images as Gallery

Making Work Slack More Fun: PHPBot and YakBot

I’ve been toying around with Slackbots a bit lately.  Back in January I wrote about publishing a Git log to Slack via one.  They’re dirt simple to implement and can be really useful.

They can also be fun.  I’ve added a couple bots in our office Slack just for posting randomness, running as cron jobs.  I figured I’d take a look at them here.


A couple weeks ago one of my coworkers posted a link to the documentation for a seemingly-random PHP function in our dev Slack channel.  It wasn’t random, it just pertained to an offline conversation, but we still made some cracks about how he was doing it to spread awareness of the function.

At nearly the same time, he and I both said, “That sounds like a great idea for a Slackbot.”  So I made it, and it looks like this:

<?php
$funcs = get_defined_functions();

do {
   $index = array_rand($funcs['internal']);
   $url = 'http://php.net/manual/en/function.' . str_replace('_', '-', $funcs['internal'][$index]) . '.php';
   $headers = @get_headers($url);

   if (strpos(implode(';', $headers), 'rel=shorturl') === false) {
      unset($url);
      sleep(2);
   } else {
      $attachments = [];
      $fields = [];

      $html = preg_replace("|\n[ ]*|is", '', file_get_contents($url));

      $doc = new DOMDocument();
      libxml_use_internal_errors(true);
      $doc->loadHTML($html);
      libxml_clear_errors();

      $xpath = new DOMXpath($doc);

      // "HEADER" INFORMATION
      $el = $xpath->query("//*/div[@class='refentry']")->item(0);
      $name = $xpath->query("./div[@class='refnamediv']/h1", $el)->item(0)->textContent;
      $verinfo = $xpath->query("./div[@class='refnamediv']/p[@class='verinfo']", $el)->item(0)->textContent;
      $refpurpose = $xpath->query("./div[@class='refnamediv']/p[@class='refpurpose']", $el)->item(0)->textContent;

      if (!$name) {
         sleep(2);
         continue;
      }

      // DESCRIPTION
      $el = $xpath->query("//*/div[@class='refsect1 description']")->item(0);
      $title = $xpath->query("./h3", $el)->item(0)->textContent;
      $example = $xpath->query("./div", $el)->item(0)->textContent;
      $description = $xpath->query("./p", $el)->item(0)->textContent;
      $note = $xpath->query("./blockquote", $el)->item(0)->textContent;

      $text = '';
      $text .= '```' . $example . '```' . PHP_EOL;
      $text .= $description . PHP_EOL;
      if ($note) $text .= '>>>' . $note . PHP_EOL;

      $fields[] = [
         'title' => $title,
         'value' => trim($text)
      ];

      $attachments[] = [
         'title' => $name,
         'title_link' => $url,
         'text' => $verinfo . PHP_EOL . $refpurpose,
         'fields' => $fields,
         'mrkdwn_in' => ['fields'],
      ];

      $msg = [
         'text' => '',
         'attachments' => $attachments,
         'username' => 'PHPBot',
         'icon_url' => 'https://example.com/slackbots/avatars/phpbot.png'
      ];
   }
} while (!$msg);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

As you can see, it’s a little more advanced than just posting a link to a PHP.net URL.  If PHP.net had metadata on its pages that allowed for a nice-looking Slack unfurl, I might have just done that.  Instead I decided to pull content from the page we’re linking to and make the Slack post a little prettier with it.

So how does that all work?

<?php
$funcs = get_defined_functions();

do {

Simple thing here.  Get the defined PHP functions and then start a do…while loop.  By using get_defined_functions() I’m limiting what our PHPBot can link to but I figured there are enough functions available there that it doesn’t really matter.

$index = array_rand($funcs['internal']);
$url = 'http://php.net/manual/en/function.' . str_replace('_', '-', $funcs['internal'][$index]) . '.php';
$headers = @get_headers($url);

if (strpos(implode(';', $headers), 'rel=shorturl') === false) {
   unset($url);
   sleep(2);
} else {

We jump into our do…while loop, where we grab a random function from our array of functions and build what should be a PHP.net documentation URL from the name.  For some reason, not every function has documentation (or at least not that matches this template), which is why we’re in a do…while loop.

We get the headers for that URL and if no shorturl is defined there, we know we don’t have a valid function documentation page.  In that case, we sleep (I like to sleep in situations like this) and unset the URL, then we’ll take another run at the do…while loop.

If we do have a shorturl, we get into the heavy lifting.

$attachments = [];
$fields = [];

$html = preg_replace("|\n[ ]*|is", '', file_get_contents($url));

$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();

$xpath = new DOMXpath($doc);

We initialize a couple arrays that we’ll use for building our Slack message, then we pull in the HTML from the documentation page and strip out line breaks and any spaces that might start a line.  We do that stripping so that the text we use in our message later on looks better.

Then we load that HTML into a DOMDocument and fire up XPath for it so we can target the elements we want a little easier.

// "HEADER" INFORMATION
$el = $xpath->query("//*/div[@class='refentry']")->item(0);
$name = $xpath->query("./div[@class='refnamediv']/h1", $el)->item(0)->textContent;
$verinfo = $xpath->query("./div[@class='refnamediv']/p[@class='verinfo']", $el)->item(0)->textContent;
$refpurpose = $xpath->query("./div[@class='refnamediv']/p[@class='refpurpose']", $el)->item(0)->textContent;

if (!$name) {
   sleep(2);
   continue;
}

We grab the element of the page that contains all of the information about the function by targeting the refentry class.

Inside that element is a div with class refnamediv, which contains an H1 with the function name, a paragraph classed as verinfo with information about what versions of PHP support the function, and a paragraph defining the function classed as refpurpose.  We grab each of these as we’ll use them in our message.

If – for some reason – we didn’t get a function name, we sleep (like I said, I like to sleep in these situations) and then head back to the start of our do…while loop.

// DESCRIPTION
$el = $xpath->query("//*/div[@class='refsect1 description']")->item(0);
$title = $xpath->query("./h3", $el)->item(0)->textContent;
$example = $xpath->query("./div", $el)->item(0)->textContent;
$description = $xpath->query("./p", $el)->item(0)->textContent;
$note = $xpath->query("./blockquote", $el)->item(0)->textContent;

$text = '';
$text .= '```' . $example . '```' . PHP_EOL;
$text .= $description . PHP_EOL;
if ($note) $text .= '>>>' . $note . PHP_EOL;

Having advanced this far, we target the “Description” section of the function documentation page, which has a class of refsect1 description.

Inside that div is an H3 that serves as the section title, a div that contains an example use of the function, a paragraph with a description, and an optional blockquote with notes about the function.  We target each of these and pull that content in, then we use them to build the test of our message.

It should be noted that we use markup to make sure that the example is displayed as a code block while the note(s) (if present) are displayed as a quote, mimicking their appearance on PHP.net.

       $fields[] = [
         'title' => $title,
         'value' => trim($text)
      ];

      $attachments[] = [
         'title' => $name,
         'title_link' => $url,
         'text' => $verinfo . PHP_EOL . $refpurpose,
         'fields' => $fields,
         'mrkdwn_in' => ['fields'],
      ];

      $msg = [
         'text' => '',
         'attachments' => $attachments,
         'username' => 'PHPBot',
         'icon_url' => 'https://example.com/slackbots/avatars/phpbot.png'
      ];
   }
} while (!$msg);

With all of the information we need acquired, we build our message.

The description section title and our formatted text from it are added as a Slack attachment field.  That attachment gets the function name as a title (linked to the documentation URL) and it’s text is the version and purpose text we grabbed early on.

We post this all as user “PHPBot” with a hosted avatar.  These two steps aren’t necessary as you can define your incoming webhook’s name and avatar, but we’re reusing a webhook for multiple purposes and, as such, define these for each.

Then we hit the end of our do…while loop.  We’ve assembled our message so we can move on.

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

Lastly, we actually send that message.  It’s just a cURL post to the Slack webhook URL (obscured here) and ends up posting a message that looks like this:

 

PHPBot example

As I said, there’s nothing too complex here.  If anything, this is probably over-complicated.  But it gives us something to talk about at 2:05 every day.


Last week another coworker posted to our developer channel asking that we all send a random photo of a Yak to one of our non-dev coworkers.  My immediate thought was that this was begging to be made into a Slackbot, and thus the YakBot was created.

<?php
$api_key = 'AbCdEfGhIjKlMnOpQrS1TuVw2xYzAbC3DeFgHiJk';
$search_engine_id = '012345678901234567890:abc0defghijk';

$channels = [
   '#developer-channel',
   'U197UV7HV',
   'U1989NZ12',
   'U198JH8GL',
   'U99EYR89S',
   'U198L05JG',
   'U198GH6H7',
   'U74JZ1KAY',
   'UAPFKFAHG',
   'U4U7VSABA',
];

$index = rand(1, 91);

$url = 'https://www.googleapis.com/customsearch/v1?q=yak&cx=' . $search_engine_id . '&imgSize=medium&safe=high&searchType=image&key=' . $api_key . '&start=' . $index;
$json = file_get_contents($url);
$data = json_decode($json);

$image_url = $data->items[0]->link;

$msg = [
   'text' => '',
   'attachments' => [
      [
         'image_url' => $image_url
      ]
   ],
   'username' => 'YakBot',
   'icon_url' => 'https://example.com/slackbots/avatars/yakbot.png'
];

$recipients = [];
foreach ($channels AS $channel) {
   $rand = rand(0, 9);
   if (!$rand) {
      $recipients[] = $channel;
   }
}

foreach ($recipients AS $recipient) {
   $msg['channel'] = $recipient;

   // send message
   $c = curl_init();
   curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
   curl_setopt($c, CURLOPT_POST, 1);
   curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
   curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($c, CURLOPT_HEADER, 0);
   curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
   curl_exec($c);
   curl_close($c);
}

We bring in the Google search API for this one in addition to the Slack API but overall it’s a bit simpler because there’s less of a message to build.

<?php
$api_key = 'AbCdEfGhIjKlMnOpQrS1TuVw2xYzAbC3DeFgHiJk';
$search_engine_id = '012345678901234567890:abc0defghijk';

$channels = [
   '#developer-channel',
   'U197UV7HV',
   'U1989NZ12',
   'U198JH8GL',
   'U99EYR89S',
   'U198L05JG',
   'U198GH6H7',
   'U74JZ1KAY',
   'UAPFKFAHG',
   'U4U7VSABA',
];

$index = rand(1, 91);

The first thing we do is define our Google search API key and the ID of the custom search engine that we’re using.  Then we build an array of possible recipients of our yak.  The recipients include our developer channel as well as individual user IDs, to whom the message would be sent in the form of a direct message from Slackbot.

Next we select a random number between 1 and 91.  This is because the Google search API won’t let you request a start point higher than 91 for some reason.  The search results return ten items, which means the most results you can get is 100.  We only need one and one 1/91 is close enough to 1/100 so I do the randomizing up front and then do the lookup and take the first response.

$url = 'https://www.googleapis.com/customsearch/v1?q=yak&cx=' . $search_engine_id . '&imgSize=medium&safe=high&searchType=image&key=' . $api_key . '&start=' . $index;
$json = file_get_contents($url);
$data = json_decode($json);

$image_url = $data->items[0]->link;

Here we actually do that lookup.  We get JSON back and decode it to pull out the first item listed.

$msg = [
   'text' => '',
   'attachments' => [
      [
         'image_url' => $image_url
      ]
   ],
   'username' => 'YakBot',
   'icon_url' => 'https://example.com/slackbots/avatars/yakbot.png'
];

Once we have our image URL, we build our message, which is relatively simple compared to the PHPBot message because all we’re doing is posting an attachment with an image.  As I mentioned above, we also define a custom username and avatar, though we don’t need to.

$recipients = [];
foreach ($channels AS $channel) {
   $rand = rand(0, 9);
   if (!$rand) {
      $recipients[] = $channel;
   }
}

foreach ($recipients AS $recipient) {
   $msg['channel'] = $recipient;

   // send message
   $c = curl_init();
   curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
   curl_setopt($c, CURLOPT_POST, 1);
   curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
   curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($c, CURLOPT_HEADER, 0);
   curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
   curl_exec($c);
   curl_close($c);
}

Finally, we wrap it up by determining who to send our yak to.  We loop through each of our channels and generate a random number between zero and nine.  If the number is zero, we add that channel to the list of recipients.

We do that because it allows for some randomness.  It’d get noisy if we were posting yaks too often or too regularly.

Then we loop through the list of recipients and send the message, defining the channel along the way.  The lucky winners end up getting something like this:

YakBot example

Posting to Slack via Git Hooks

At my day job our codebase is kept in a handful of self-hosted Git repositories. We have a tool that runs nightly, emailing out a digest of all of the previous day’s commits.

It’s kinda cool but I have the tendency to ignore it as a wall of text. I prefer more granular messaging and since we’re also using Slack, I saw an opportunity to do something with a post-receive hook to get a message as changes came in.

Posting from Git to Slack isn’t revolutionary. There are a tons of solutions for this out there. In fact, my original attempt just used a modified version of Chris Eldredge’s shell script, which I grabbed off of GitHub.  However, my Bash-foo is weak and we’re a PHP shop so I decided to write a solution based in PHP (though heavily based on Eldredge as I had that code in front of me).

To fire off the PHP script, the post-receive hook looks like this:

#!/bin/bash
while read oldrev newrev refname
	php git-slack-post.php --oldrev="$oldrev" --newrev="$newrev" --refname="$refname"
done

That’s simplified a bit as the actual hooks use an absolute path to the script but you see that the script accepts the oldrev, newrev, and refname arguments.

As for the PHP script itself, it looks a bit like this (I’ve sanitized some things to remove references to our internal services).

#!/usr/bin/php -f
<?php
// expects to be called as follows:
// php git-slack-post.php --oldrev="1234567890abcdef" --newrev="0987654321fedcba" --refname="refs/foo/bar"

const WEBHOOK_URL = 'https://hooks.slack.com/services/ABC123FDG/456HIJ789/tWeNtYfOuRcHaRaCtErS0024';
const ZEROREV = '0000000000000000000000000000000000000000';

// pull the arguments from the command line
$newrev = $oldrev = $refname = '';
extract(getopt('', array('oldrev:', 'newrev:', 'refname:')));

// determine change type based on what revisions are available
if ($oldrev === ZEROREV) {
	$change_type = 'create';
} elseif ($newrev === ZEROREV) {
	$change_type = 'delete';
} else {
	$change_type = 'update';
}

$newrev_type = trim(`git cat-file -t {$newrev}`);
$oldrev_type = trim(`git cat-file -t {$oldrev}`);

$rev = $rev_type = '';
if (in_array($change_type, array('create', 'update'))) {
	$rev = $oldrev;
	$rev_type = $oldrev_type;
} elseif ($change_type === 'delete') {
	$rev = $newrev;
	$rev_type = $newrev_type;
}

if ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'tag')) {
	$refname_type = 'annotated tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/heads/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'branch';
	$short_refname = str_replace('refs/heads/', '', $refname);
} elseif ((strpos($refname, 'refs/remotes/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tracking branch';
	$short_refname = str_replace('refs/remotes/', '', $refname);

	echo '*** Push-update of tracking branch, ' . $refname;
	echo '***  - no notification generated.';

	exit;
} else {
	// this shouldn't be possible
	echo '*** Unknown type of update to ' . $refname . ' (' . $rev_type . ')';
	echo '***  - no notification generated.';

	exit;
}

// get the repo name
// only get the final directory name, cut ".git" off the end
$repo = substr(basename(realpath(getcwd())), 0, -4);

// get the user
$process_user = posix_getpwuid(posix_geteuid());
$user = $process_user['name'];

$header = '[' . $repo . '/' . $short_refname . '] ';
switch ($change_type) {
	case 'create':
		$header .= 'New ' . $refname_type . ' has been created';
		break;
	case 'delete':
		$header .= ucwords($refname_type) . ' has been deleted';
		break;
	case 'update':
		$commits = intval(trim(`git log --pretty=oneline {$oldrev}..{$newrev}|wc -l`));
		$header .= $commits . ' new commit' . (($commits > 1) ? 's' : '') . ' pushed by ' . $user;
		break;
	default:
		// this shouldn't be possible
		echo '*** Unknown type of update to ' . $refname . '(' . $rev_type . ')';
		echo '***  - notifications will probably screw up.';
		break;
}

$start = ($change_type === 'update') ? $oldrev : 'HEAD';
$end = $newrev;

// get git data
$data = `git log --pretty=format:"%an&&&&&%h&&&&&%s&&&&&%b@@@@@" {$start}..{$end}`;

$attachments = array();
foreach (explode('@@@@@', $data) AS $item) {
	if (!trim($item)) {
		continue;
	}
	list($author, $hash, $comment, $body) = explode('&&&&&', $item);

	$text = trim('`' . $hash . '` ' . $comment . ' - *' . trim($author) . '*' . "\n\n" . $body);
	$fallback = trim($hash . ' - ' . $comment . ' - ' . trim($author) . "\n\n" . $body);

	// link jira tasks
	$text = preg_replace('#ABC-(\d+)#is', '<https://jira.example.com/browse/ABC-$1|ABC-$1>', $text);

	$text = preg_replace('#\[ABC\][ ]?\((\d+)\)#is', '<https://jira.example.com/browse/ABC-$1|$0>', $text);

	// link support bugs/tickets
	if (preg_match('#(TICKET|BUG|SUPPORT)[\-| ](\d+)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.php?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	if (preg_match('#\[HOTFIX\][ ]?\((\d+)\)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.mh?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	$attachments[] = array(
		'text'      => $text,
		'fallback'  => $fallback,
		'color'     => '#1881d0',
		'mrkdwn_in' => array('text'),
	);
}

// build message
$msg = array('text' => $header, 'attachments' => $attachments);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, WEBHOOK_URL);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

We get details about what’s being pushed and build a message out of all of that. Simple enough. So lets break that down a little bit.

const WEBHOOK_URL = 'https://hooks.slack.com/services/ABC123FDG/456HIJ789/tWeNtYfOuRcHaRaCtErS0024';
const ZEROREV = '0000000000000000000000000000000000000000';

// pull the arguments from the command line
$newrev = $oldrev = $refname = '';
extract(getopt('', array('oldrev:', 'newrev:', 'refname:')));

We start by defining our Slack webhook URL (which can be set up at https://my.slack.com/services/new/incoming-webhook) and what an “empty” revision number looks like. Then we pull in the oldrev, newrev, and refname that should have been passed into the script as arguments.

// determine change type based on what revisions are available
if ($oldrev === ZEROREV) {
	$change_type = 'create';
} elseif ($newrev === ZEROREV) {
	$change_type = 'delete';
} else {
	$change_type = 'update';
}

$newrev_type = trim(`git cat-file -t {$newrev}`);
$oldrev_type = trim(`git cat-file -t {$oldrev}`);

If the old revision is empty, it means we’re creating something. If the new revision is empty, it means we’re deleting something. Otherwise it’s an update of something that already existed and continues to do so.

Whatever the change type, we get more information about the old and new revisions by using the backtick operator to run the get cat-file -t command for each revision number.

$rev = $rev_type = '';
if (in_array($change_type, array('create', 'update'))) {
	$rev = $oldrev;
	$rev_type = $oldrev_type;
} elseif ($change_type === 'delete') {
	$rev = $newrev;
	$rev_type = $newrev_type;
}

If the change type is a create or an update, we’ll use the old revision data to reference things going forward. If it’s a delete we’ll use the new revision data.

if ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'tag')) {
	$refname_type = 'annotated tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/heads/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'branch';
	$short_refname = str_replace('refs/heads/', '', $refname);
} elseif ((strpos($refname, 'refs/remotes/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tracking branch';
	$short_refname = str_replace('refs/remotes/', '', $refname);

	echo '*** Push-update of tracking branch, ' . $refname;
	echo '***  - no notification generated.';

	exit;
} else {
	// this shouldn't be possible
	echo '*** Unknown type of update to ' . $refname . ' (' . $rev_type . ')';
	echo '***  - no notification generated.';

	exit;
}

This is just a bunch of logic that looks at the refname and the revision type and determines exactly what you’ve pushed. If we can’t figure out what it is, we exit with an error.

// get the repo name
// only get the final directory name, cut ".git" off the end
$repo = substr(basename(realpath(getcwd())), 0, -4);

// get the user
$process_user = posix_getpwuid(posix_geteuid());
$user = $process_user['name'];

We determine the repo name based on the path the hook is running from and we get the user it’s running as so we know who did the push we’re about to notify people of.

$header = '[' . $repo . '/' . $short_refname . '] ';
switch ($change_type) {
	case 'create':
		$header .= 'New ' . $refname_type . ' has been created';
		break;
	case 'delete':
		$header .= ucwords($refname_type) . ' has been deleted';
		break;
	case 'update':
		$commits = intval(trim(`git log --pretty=oneline {$oldrev}..{$newrev}|wc -l`));
		$header .= $commits . ' new commit' . (($commits > 1) ? 's' : '') . ' pushed by ' . $user;
		break;
	default:
		// this shouldn't be possible
		echo '*** Unknown type of update to ' . $refname . '(' . $rev_type . ')';
		echo '***  - notifications will probably screw up.';
		break;
}

Now we start building the message that will be posted to Slack. The message begins in the form of “[reponame/branchname]. If it’s a create or delete, we then note what was created or deleted. If it’s a commit, we note the number of commits and who they were pushed by.

$start = ($change_type === 'update') ? $oldrev : 'HEAD';
$end = $newrev;

// get git data
$data = `git log --pretty=format:"%an&&&&&%h&&&&&%s&&&&&%b@@@@@" {$start}..{$end}`;

$attachments = array();
foreach (explode('@@@@@', $data) AS $item) {
	if (!trim($item)) {
		continue;
	}
	list($author, $hash, $comment, $body) = explode('&&&&&', $item);

We’re going to start building a series of messages (what Slack calls “attachments”) detailing items from the Git log pertinent to this push. We use the git log command and define our format. We get the author name with %an, the hash with %h, the commit message with %s and the commit body with %b. Those are all separated by five ampersands, with each item separated by five at signs. We use those goofy separators so we can split on them later, as it’s unlikely anyone enters those in text.

	$text = trim('`' . $hash . '` ' . $comment . ' - *' . trim($author) . '*' . "\n\n" . $body);
	$fallback = trim($hash . ' - ' . $comment . ' - ' . trim($author) . "\n\n" . $body);

Here we actually build our message. The text property of a Slack attachment can be markdown, so we pretty it up a little bit. The fallback property is plaintext so it doesn’t get that formatting. The result is the hash, then the commit message, then the author. If there is a longer commit message, it gets added after that.

	// link jira tasks
	$text = preg_replace('#ABC-(\d+)#is', '<https://jira.example.com/browse/ABC-$1|ABC-$1>', $text);

	$text = preg_replace('#\[ABC\][ ]?\((\d+)\)#is', '<https://jira.example.com/browse/ABC-$1|$0>', $text);

	// link support bugs/tickets
	if (preg_match('#(TICKET|BUG|SUPPORT)[\-| ](\d+)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.php?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	if (preg_match('#\[HOTFIX\][ ]?\((\d+)\)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.mh?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

We’re not done with that text yet, though. We have a loosely-followed naming convention for our commits and we can use that to link back to other systems that might have more information about the commit. Anything that was a Jira task should start with the task number in the format “[ABC-1234]” but sometimes it’s “(ABC-1234)” or “[ABC] (1234)” or “[ABC](1234)” so we account for all of those. Similarly, references to our ticket system sometimes use “TICKET” or “BUG” or “SUPPORT” and sometimes have a space or a dash and sometimes use “HOTFIX” and… You get the idea. There are probably better regular expressions to use here but these work. So we find references to our Jira cards and link back to them, then find references to our support system and link back to it, where there’s a script that will do some additional parsing to figure out where to go.

	$attachments[] = array(
		'text'      => $text,
		'fallback'  => $fallback,
		'color'     => '#1881d0',
		'mrkdwn_in' => array('text'),
	);

With all of that done, we build an array for this attachment, defining our text, our fallback text, a color to display alongside the attachment, and confirming that there is markdown in our text field.

}

// build message
$msg = array('text' => $header, 'attachments' => $attachments);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, WEBHOOK_URL);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

Once we’re done looping through our data from the git log and building our attachments, we put together the message and send it off via Curl. The message is just an array, which then gets JSON-encoded and posted to our webhook URL.

It’s fire-and-forget, so we don’t make any note if the webhook doesn’t respond or anything like that.

For a short time we had an additional message attachment that included diff data but we decided we didn’t want our code getting posted to Slack so we removed it.

As I said, there are tons of solutions for this out there, this is just one more.

Reinventing the Wheel: Cache-Busting via Automated File Versioning

I have a tendency to like to reinvent the wheel in my development, just to prove that I can.

I’ve gotten better about it over the years.  I replaced my custom-built blog engine with WordPress on DetroitHockey.Net, for example.  I still roll my own classes for interacting with the Trello and Twitter APIs, though.

Lately I’ve been looking to solve asset caching issues.  If I make a change to main.js, for example, how do I make sure my users get the updated file?  This is something that I’m sure has been solved, but I wanted to give it a shot myself.

We can’t force a user to know that a file has changed and re-download it.  We can tell them how long to cache a file for, and the browser may or may not respect that, but we can’t interrupt that with a new file.

What we can do is version the file.  That’s not a huge deal.  Drop a suffix onto the file name (main.js becomes main-v2.js).  The problem is I’m lazy.  I don’t want to have to rename the file and update all of the places it’s used.  I want that to happen automatically if I happened to change the file.

Conveniently, my build process already had a point where it acted only if a CSS or Javascript file had changed.

I use BitBucket for my source control and BitBucket Pipelines for publishing.  There’s a step in my publish plan that sends a curl request to a listener on my server.  The listener takes a look at each CSS and JS file and minifies it, but it only saves the newly-minified file if it’s different from what’s already on the server.

So I’ve got a point where I know if the file has changed, what do I do with it?

I added a configuration file writable by that listener.  It’s an array stored as JSON tracking a version number for each asset file.  I then updated the references to those files in my site templates to make sure to use the version number from the configuration file is used.

The last step was to make the listener actually write to the configuration file.  The listener, as I said, checks to see if the newly-minified version of main.js (for example) is different from the existing minified version of main.js.  If it is, the existing file is deleted, the configuration file is updated with a new version number based on the time, and the new file is saved with that number.

I don’t have to rename anything.  I don’t have to change any links.  I don’t have to ask anyone to hard refresh.  So far, it works out pretty well.

Griffins Jersey Contest: Sour Grapes Again

Several years ago I embraced the sour grapes I had about losing the Grand Rapids Griffins’ jersey design contest and wrote about it.  Since I’ve been ranting about it all day on Twitter, I figured it was time to do that again.

I’ve repeatedly said that I put more thought than the Griffins intend into my entries in their contests.  This year was no different, as I was intrigued by the idea of what exactly makes a 1980s hockey jersey design.  I won’t rehash that all here, I included it all in my post about my entry.

I researched, I designed, I wrote about all of that because I’m genuinely curious.  Also I wanted to win, but I never expected to because there are way better designers than me out there.

So I did all of this research and I designed and I wrote and I published.  Genuinely curious about that question, I found no one willing to engage in discussion.  No one responded to me here.  Discussion at Uni-Watch, the contest host, seemed to center around not knowing what the Griffins actually wanted, not what a fauxback should actually look like.  The Griffins themselves provided no direction.

My design made it through a round of voting and was named as one of twelve finalists.  I posted a review of the finalists and the biggest piece of feedback was that the best designs didn’t make it to the finals.  While true, it doesn’t answer the question.

Then this morning the contest winner was announced and it’s something so far from what I consider 80s hockey jersey design that I’m completely at a loss.

It’s a nicely-rendered jersey, for sure.  But I can point to five reasons I don’t think it’s a proper 80s fauxback.  It also hits my previously-noted nerve about logos that only describe what they’re for literally, as there is no griffin on that jersey.

And when the world’s foremost expert in sports identities says you got it wrong, it probably means something.

So we have what the Griffins were looking for from their 80s fauxback contest but it’s so far from what I would expect that I still feel the need for discussion.  I want to know what the designer took his inspiration from.  I want to know what about that design the Griffins were particularly struck by.  But the Griffins aren’t giving details and the designer didn’t write a blog post about it like I did.

There’s this question out there I’m really curious about and I feel like the response is deafening silence.  It’s driving me nuts.

Griffins Jersey Design Contest – 80s Edition

As I’ve noted in the past, I have a love-hate relationship with the Grand Rapids Griffins’ annual jersey design contest.  I have the tendency to put more thought than they probably intend into my own entries and, while I was a finalist last year, I don’t think I ever come close to winning.

This year I was going to skip out on the endeavor entirely until they added an impossible new wrinkle to the contest: Design a 1980s “fauxback” jersey.

The Griffins were founded in 1996, hence the “fauxback” requirement.  The idea is to come up with a look that represents a Griffins team that existed in that decade.  Which raises an interesting question: What makes an 80s hockey jersey?

The shoulder yokes of the Minnesota North Stars, New Jersey Devils, and Buffalo Sabres all jumped out at me, though all but New Jersey’s found their origins in the 70s.  Likewise the 70s birthed the over-the-shoulder stripes of the Winnipeg Jets, Toronto Maple Leafs, and a handful of All-Star teams.

I thought about modern teams that throwback to the 80s and how their identities have changed since then.  The Edmonton Oilers’ logo is virtually untouched but changed from royal blue and orange to navy blue and copper (then back to royal blue and orange, and now navy and orange).  The Calgary Flames were bright red and yellow in the 80s, darkening their red and adding black in the 90s.  The Los Angeles Kings went from purple and yellow to black and silver.  The Devils went from red and green to red and black.

What makes an 80s hockey jersey?  It’s not so much the striping pattern or the logo…  It’s the colors.  The bright colors of 1980s hockey design were virtually abandoned in the 1990s.  In addition to the previously-mentioned changes, the 90s saw the North Stars go from bright green and yellow to forest green and metallic gold.  Like the Oilers, the New York Islanders abandoned royal blue and orange (only to – also like the Oilers – eventually return to it).  The Hartford Whalers gave up green and royal blue for green, silver, and navy.

Which is a problem for this contest because the Griffins explicitly stated that entrants should use the team’s current colors, with the jersey base color being red or black.

By my count, in the 1980s across the National Hockey League, the American Hockey League, and the International Hockey League, only the Chicago Blackhawks (and some of their affiliates) used a red, black, and white combination.  The metallic silver the Griffins now use was unseen in the NHL until the 90s, though the Kings added grey in 1988.  Teams in the 80s used yellow, not the gold in Grand Rapids’ current identity.

As such, I don’t think it’s possible to have an “80s fauxback” that uses the Griffins’ current colors.  That didn’t stop me from trying, though.

My first sketch used the Winnipeg/Toronto-style over-the-shoulder striping and a Edmonton Oilers-like logo, with a griffin silhouette over the word “Griffins” in a circle.  I also dropped gold and silver from the color scheme to simplify things.  The logo felt forced, though, and the striping seemed too modern, so I scrapped that idea.

Next I ignored the logo and tried a striping pattern based on the Buffalo Sabres.  The shoulders featured a black yoke with white and gold outlines. The sleeves and waist had a black/white/gold/white/black stripe set.  I actually like this idea a bit but, again, there was nothing that made me think “80s” so I moved on.

Just to see if it led to anything, I cloned the North Stars’ 1988 jersey set, swapping green for red and yellow for gold.  This led me to believe that the gold just wasn’t going to work and I needed to go back to red, white, and black.

Starting with a red jersey, I added thick stripes in white and black – separated by a thin red stripe – to the sleeves and waist.  I then included a yoke in black with a white outline.  I realized I had something similar to the Devils’ 1982 home uniform and decided it was 80s enough to move forward.  I also decided that between the North Stars, the New York Rangers, various NHL All-Star teams, and some of the 1988 Olympic teams, a drop-shadowed number represented the 80s pretty well, too.

For the logo, the Moncton Hawks jumped out at me, with their bird head and abstract wings inside a circle.  I tried going in that direction with a stylized wing attached to a griffin head with an outstretched claw, all merged together with the team’s name as a wordmark.  It didn’t look like a griffin to me so I decided to go in a different direction.

Next I went simple.  A griffin silhouette in a circle with “Grand Rapids Griffins” around it.  Specifically, this was based off of an old Muskegon Mohawks jersey that I couldn’t figure out the exact year for.  The wing came from the Detroit Red Wings’ logo while the tail was from the Griffins’ original logo as a pair of homages.  It looked more 1960s than 1980s, though, so I went back to the abstract griffin idea.

I removed the circle and added in the bottom half of the griffin’s body. This gave me an opportunity to reuse the tail from the Griffins’ original logo, as with the silhouette logo. Then I switched up the griffin’s head to look more like an eagle and less like a dragon.  At this point, I had my crest, and I decided not to worry about shoulder logos because, by and large, they weren’t used in the 80s.

While I’ve submitted the red, black, and white design, I still don’t think it’s 80s enough.  The colors make it look 90s to me.

I tried simplifying the design to just red and white and, while I think it looks more 80s, I don’t think it looks as good.  In the end, the design has to look good enough to win.

But I come back to the idea that, by forcing the modern colors, the Griffins have unintentionally made any submission less 1980s.  I tried the team’s colors from before their rebrand two years ago – red, white, and blue – and feel that it’s a design that screams 80s.  I just can’t use it.

Wrong colors or not, as I said at the start, I don’t expect to win this thing anyway.  Based on last year, the voters seem swayed by submissions that look like they came out of a catalogue or a video game, not flat designs in the template I use.  I don’t get to submit all the thought I put into it. But it will be interesting to see what the voters think an 80s jersey is.


Update 8/21/2017, 8:50 PM: My design is up for vote today, which gets me thinking about my design more.  I now think I should have included a circle behind the logo, as I originally attempted.  It helps make it look more 80s than 90s.

Though it’s too late for the contest, I’ve updated the red jersey I submitted to include the circle, added it to the blue variant that I think the team should actually wear, and created white versions of each.


View All Images as Gallery

Rebranding FantasyHockeySim.com

I launched FantasyHockeySim.com as a spinoff of DetroitHockey.Net last summer and the visual elements of it were a rush job.  Getting the site out the door was my priority, so I stole design elements for FHS from DH.N and put together a logo that didn’t say “fantasy hockey” at all.

Awhile ago I ranted about the Detroit Red Wings’ “Hockeytown” logo and how the only way it said “Hockeytown” was literally, with the text splashed across it.  I’d done the same thing with the FantasyHockeySim.com logo, as crossed sticks said “hockey” but the only way it said “fantasy hockey” was via the FHS acronym across the front.  Even then, it looked more like the logo for a high school hockey team than for simulated fantasy hockey software.

While stuck on a development project, I decided to take a crack at a new FHS logo.

Because I like shield-based logos far too much, my first pass centered around different shields.  Eventually I put together one that I really liked the look of and started building alternate logos around it.

I showed the “final” set (the shield logo and “promotional” versions featuring additional text) around and realized I hadn’t solved the problem I was trying to handle in the first place.  The logos still only said “fantasy hockey” literally, and even then it was only the promotional ones.

I stepped back from it and didn’t think about it for awhile until an idea came to me during my drive into work a couple weeks later.

Representing hockey in a logo is easy.  Sticks, pucks, all sorts of imagery is available.  How do you represent “simulation” though?  Well, simulation means computers and code and, even to a layman, X/HTML’s angle brackets are recognizable as code.  So I wrapped a pair of crossed hockey sticks in angle brackets and went from there.

The first issue I hit with the logo was that the crossed sticks looked a bit like an X, so I changed their position and added lines representing tape to the blades.  After that, I decided to give up on my attempt at a monochrome logo, changing the color of the angle brackets to help separate them from the sticks (with the added effect of appearing as syntax coloring).

As I worked on a primary version of the logo, I also created an alternate version and a “promotional” version.  The promotional logo features the “bracket” logo inside a roundel containing the “Simulated Fantasy Hockey” descriptor and the site name while the alternate is simplified version of that, without the text.

The biggest issue with the alternate and promotional logos was making sure the broken inner circle of the roundel didn’t appear like it was supposed to be attached to the angle brackets.  To handle that, I shrank down the width of that inner circle and made the break in it wider.  Changing the color of the brackets completed the effect.

The FHS site still needs a redesign to get away from borrowing so much from DH.N but at least now the logo is original and descriptive.