input(type='checkbox' name='special-mask' value='true' id='special-mask' class='special-mask option')
label(for='special-mask' class='option') ✨ Golden mask

input(type='checkbox' name='talking-mask' value='true' id='talking-mask' class='talking-mask option')
label(for='talking-mask' class='option') 💬 Talking mask

- var emotions = ['neutral','angry','sad','shocked','confused'];
- var emoji = ['😐', '😡', '😞', '😨', '🤔'];
- for (var e = 0; e < emotions.length; e++)
  input(type='radio' name='mask-emotions' value='true' id='mask-emotions--'+emotions[e] class='mask-emotions--'+emotions[e]+' option' checked=e===0)
  label(for='mask-emotions--'+emotions[e] class='option')
    = emoji[e] + ' ' + emotions[e]

.aku-aku.star
  .mask-wrapper.star
    .feathers
      .feather
      .feather
      .feather
      .feather
    .mask
      .nose
      .eye.eye--left
        .brow.brow--left
      .eye.eye--right
        .brow.brow--right
      .goatee
      .mouth
        .teeth
View Compiled
$u: 80vmin; // Base unit

@mixin transition($props) {
  will-change: unquote($props);
  transition-property: unquote($props);
  transition-duration: .35s;
  transition-timing-function: ease-in-out;
}

:root {
  --lip: .01em;
  --brow-offset: .035em;
  --mouth-basis: .111em;
  --mouth: var(--mouth-basis);
  --bg1: #001195;
  --bg2: #0c0c39;
  --wood: #572f1c;
  --feather1: #55428d;
  --feather2: #d6b644;
  --feather3: #cb8138;
  --feather4: #b8346e;
  --brows: #c94225;
  --eyes: #d6b644;
  --eyelids: #4aa759;
  --teeth: #e5d2cf;
  --goatee: #4aa85a;
  --rotate-mask: 0deg;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  font-size: 16px;
  overflow: hidden;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0;
  background-image: radial-gradient(circle at center, var(--bg1), var(--bg2));
}

/* Here's where the magic starts */

.aku-aku {
  position: relative;
  width: .589em;
  height: 1em;
  font-size: $u;
  perspective: 1em;
  animation: maskFloat 2s ease-in-out alternate infinite;
  transform-style: preserve-3d;
  will-change: transform;
  
  div { // Save ourselves some repetition…
    position: absolute;
  }
}

.mask-wrapper {
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  transform-origin: 43.4% 50%;
  transform: rotateY(var(--rotate-mask));
  @include transition('transform');
}

.feathers {
  left: 0;
  top: 0;
  width: 100%;
  height: .5em;
}

.feather {
  bottom: 0;
  width: .1417em;
  clip-path: polygon(
    50%  0,
    100% 30%,
    50%  100%,
    0    32%
  );
  transform-origin: center bottom;
  transform: rotate(var(--feather-angle, 0deg));
  animation: feather 2s ease-in-out alternate infinite;
  @include transition('background-color');
  
  &:nth-child(1) {
    --feather-angle: -30deg;
    left: .122em;
    height: .375em;
    background-color: var(--feather1);
    animation-delay: -1.5s;
  }
  
  &:nth-child(2) {
    --feather-angle: 0deg;
    left: .16em;
    height: .5em;
    background-color: var(--feather2);
    animation-delay: -.5s;
  }
  
  &:nth-child(3) {
    --feather-angle: 23deg;
    left: .21em;
    height: .41em;
    background-color: var(--feather3);
  }
  
  &:nth-child(4) {
    --feather-angle: 50deg;
    left: .278em;
    height: .319em;
    background-color: var(--feather4);
    animation-delay: -1s;
  }
}

.mask {
  left: .0833em;
  top: .433em;
  width: .336em;
  height: .4027em;
  
  &::before { // In a pseudo-element so the clip doesn't affect the entire mask
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: var(--wood);
    clip-path: polygon(
      0    0,
      100% 10%,
      90%  95%,
      10%  100%
    );
    @include transition('background-color');
  }
}

