Solving a CSS Brainteaser.

October 12, 2020

Jackson Gabbard posted this brainteaser and I decided to take a crack at it!

The rules!

  • One <a> tag.
  • No JavaScript.
  • No images.
  • No SVGs.
  • No box shadow!

The start!

<a class="magic" href="#"></a>

The goal!

10 minutes later…

Okay, this is harder than I thought it would be.

30 minutes…

I ran into some trouble with a clipped top and bottom on the inner circles.

circle button with partially flat sides on the top and bottom.
circle button with partially flat sides on the top and bottom.

I fixed this with a background-origin that expands the bounds of the background gradients:

background-origin: border-box;

40 minutes…

I'm so close! But the question mark is vibrating during the transition…

Maybe I just need the right font?

I took a dive into this great writeup on CSS fonts and line heights. That got me thinking that my text vibration problem might be due to the font's content-area vs. line-height. Maybe this will all work better if I use the same font as Jackson? I might as well try to make an exact replica anyway.

After trying a bunch of things, I ended up using a screenshot of his question mark and likefont to find Ankora.

screenshot of the result from en.likefont.com
screenshot of the result from en.likefont.com

… but it's still vibrating

A couple hours later…

I fixed the text vibration! by switching to firefox.

The last stretch…

After fixing the vibration, the text is now shifting slightly down during the transition… and I noticed that the border animation is staggered instead of happening all at once.

I fixed both of these problems by changing:

transition: all 0.25s;

to:

transition-duration: 0.25s;
transition-property: font-size, line-height;

I only need to transition the font-size and line-height to keep the question mark the same size on hover. These will scale down in size to counter the increase from the parent element. The other changes to the :after element on hover should be immediate.

My solution!

With code!

<a class="magic" href="#"></a>

<style>
@font-face {
  font-family: Ankora;
  src: url(ANKOR___.ttf);
}
:root {
  --scaling: 1.075;
  --transition: 0.25s;
  --width: 200px;
  --border-width: 11px;
  --hover-border-width: 1px;
  --in-width: calc(var(--width) - var(--border-width) * 2 - var(--hover-border-width) * 2);
  --in-in-width: calc(var(--in-width) - var(--border-width) * 2 - var(--hover-border-width) * 2);
  --font-factor: calc(var(--width) / 2);
  --green: Lime;
}
.magic {
  backface-visibility: hidden;
  background: linear-gradient(0deg, #888 0%, #444 100%);
  border-radius: 50%;
  display: block;
  font-family: Ankora;
  height: var(--width);
  position: relative;
  text-decoration: none;
  transition: transform var(--transition);
  width: var(--width);
}
.magic:hover {
  transform: scale(var(--scaling));
  transform-origin: center;
}
.magic:before {
  backface-visibility: hidden;
  background: linear-gradient(0deg, #bbb 0%, #eee 100%);
  background-origin: border-box;
  border: var(--hover-border-width) solid transparent;
  border-radius: 50%;
  content: "";
  display: inline-block;
  height: var(--in-width);
  left: var(--border-width);
  position: absolute;
  top: var(--border-width);
  width: var(--in-width);
}
.magic:hover:before {
  border: var(--hover-border-width) solid var(--green);
}
.magic:after {
  backface-visibility: hidden;
  background: linear-gradient(0deg, #888 0%, #444 100%);
  background-origin: border-box;
  border-radius: 50%;
  color: #ccc;
  content: "?";
  font-size: var(--font-factor);
  height: var(--in-in-width);
  left: var(--border-width);
  line-height: calc(1.1 * var(--in-in-width));
  margin: calc(var(--border-width) + var(--hover-border-width) * 2);
  position: absolute;
  text-align: center;
  top: var(--border-width);
  transition-duration: var(--transition);
  transition-property: font-size, line-height;
  width: var(--in-in-width);
}
.magic:hover:after {
  border: var(--hover-border-width) solid var(--green);
  color: var(--green);
  font-size: calc(var(--font-factor) / var(--scaling));
  line-height: calc(1.1 * var(--in-in-width) - var(--hover-border-width));
  margin: calc(var(--border-width) + var(--hover-border-width));
}
</style>