Drawing Bill Cipher in CSS

Image from Gravity Falls Fandom page.

Setting the stage

<div class="canvas"></div>
html, body {
background: #999;
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.canvas {
width: 1px;
height: 1px;
position: relative;
}

The body

<div class="body"></div>

CAUTION: The standard definition for clip-path includes a path function. Unfortunately, it is not supported by any major browser at the moment. Paths can still be used, but they need to be defined inside an SVG and imported with the url function.

.body {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 40vmin;
height: 40vmin;
background: #ff5;
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}

NOTE: we are using the vmin unit. This is a relative unit that will make our drawing responsive: it will grow in larger screens and shrink in smaller ones.

The base for Bill’s body
.body {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 40vmin;
height: 40vmin;
background: #ff5;
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
/* first the horizontal lines, then the vertical lines */
background-image:
repeating-linear-gradient(to top, transparent 0 30%, #dd2 0 33%),
repeating-linear-gradient(to right, transparent 0 40%, #dd2 0 41%),
repeating-linear-gradient(to right, transparent 0 25%, #dd2 0 26%),
repeating-linear-gradient(to right, transparent 0 30%, #dd2 0 31%);
background-position:
bottom center,
bottom center,
0% 91%,
0% 81%; /* this should be 0% 82%, but then it looks weird :S */
background-size:
100% 27%,
100% 9%,
100% 9%,
100% 9%;
background-repeat:
repeat-x; /* only one value, applies to all */
}
Bill’s body with some “brick texture”

NOTE: The final version of this drawing (see below) combines repeating-linear-gradients and linear-gradients. You could do the same and practice both.

The eye and the bowtie

<div class="body">
<div class="bowtie"></div>
<!-- we'll see this two in a second -->
<div class="eyelashes"></div>
<div class="eye"></div>
</div>

NOTE: when specifying a position, it is calculated from the closest positioned ancestor or the viewport (for fixed). That’s why the body needs –at least– a relative position. (told you it was going to be important 😋).

.bowtie {
position: absolute;
bottom: 18%; /* so it matches the top brick line*/
left: 50%;
transform: translate(-50%, 50%); /* so it's centered */
width: 22%;
height: 12%;
background: black;
clip-path: polygon(0% 0%, 50% 42%, 100% 0%, 100% 100%, 50% 58%, 0% 100%);
}
Bill’s body looking classy with a bowtiw
.eye {
width: 25%;
height: 25%;
background: white;
border: 0.35vmin solid black;
border-radius: 2% 80% 2% 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
}
.eye::before {
content: "";
display: block;
position: absolute;
width: 14%;
height: 50%;
background: black;
top: 45%;
left: 55%;
transform: translate(-50%, -50%) rotate(45deg);
border-radius: 100%;
}
We are almost done with Bill’s body

NOTE: this conic-gradient is so we can practice it. As you will see in the result, it doesn't look that great, so we will undo it later and try a different approach.

.eyelashes {
position: absolute;
width: 35%;
height: 30%;
border-radius: 50%;
top: 49%;
left: 50%;
transform: translate(-50%, -50%);
background-image:
conic-gradient(
transparent 3%, black 0 4%,
transparent 0 9%, black 0 10%,
transparent 0 40%, black 0 41%,
transparent 0 46%, black 0 47%,
transparent 0 53%, black 0 54%,
transparent 0 59%, black 0 60%,
transparent 0 90%, black 0 91%,
transparent 0 96%, black 0 97%,
transparent 0
)
}
This eyelashes do not look that great
.eyelashes {
abackground: #f007;
position: absolute;
width: 35%;
height: 30%;
border-radius: 50%;
top: 49.25%;
left: 50%;
transform: translate(-50%, -50%);
background-image:
conic-gradient(
transparent 3%, black 0 4%,
transparent 0 9%, black 0 10%,
transparent 0 40%, black 0 41%,
transparent 0 46%, black 0 47%,
transparent 0 53%, black 0 54%,
transparent 0 59%, black 0 60%,
transparent 0 90%, black 0 91%,
transparent 0 96%, black 0 97%,
transparent 0
);
background-image:
linear-gradient(57deg, transparent 29%, black 0 31%, transparent 0),
linear-gradient(57deg, transparent 71%, black 0 73%, transparent 0),
linear-gradient(72deg, transparent 43%, black 0 45%, transparent 0),
linear-gradient(72deg, transparent 56%, black 0 58%, transparent 0),
linear-gradient(106deg, transparent 56%, black 0 58%, transparent 0),
linear-gradient(106deg, transparent 43%, black 0 45%, transparent 0),
linear-gradient(120deg, transparent 71%, black 0 73%, transparent 0),
linear-gradient(120deg, transparent 29%, black 0 31%, transparent 0);
background-size:
100% 50%;
background-position:
top center,
bottom center;
background-repeat: no-repeat;
}

NOTE: we didn’t remove the old background-image with the radial-gradient. In CSS, you can have the same property several times in a rule; the last valid one is the one that will be applied. It's like we are keeping the radial-gradient background as a fall back (although we don't really need it.)

We completed Bill’s body, time to do the rest!

The hat

.hat {
width: 4vmin;
height: 18vmin;
background: black;
position: absolute;
top: -19.5vmin;
left: 0.5vmin;
transform: translate(-50%, -100%) rotate(3deg)
}
.hat::before {
content: "";
display: block;
position: absolute;
width: 280%;
height: 1.5vmin;
background: black;
bottom: 0;
left: -90%;
}
Bill starts to look more like Bill now

The legs

<div class="leg"></div>
.leg {
width: 6vmin;
height: 15vmin;
border: 3vmin solid transparent;
border-left: 2vmin solid black;
border-bottom: 2vmin solid black;
border-radius: 50% 10% 50% 50%;
transform: skewY(-30deg);
position: absolute;
top: 13vmin;
left: -9vmin;
}
.leg::before {
content: "";
display: block;
position: absolute;
width: 2.5vmin;
height: 8vmin;
background: black;
transform: skewY(30deg);
border-radius: 50%;
top: 90%;
right: -0.85vmin;
}

CAUTION: An element can have more than one transform, and the transforms’ order affects the result. In this case, the scaleX(-1) must be the first one!

.leg + .leg {
top: 13vmin;
left: -1vmin;
transform: scaleX(-1) rotate(10deg) skewY(-30deg);
}
Beware! Bill now has legs!

The arms

.arm-1 {
position: absolute;
right: 18.5vmin;
top: 3vmin;
width: 3vmin;
height: 12vmin;
border: 1.75vmin solid black;
border-top: 2.25vmin solid black;
border-radius: 100% / 70% 70% 120% 120%;
transform: rotate(-90deg);
}
.arm-1::after {
content: "";
display: block;
position: absolute;
top: 70%;
right: 70%;
width: 6vmin;
height: 4vmin;
background: black;
border-radius: 50%;
transform: rotate(55deg);
}
.arm-2 {
width: 17vmin;
height: 16vmin;
border: 1.75vmin solid transparent;
border-right-color: black;
border-bottom-color: black;
border-radius: 50%;
position: absolute;
top: -6.5vmin;
left: 10vmin;
}
.arm-2::before {
content: "";
display: block;
position: absolute;
width: 6vmin;
height: 5vmin;
background: black;
border-radius: 50%;
left: 86%;
top: -5%;
}
.arm-2::after {
content: "";
display: block;
position: absolute;
top: -20%;
left: 63%;
width: 4vmin;
height: 6vmin;
border-radius: 50% 40% 40% 40%;
box-shadow: 2vmin 0 0 -0.5vmin;
transform: rotate(-13deg);
}
Bill is complete!

The glow

CAUTION: all the browsers do not support the drop-shadow filter, so it may not be visible in some of them.

.canvas {
width: 1px;
height: 1px;
position: relative;
filter: drop-shadow(0 0 1vmin #ffc);
}

Conclusion

NOTE: In a previous version of this post, I described how to draw Bill using a slightly different code. Unfortunately, while it worked fine on Chrome, Firefox, or Edge (new), it didn’t work correctly on Safari.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store