.nose {
  left: .117em;
  top: .181em;
  width: .106em;
  height: .15em;
  background-color: var(--feather3);
  clip-path: polygon(
    50%  0,
    100% 71%,
    50%  100%,
    0    74%
  );
  @include transition('background-color');
}

.eye {
  top: .0917em;
  width: .156em;
  height: .156em;
  
  &::before { // In a pseudo-element so the animation doesn't affect the eyebrows
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-color: var(--eyes);
    clip-path: polygon(0 10%, 100% 10%, 100% 100%, 0 100%); // Clip the top part of the eyes under the eyebrow
    transform-origin: 50% 12.5%;
    animation: blink 3s ease-in-out alternate infinite;
    @include transition('background-color, transform, box-shadow');
  }
  
  &--left {
    left: -.002em;
    
    &::before {
      border-radius: 50% 25% 50% 50%;
      box-shadow: inset .0125em  -.025em 0 var(--eyelids);
    }
  }
  
  &--right {
    right: -.008em;
    
    &::before {
      border-radius: 25% 50% 50% 50%;
      box-shadow: inset -.0125em -.025em 0 var(--eyelids);
    }
  }
}

.brow {
  width: 120%;
  height: 50%;
  background-color: var(--brows);
  @include transition('transform, background-color');
  
  &--left {
    left: -15%;
    top: -8%;
    transform-origin: calc(50% + var(--brow-offset)) 125%;
    clip-path: polygon(
      0                   .002em,
      calc(100% - .002em) 0,
      100%                70%,
      .002em              100%
    );
  }
  
  &--right {
    right: -15%;
    top: -4%;
    transform-origin: calc(50% - var(--brow-offset)) 125%;
    clip-path: polygon(
      .002em              0,
      calc(100% - .002em) .002em,
      100%                100%,
      0                   70%
    );
  }
}

.mouth {
  left: -.008em;
  top: .328em;
  width: .353em;
  height: var(--mouth);
  background-color: var(--brows);
  clip-path: polygon(
    .035em              0,
    calc(50% + .001em)  .02em,
    calc(100% - .037em) 0,
    100%                100%,
    0                   100%
  );
  @include transition('height, background-color'); // Yeah, terrible thing to do, I know - I don't do this on actualy websites, I promise!
}

.teeth {
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  width: calc(100% - var(--lip) * 9);
  height: calc(100% - var(--lip) * 9);
  margin: auto;
  background-color: var(--teeth);
  clip-path: polygon(
    var(--lip)              0,
    calc(50% + .001em)      var(--lip),
    calc(100% - var(--lip)) 0,
    100%                    100%,
    0                       100%
  );
  @include transition('background-color');
}

.goatee {
  top: .42em;
  left: 0;
  right: 0;
  width: .156em;
  height: .111em;
  margin: 0 auto;
  
  &::before,
  &::after {
    content: '';
    position: absolute;
    top: 0;
    border-style: solid;
    border-color: var(--goatee) transparent transparent;
    @include transition('border-color');
  }
  
  &::before {
    left: calc(45% - .063em);
    border-width: .11em .063em 0 .063em;
    filter: brightness(.95);
  }
  
  &::after {
    left: calc(65% - .047em);
    border-width: .092em .047em 0 .047em;
  }
}

// Light animations…
@keyframes maskFloat {
  from { transform: translateY(-.025em) rotate(-1deg); }
  to { transform: translateY(.025em) rotate(1deg); }
}

@keyframes blink {
  0% { transform: scaleY(1); }
  5% { transform: scaleY(.25); }
  10% { transform: scaleY(1); }
}

@keyframes feather {
  from { transform: rotate(calc(var(--feather-angle) - 2deg)); }
  to { transform: rotate(calc(var(--feather-angle) + 2deg)); }
}

