The content scale effect has become a fairly popular design pattern across the web and mobile apps recently. This is the effect where the main content section of the UI is pushed, pulled, scaled, or rotated in some way to reveal additional information. In most cases it is used show and hide some sort of navigation or menu. Today I’d like to explore this effect in a slightly different context – image overlays.

View Demo

It is important to note that we will be using CSS3 transforms and transitions to achieve these effects so older browsers might not render them correctly.

In this example we’ll scale our image back while fading in the overlay and give the text inside a slight upward motion while fading it in. First, lets start with out basic thumbnail component structure:

  <li class="thumbnail">
    <div class="thumbnail__overlay">
      <h3 class="title"> </h3>
      <p class="caption"> </p>
      <a class="button"> Read more <span class="button__arrow"> &#8594 </span>
    <div class="thumbnail__image">
      <img src="...">
  <li class="thumbnail">
  <li class="thumbnail">

Next we can add initial styles:

.thumbnail {
  position: relative;
  overflow: hidden;

.thumbnail__image {
  transform-origin: bottom center;
  transition: transform 0.5s;

.thumbnail__overlay {
  position: absolute;
  width: 100%;
  height: 100%;
  padding: 25px;
  background-color: rgba(87,192,232,0.85);
  opacity: 0;
  transform: translateY(100%);
  transition: transform 0.5s, opacity 0.5s;

.thumbnail__overlay .title,
.thumbnail__overlay .caption,
.thumbnail__overlay .button {
  opacity: 0;
  transform: translateY(20px);
  transition: transform 0.5s, opacity 0.5s;
  transition-delay: 0.3s;

And then layer on our transition styles:

.thumbnail:hover .thumbnail__image {
  transform: scale(0.9);

.thumbnail:hover .thumbnail__overlay {
  transform: translateY(0%);

.thumbnail:hover .thumbnail__overlay .title,
.thumbnail:hover .thumbnail__overlay .caption,
.thumbnail:hover .thumbnail__overlay .button {
  opacity: 1;
  transform: translateY(0%);

Please check the demo for some more examples. I hope find this little experiment enjoyable and inspiring!


This question pops up a lot when I’m meeting with potential clients. They want to know about software maintenance costs. What happens after we build an app? How do they keep it working day-to-day? And how much manpower and budget will that eat up?

Let’s start with a simple truth: A custom app is never done. Just like you maintain your house and car, you’ll need to maintain an app. But this reality doesn’t have to be scary, budget busting or overly complicated. It’s just something you plan for from the start. We typically work with clients to choose one of four software maintenance options:

1. The Monthly Support Retainer

This is one of the most popular choices for our clients, and it’s an especially good option if you have a large code base. You’ll be able to call us to address any snags, and we’ll proactively work with you to identify and complete routine maintenance tasks.

We already know your software and team well, so we’ll be able to make this process incredibly efficient. This involves a flat monthly fee.

2. Hourly Support As Needed

If your budget doesn’t allow for a monthly retainer or your app is relatively small, you can continue to engage with the Gaslight team on an as-needed basis. We’ll charge you hourly for this support whenever you want to make a small upgrade, address a bug or need help with support requests. The only downside is that we can’t always promise turnaround as quickly as we do for monthly retainer clients.

3. Train Your Existing Staff

Do you already have an IT or development staff? We’re happy to set up training sessions to empower them to maintain and even upgrade your new software. In the past, we’ve done training sessions on everything from Agile development methodologies to the latest JavaScript framework. We can design a session tailored to your app and send one or two Gaslight team members to your company to conduct in-person, hands-on training.

4. Create a New Position

If your software supports a key business function, it might make sense to add a full-time position to maintain this new business asset. The trick is finding the right person to hire.

Since it’s hard to find development talent, recruiters have swooped in to help companies connect with tech talent. Unfortunately, many don’t have the knowledge required to make truly good matches.

What’s the answer? We’re happy to help you find and hire the right person. Our deep understanding of your software means we know exactly what skills it’s going to take to maintain it. Plus, we have a rich network in the tech community to draw on.

Ultimately, it’s about finding the best solution for your app and your company. And making sure your new technology remains a valuable asset for years to come.


Test-driven development is more art than science, and understanding what to test for comes only with experience. Further complicating things is that testing for the wrong stuff can create a suite of tests that are ugly, brittle, and provide false-positive passing tests.

One of the keys to clean and reliable unit tests is the idea of testing for behavior vs. testing for implementation. The differences between these two characteristics are subtle, but really important to understand. Here is a simplified example showing this difference.

When I test for behavior, I’m saying:

“I don’t care how you come up with the answer, just make sure that the answer is correct under this set of circumstances”

When I test for implementation, I’m saying:

“I don’t care what the answer is, just make sure you do this thing while figuring it out.”

In actual practice, what ends up happening is that a test looks for the result of the method (good), but it also makes assertions about how that answer was derived (not-so-good) by relying too much on mock and stubs. Its the mocks and stubs that get you into trouble as time passes and the system evolves.

Testing implementation vs. Supporting implementation

I don’t mean to imply that mocks and stubs are bad. They are incredibly useful in making good tests, and any meaningful test needs some knowledge of how the method actually works. Stubbing dependencies allows tests to run with different inputs to ensure code is in spec under different scenarios. This is supporting implementation. It turns bad when too many assertions start popping up on those stubs.

An Exception

Sometimes, testing behavior and testing implementation are one in the same. This happens a lot in coordinator methods, whose purpose is to make a decision, and then delegate control (to a different class or method) accordingly. Since the behavior of this method is delegation, the only way you can test it is to assert that the correct stub was called.


Lets use an example to tie all this together. Below is a method called FindTerminals, whose job is to find a list of computers in the database (via the TerminalService object), then delegate to the ContactTerminal method for each one to see if its online. Here is what the method looks like:

void FindTerminals() {
    terminals = terminalService.FindAll();
    foreach (var terminal in terminals)

And here is the test for this method. Its pretty straight-forward. The TerminalService object is given a list of 3 terminals to substitute for the FindAll method, then we test that each terminal is contacted by counting the number of times the ContactTerminal method is called. Here’s that:

var terminals = new List<Terminal> {
    new Terminal(), new Terminal(), new Terminal() };           
    .Setup(f => f.FindAll())
var activeTerminalLocatorMock =
    new Mock<ActiveTerminalLocator>(


    f => f.ContactTerminal(It.IsAny<Terminal>()),

So all is well and good. We get terminals, we contact terminals, the test is passing, everyone is happy. Except that if a terminal is turned off, we find our selves waiting up to 30 seconds per to time out, which quickly adds up while the user is standing there waiting for this thing to happen. As a convenience, we’re going to change this method to contact each terminal asynchronously so that all terminals are contacted at once:

void FindTerminals() {
    terminals = terminalService.FindAll();
    foreach (var terminal in terminals) {
        using(var worker = workerFactory.CreateWorker(){
            worker.DoWork += 
                (o, ea) => ContactTerminal(terminal);

And the test continues to pass! A fake worker factory is already being injected into the class for the method to use, and I don’t intend to make assertions with it. The purpose of this method is not to do something asynchronously, but to contact each of the given terminals. That goal is reached regardless of the contacts being made inline or not - its just an implementation detail.

So here’s what I can take away from this example:

  • The implementation has changed from running code inline to running it async
  • The behavior (or result of the method) has not changed
  • The test has not changed

This is a good test. If a change in implementation triggers significant changes to a test (without a corresponding change in behavior), then there is a good chance the test is not providing a lot of value.