JOIN US Button — Complete Code Reference
This document contains all the code used to build the “JOIN US” button on the homepage of the Alamo Tech Collective Jekyll site, including the fade-in entrance animation and the hover glitch animation.
Stack
- Jekyll 3.9.3 (Liquid templating)
- SCSS (compiled by Jekyll)
- No JavaScript is required for this button — all animations are pure CSS.
1. HTML markup (Liquid template)
File: _layouts/home.html (lines 14–20)
<button class="btn hero-cta">
<a href="/" class="cyber-btn">
JOIN US<span aria-hidden>_</span>
<span aria-hidden class="cyber-btn__glitch">JOIN US</span>
</a>
<span aria-hidden class="btn__tag">B00</span>
</button>
Notes
- The link text
JOIN USis duplicated inside.cyber-btn__glitch. The duplicate sits on top of the original and is hidden until hover, when it becomes the animated glitch overlay. B00is the small tag in the bottom-right corner — purely decorative.- The button uses the default
.btnstyling (no.b-01/.b-02modifier class), so it falls through to the base.cyber-btnrules. .hero-ctais the class that drives the fade-in entrance animation.
2. Front matter that supplies the link
File: index.md (lines 6–9)
---
hero_title: San Antonio's Tech-Exclusive Hackerspace
hero_subtitle: Built for people who want to build, connect, and learn. No corporate noise. No gatekeeping. Just real community.
hero_cta_text: Join the Community
hero_cta_link: /get-involved
---
3. Hero fade-in (entrance animation)
File: _sass/_home.scss (lines 55–58)
.hero-cta {
opacity: 0;
animation: fadeInBtn 1s ease-in-out 2s forwards;
}
The button starts invisible. After a 2-second delay, it fades in over 1 second. forwards keeps it at opacity: 1 after the animation finishes.
4. Fade-in keyframes
File: _sass/_animations.scss (lines 1–8)
@keyframes fadeInBtn {
from { opacity: 0; }
to { opacity: 1; }
}
5. Cyber font + base button styles
File: _sass/_btn.scss (lines 1–201)
@font-face {
font-family: Cyber;
src: url("https://assets.codepen.io/605876/Blender-Pro-Bold.otf");
font-display: swap;
}
.btn {
position: relative;
background: transparent;
border: 0;
margin: 20px auto 30px;
.cyber-btn {
--shimmy-distance: 5;
--clip-one: polygon(0 2%, 100% 2%, 100% 95%, 95% 95%, 95% 90%, 85% 90%, 85% 95%, 8% 95%, 0 70%);
--clip-two: polygon(0 78%, 100% 78%, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 8% 100%, 0 78%);
--clip-three: polygon(0 44%, 100% 44%, 100% 54%, 95% 54%, 95% 54%, 85% 54%, 85% 54%, 8% 54%, 0 54%);
--clip-four: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
--clip-five: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
--clip-six: polygon(0 40%, 100% 40%, 100% 85%, 95% 85%, 95% 85%, 85% 85%, 85% 85%, 8% 85%, 0 70%);
--clip-seven: polygon(0 63%, 100% 63%, 100% 80%, 95% 80%, 95% 80%, 85% 80%, 85% 80%, 8% 80%, 0 70%);
font-family: 'Cyber', sans-serif;
position: relative;
background: transparent;
color: $cyan; // #70e1e8
border: 2px solid $cyan;
padding: 15px 50px;
font-weight: bold;
font-size: 25px;
overflow: hidden;
cursor: pointer;
clip-path: polygon( // chamfered cyberpunk outer shape
0 0,
100% 0,
100% 100%,
95% 100%,
95% 90%,
80% 90%,
80% 100%,
12% 100%,
0 60%
);
transition: all 200ms linear;
&:before { // small cyan triangle in bottom-left corner
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
border-bottom: 23px solid $cyan;
border-right: 23px solid transparent;
}
&__glitch { // hover-only animated overlay
position: absolute;
top: calc(4px * -1);
left: calc(4px * -1);
right: calc(4px * -1);
bottom: calc(4px * -1);
background: $cyan;
border: 2px solid $cyan;
padding: 15px 50px;
color: #000;
clip-path: polygon(
0 0,
100% 0,
100% 100%,
95% 100%,
95% 90%,
85% 90%,
85% 100%,
12% 100%,
0 60%
);
animation: glitch 2s infinite;
display: none; // hidden until parent is hovered
}
}
&__tag { // the "B00" tag in the corner
font-family: 'Cyber', sans-serif;
position: absolute;
letter-spacing: 1px;
bottom: -18px;
right: 20px;
color: $yellow; // #f7d55e
font-size: 10px;
z-index: 10;
}
&:hover {
.cyber-btn {
border: 2px solid transparent;
transition: all 200ms linear;
&:before {
border-bottom: 23px solid transparent;
border-right: 23px solid transparent;
transition: all 200ms linear;
}
}
.cyber-btn__glitch { display: block; } // reveals the glitch overlay
.btn__tag { color: $cyan; }
}
}
Notes on the variant classes (.b-01, .b-01_5, .b-02, .b-03)
The full _btn.scss file also contains these variant blocks. They override the clip-path of .cyber-btn to produce different chamfer widths in the bottom-left corner, and shift the position of .btn__tag accordingly. The JOIN US button does not use any variant — it uses the base .btn rules above.
6. Mobile button overrides
File: _sass/_btn.scss (lines 282–300)
@media (max-width: $breakpoint-mobile) { // 768px
.btn {
.cyber-btn {
padding: 15px 40px;
font-size: 20px;
clip-path: polygon(
0 0,
100% 0,
100% 100%,
95% 100%,
95% 90%,
80% 90%,
80% 100%,
15% 100%,
0 57.5%
);
}
&__tag {
bottom: -19px;
right: 15px;
}
}
}
7. Glitch keyframes (hover animation)
File: _sass/_animations.scss (lines 10–69)
This is what runs on the .cyber-btn__glitch overlay when the user hovers. It cycles through the seven --clip-* polygons defined on .cyber-btn and shimmies the overlay horizontally by --shimmy-distance (5%) to fake a CRT glitch.
@keyframes glitch {
0% {
clip-path: var(--clip-one);
}
2%, 8% {
clip-path: var(--clip-two);
transform: translate(calc(var(--shimmy-distance) * -1%), 0);
}
6% {
clip-path: var(--clip-two);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
9% {
clip-path: var(--clip-two);
transform: translate(0, 0);
}
10% {
clip-path: var(--clip-three);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
13% {
clip-path: var(--clip-three);
transform: translate(0, 0);
}
14%, 21% {
clip-path: var(--clip-four);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
25% {
clip-path: var(--clip-five);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
30% {
clip-path: var(--clip-five);
transform: translate(calc(var(--shimmy-distance) * -1%), 0);
}
35%, 45% {
clip-path: var(--clip-six);
transform: translate(calc(var(--shimmy-distance) * -1%));
}
40% {
clip-path: var(--clip-six);
transform: translate(calc(var(--shimmy-distance) * 1%));
}
50% {
clip-path: var(--clip-six);
transform: translate(0, 0);
}
55% {
clip-path: var(--clip-seven);
transform: translate(calc(var(--shimmy-distance) * 1%), 0);
}
60% {
clip-path: var(--clip-seven);
transform: translate(0, 0);
}
31%, 61%, 100% {
clip-path: var(--clip-four);
}
}
8. Color tokens used
File: _sass/_variables.scss
$black: #1d1d1d; // page background
$white: #ffffff;
$red: #d4675c;
$cyan: #70e1e8; // button border, text, glitch fill
$yellow: #f7d55e; // B00 tag (default state)
$primary-font: 'Helvetica Neue', Helvetica, Arial, sans-serif;
$secondary-font: 'Orbitron', sans-serif;
$breakpoint-mobile: 768px;
How it all fits together (timeline)
- Page load —
.hero-ctais invisible (opacity: 0). - 2s delay, then
fadeInBtnruns for 1s — button fades to fully visible. - Idle state — cyan-bordered, chamfered button reading
JOIN US_with a yellowB00tag in the corner. A small cyan triangle sits in the bottom-left corner (.cyber-btn:before). - Hover —
- The cyan border on
.cyber-btnbecomes transparent (200ms linear). - The bottom-left triangle becomes transparent.
.cyber-btn__glitchflips fromdisplay: none→display: block, revealing a cyan rectangle with blackJOIN UStext on top of the original.- The 2s infinite
glitchkeyframe animation runs on that overlay, swapping clip-paths and translating it horizontally to simulate a CRT/digital glitch effect. .btn__tagcolor flips from yellow to cyan.
- The cyan border on
- Mouse leaves — overlay returns to
display: none, border + triangle fade back in.
Minimum self-contained reproduction
If you want to drop this button into a non-Jekyll project, here is a minimal HTML + CSS version. Replace SCSS variables with their literal values.
<button class="btn hero-cta">
<a href="#" class="cyber-btn">
JOIN US<span aria-hidden="true">_</span>
<span aria-hidden="true" class="cyber-btn__glitch">JOIN US</span>
</a>
<span aria-hidden="true" class="btn__tag">B00</span>
</button>
@font-face {
font-family: Cyber;
src: url("https://assets.codepen.io/605876/Blender-Pro-Bold.otf");
font-display: swap;
}
@keyframes fadeInBtn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes glitch {
0% { clip-path: var(--clip-one); }
2%, 8% { clip-path: var(--clip-two); transform: translate(calc(var(--shimmy-distance) * -1%), 0); }
6% { clip-path: var(--clip-two); transform: translate(calc(var(--shimmy-distance) * 1%), 0); }
9% { clip-path: var(--clip-two); transform: translate(0, 0); }
10% { clip-path: var(--clip-three); transform: translate(calc(var(--shimmy-distance) * 1%), 0); }
13% { clip-path: var(--clip-three); transform: translate(0, 0); }
14%, 21% { clip-path: var(--clip-four); transform: translate(calc(var(--shimmy-distance) * 1%), 0); }
25% { clip-path: var(--clip-five); transform: translate(calc(var(--shimmy-distance) * 1%), 0); }
30% { clip-path: var(--clip-five); transform: translate(calc(var(--shimmy-distance) * -1%), 0); }
35%, 45% { clip-path: var(--clip-six); transform: translate(calc(var(--shimmy-distance) * -1%)); }
40% { clip-path: var(--clip-six); transform: translate(calc(var(--shimmy-distance) * 1%)); }
50% { clip-path: var(--clip-six); transform: translate(0, 0); }
55% { clip-path: var(--clip-seven); transform: translate(calc(var(--shimmy-distance) * 1%), 0); }
60% { clip-path: var(--clip-seven); transform: translate(0, 0); }
31%, 61%, 100% { clip-path: var(--clip-four); }
}
.btn {
position: relative;
background: transparent;
border: 0;
margin: 20px auto 30px;
}
.hero-cta {
opacity: 0;
animation: fadeInBtn 1s ease-in-out 2s forwards;
}
.cyber-btn {
--shimmy-distance: 5;
--clip-one: polygon(0 2%, 100% 2%, 100% 95%, 95% 95%, 95% 90%, 85% 90%, 85% 95%, 8% 95%, 0 70%);
--clip-two: polygon(0 78%, 100% 78%, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 8% 100%, 0 78%);
--clip-three: polygon(0 44%, 100% 44%, 100% 54%, 95% 54%, 95% 54%, 85% 54%, 85% 54%, 8% 54%, 0 54%);
--clip-four: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
--clip-five: polygon(0 0, 100% 0, 100% 0, 95% 0, 95% 0, 85% 0, 85% 0, 8% 0, 0 0);
--clip-six: polygon(0 40%, 100% 40%, 100% 85%, 95% 85%, 95% 85%, 85% 85%, 85% 85%, 8% 85%, 0 70%);
--clip-seven: polygon(0 63%, 100% 63%, 100% 80%, 95% 80%, 95% 80%, 85% 80%, 85% 80%, 8% 80%, 0 70%);
font-family: 'Cyber', sans-serif;
position: relative;
background: transparent;
color: #70e1e8;
border: 2px solid #70e1e8;
padding: 15px 50px;
font-weight: bold;
font-size: 25px;
overflow: hidden;
cursor: pointer;
text-decoration: none;
clip-path: polygon(0 0, 100% 0, 100% 100%, 95% 100%, 95% 90%, 80% 90%, 80% 100%, 12% 100%, 0 60%);
transition: all 200ms linear;
}
.cyber-btn:before {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
border-bottom: 23px solid #70e1e8;
border-right: 23px solid transparent;
}
.cyber-btn__glitch {
position: absolute;
top: -4px;
left: -4px;
right: -4px;
bottom: -4px;
background: #70e1e8;
border: 2px solid #70e1e8;
padding: 15px 50px;
color: #000;
clip-path: polygon(0 0, 100% 0, 100% 100%, 95% 100%, 95% 90%, 85% 90%, 85% 100%, 12% 100%, 0 60%);
animation: glitch 2s infinite;
display: none;
}
.btn__tag {
font-family: 'Cyber', sans-serif;
position: absolute;
letter-spacing: 1px;
bottom: -18px;
right: 20px;
color: #f7d55e;
font-size: 10px;
z-index: 10;
}
.btn:hover .cyber-btn {
border: 2px solid transparent;
transition: all 200ms linear;
}
.btn:hover .cyber-btn:before {
border-bottom: 23px solid transparent;
border-right: 23px solid transparent;
transition: all 200ms linear;
}
.btn:hover .cyber-btn__glitch { display: block; }
.btn:hover .btn__tag { color: #70e1e8; }
@media (max-width: 768px) {
.cyber-btn {
padding: 15px 40px;
font-size: 20px;
clip-path: polygon(0 0, 100% 0, 100% 100%, 95% 100%, 95% 90%, 80% 90%, 80% 100%, 15% 100%, 0 57.5%);
}
.btn__tag { bottom: -19px; right: 15px; }
}