@keyframes talking {
  0% { height: var(--mouth); }
  10% { height: calc(var(--mouth) * .9); }
  20% { height: calc(var(--mouth) * 1.1); }
  30% { height: var(--mouth) ; }
  40% { height: calc(var(--mouth) * 1.2); }
  50% { height: var(--mouth); }
  60% { height: calc(var(--mouth) * .8); }
  70% { height: calc(var(--mouth) * 1.15); }
  80% { height: var(--mouth); }
  90% { height: calc(var(--mouth) * .9); }
  100% { height: var(--mouth); }
}

@keyframes stars {
  from {
    opacity: 1;
    transform: translate(0, 0);
  }
}

// Here are the variations depending on the options checked (default is 'neutral')
.special-mask:checked ~ .aku-aku {
  --wood: #92572d;
  --feather1: #d97937;
  --feather2: #d6b644;
  --feather3: #d97937;
  --feather4: #d6b644;
  --brows: #c74a26;
  --eyes: #eaba58;
  --eyelids: #4aa85a;
  --teeth: #f7c06e;
  --goatee: #cb8138;
  --rotate-mask: 360deg;
  
  &,
  > .star {
    &::before,
    &::after {
      content: '✨';
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      width: 2rem;
      height: 2rem;
      margin: auto;
      font-size: 2rem;
      opacity: 0;
      animation: stars .75s ease-out infinite forwards;
      z-index: -1;
    }
  }

  &::before {
    transform: translate(#{$u * -.75}, #{$u * 1.1});
  }

  &::after {
    transform: translate(#{$u * 1.25}, #{$u * -.8});
    animation-delay: -.75s;
  }
  
  > .star {
    &::before {
      transform: translate(#{$u * -1.125}, #{$u * -.9});
      animation-delay: -.5s;
    }

    &::after {
      transform: translate(#{$u * .75}, #{$u * 1.2});
      animation-delay: -.25s;
    }
  }
}

.talking-mask:checked ~ .aku-aku {
  @include transition('--mouth');
  
  .mouth {
    animation: talking 2s ease-in-out infinite none;
  }
}

.mask-emotions--angry:checked ~ .aku-aku {
  --mouth: calc(var(--mouth-basis) * .9);
    
  .brow--left {
    transform: rotate(10deg);
  }
  
  .brow--right {
    transform: rotate(-10deg);
  }
}

.mask-emotions--sad:checked ~ .aku-aku {
  --mouth: calc(var(--mouth-basis) * 1.1);
  
  .brow--left {
    transform: translateX(calc(var(--brow-offset) * .5)) rotate(-10deg);
  }
  
  .brow--right {
    transform: translateX(calc(var(--brow-offset) * -.5)) rotate(10deg);
  }
}

.mask-emotions--shocked:checked ~ .aku-aku {
  --mouth: calc(var(--mouth-basis) * 1.5);
  
  .brow--left {
    transform: translateY(calc(var(--brow-offset) * -.5)) rotate(-7deg);
  }
  
  .brow--right {
    transform: translateY(calc(var(--brow-offset) * -.5)) rotate(7deg);
  }
}

.mask-emotions--confused:checked ~ .aku-aku {
  --mouth: calc(var(--mouth-basis) * 1.05);
  
  .brow--left {
    transform: translateY(calc(var(--brow-offset) * -.5)) rotate(-7deg);
  }
  
  .brow--right {
    transform: translateX(calc(var(--brow-offset) * -.5)) rotate(10deg);
  }
}

/* And here's where the magic ends */

input,
label {
  position: fixed;
  top: .5rem;
  color: #fff;
}

input {
  left: .5rem;
}

label {
  left: 2rem;
  text-transform: capitalize;
}

@for $o from 1 through 7 {
  .option:nth-of-type(#{$o}) {
    $offset: $o - 1;
    top: #{$offset * 2rem + .5rem};
  }
}
View Compiled
// Inspired by the wonderful masks by Elio Qoshi: https://dribbble.com/shots/4127868-Minimal-Aku-Aku-Illustration

CSS.registerProperty({
	'name': '--mouth', 
	'syntax': '<length>', 
	'initialValue': '.111em'
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.