mixin cube(className)
  .cube(class=className)
    - let s = 0
    while s < 6
      .cube__side
      - s++
input#toast(type='checkbox')
.rotater.rotater--top
.rotater.rotater--right
.rotater.rotater--bottom
.rotater.rotater--left
.scene
  .plane
    +cube('table')
    .toaster__shadow
    label.toaster(for='toast')
      .toast__wrapper
          .cube.toast
            .cube__side
            .cube__side
              .toast__face
                .eyes
                  .eye.eye--left
                  .eye.eye--right
                .mouth
            .cube__side
            .cube__side
            .cube__side
            .cube__side
      .toaster__toaster
        +cube('toaster__leg toaster__leg--tl')
        +cube('toaster__leg toaster__leg--tr')
        +cube('toaster__leg toaster__leg--br')
        +cube('toaster__leg toaster__leg--bl')
        .cube.toaster__body
          .cube__side
            img(src="https://assets.codepen.io/605876/bear-with-cap.svg")
            .toaster__face
              .eyes
                .eye.eye--left
                .eye.eye--right
              .mouth
          .cube__side
          .cube__side
            +cube('toaster__handle')
          .cube__side
          .cube__side
          .cube__side
      
label.cloak(for='toast')

View Compiled
*
*:after
*:before
  box-sizing border-box

body
  min-height 100vh
  background hsl(195, 20%, 70%)
  overflow hidden
  margin 0
  padding 0

:root
  --toaster-width 18
  --toaster-height 10
  --toaster-depth 12
  --toast-depth 1
  --eye-size 1.75vmin
  --feature hsl(0, 0%, 5%)
  --shadow hsla(0, 0%, 25%, 1)
  --cloth-color-one hsla(320, 60%, 50%, 0.65)
  --speed 1
  --transition 0.25
  --handle hsl(0, 0%, 20%)
  --shade-one hsl(0, 0%, 85%)
  --shade-two hsl(0, 0%, 75%)
  --shade-three hsl(0, 0%, 65%)
  --shine hsla(0, 0%, 100%, 0.5)


[type='checkbox']
  position absolute
  left 100%
  opacity 0
  height 0
  width 0

.rotater
  position fixed
  height 10vmin
  width 100vmax
  top 0
  left 0
  z-index 2

  &:hover
    & ~ .scene .plane
      animation spin-y 1s infinite linear

  &--bottom
    transform translate(0, calc(100vh - 10vmin))

    &:hover
      & ~ .scene .plane
        animation spin-y 1s infinite reverse linear

  &--left
    transform rotate(90deg)
    transform-origin 0 100%

    &:hover
      & ~ .scene .plane
        animation spin-x 1s infinite reverse linear

  &--right
    transform rotate(90deg) translate(0, calc(-100vw + 10vmin))
    transform-origin 0 100%

    &:hover
      & ~ .scene .plane
        animation spin-x 1s infinite linear


.scene
  perspective 800px
  transform-style preserve-3d
  height 100vh
  width 100vw
  display flex
  align-items center
  justify-content center

.plane
  height 20vmin
  width 40vmin
  transform-style preserve-3d
  transform rotateX(-24deg) rotateY(-24deg) rotateX(90deg)
  // transform rotateX(-0deg) rotateY(-0deg) rotateX(90deg)

@keyframes spin-x
  to
    transform rotateX(-24deg) rotateY(-24deg) rotateX(90deg) rotateZ(-360deg)
@keyframes spin-y
  to
    transform rotateX(-24deg) rotateY(-24deg) rotateX(90deg) rotateX(360deg)


.cube
  --width 15
  --height 10
  --depth 4
  height calc(var(--depth) * 1vmin)
  width calc(var(--width) * 1vmin)
  transform-style preserve-3d
  position absolute
  font-size 1rem
  animation spin 1s infinite linear
  transform translate3d(0, 0, 5vmin)

  & > div:nth-of-type(1)
    height calc(var(--height) * 1vmin)
    width 100%
    transform-origin 50% 50%
    transform rotateX(-90deg)
    position absolute
    top 50%
    left 50%
    transform translate(-50%, -50%) rotateX(-90deg) translate3d(0, 0, calc((var(--depth) / 2) * 1vmin))

  & > div:nth-of-type(2)
    height calc(var(--height) * 1vmin)
    width 100%
    transform-origin 50% 50%
    transform translate(-50%, -50%) rotateX(-90deg) rotateY(180deg) translate3d(0, 0, calc((var(--depth) / 2) * 1vmin))
    position absolute
    top 50%
    left 50%

  & > div:nth-of-type(3)
    height calc(var(--height) * 1vmin)
    width calc(var(--depth) * 1vmin)
    transform translate(-50%, -50%) rotateX(-90deg) rotateY(90deg) translate3d(0, 0, calc((var(--width) / 2) * 1vmin))
    position absolute
    top 50%
    left 50%

  & > div:nth-of-type(4)
    height calc(var(--height) * 1vmin)
    width calc(var(--depth) * 1vmin)
    transform translate(-50%, -50%) rotateX(-90deg) rotateY(-90deg) translate3d(0, 0, calc((var(--width) / 2) * 1vmin))
    position absolute
    top 50%
    left 50%

  & > div:nth-of-type(5)
    height calc(var(--depth) * 1vmin)
    width calc(var(--width) * 1vmin)
    transform translate(-50%, -50%) translate3d(0, 0, calc((var(--height) / 2) * 1vmin))
    position absolute
    top 50%
    left 50%

  & > div:nth-of-type(6)
    height calc(var(--depth) * 1vmin)
    width calc(var(--width) * 1vmin)
    transform translate(-50%, -50%) translate3d(0, 0, calc((var(--height) / 2) * -1vmin)) rotateX(180deg)
    position absolute
    top 50%
    left 50%

.table
  --height 2
  --width 40
  --depth 30
  position absolute
  top 50%
  left 50%
  transform translate(-50%, -50%)

  div
    background repeating-linear-gradient(90deg, var(--cloth-color-one) 2px, transparent 5px 9px, var(--cloth-color-one) 10px),
      repeating-linear-gradient(0deg, var(--cloth-color-one) 5px, transparent 6px 12px, var(--cloth-color-one) 15px), hsl(0, 0%, 90%)
    &:nth-of-type(3)
      filter brightness(0.75)
    &:nth-of-type(5)
      filter brightness(0.95)      

img
  position absolute
  height 20%
  right 0.5vmin
  bottom 0.5vmin
  mix-blend-mode overlay
  filter grayscale(1)

:checked ~ .scene
  .toaster
    animation none

    .mouth
      background var(--feature)
      border 0

      &:after
        content ''

    &__handle
      transform translate3d(-50%, calc(var(--toaster-height) * 0.4vmin), calc(var(--height) * 0.5vmin))
      animation switch calc(var(--speed) * 1s) calc(var(--transition) * 1s) infinite ease-in-out

    .toast__wrapper
      transform translate3d(-50%, -50%, calc(var(--toaster-height) * 0.3vmin))
      animation jump calc(var(--speed) * 1s) calc(var(--transition) * 1s) infinite ease-in-out

    .toast
      animation flip calc(var(--speed) * 2s) calc(var(--transition) * 1s) infinite ease-in-out

    // .toaster__body
    //   animation squish calc(var(--speed) * 1s) calc(var(--transition) * 1s) infinite ease-in-out

:checked ~ .cloak
  height 100vh
  width 100vw
  position absolute
  top 0
  left 0

.toaster
  height calc(var(--toaster-depth) * 1vmin)
  width calc(var(--toaster-width) * 1vmin)
  top 50%
  left 50%
  position absolute
  transform translate3d(-50%, -50%, 1vmin)
  transform-style preserve-3d

  &:hover
    animation rock calc(var(--speed) * 0.1s) infinite

    .mouth
      border 0
      &:before
        content ''

  &__shadow
    height calc(var(--toaster-depth) * 1vmin)
    width calc(var(--toaster-width) * 1vmin)
    top 50%
    left 50%
    position absolute
    transform translate3d(-50%, -50%, 1vmin)
    background var(--shadow)
    filter blur(10px)

  &__leg
    --width 1
    --depth 1
    --height 1
    position absolute
    transform translate3d(0, 0, 0.5vmin)

    --perimeter 5

    &--tr
      top calc(var(--perimeter) * 1%)
      left calc(var(--perimeter) * 1%)
    &--tl
      top calc(var(--perimeter) * 1%)
      right calc(var(--perimeter) * 1%)
    &--br
      bottom calc(var(--perimeter) * 1%)
      right calc(var(--perimeter) * 1%)
    &--bl
      bottom calc(var(--perimeter) * 1%)
      left calc(var(--perimeter) * 1%)

    div
      background var(--feature)

  &__body
    --depth var(--toaster-depth)
    --width var(--toaster-width)
    --height var(--toaster-height)
    position absolute
    top 50%
    left 50%
    transform translate3d(-50%, -50%, 6vmin)

    & > div:nth-of-type(1)
      background linear-gradient(120deg, transparent 10%, var(--shine) 10% 20%, transparent 20% 25%, var(--shine) 25% 30%, transparent 30%), var(--shade-one)
    & > div:nth-of-type(2)
      background var(--shade-one)
    & > div:nth-of-type(3)
      background var(--shade-three)
    & > div:nth-of-type(4)
      background var(--shade-three)
    & > div:nth-of-type(5)
      background var(--shade-two)
    & > div:nth-of-type(6)
      background var(--shade-two)

    & > div:nth-of-type(3)
      transform-style preserve-3d

      // handle slot
      &:after
        content ''
        position absolute
        width 8%
        height 50%
        background hsl(0, 0%, 40%)
        transform translate(-50%, 0)
        left 50%
        top 10%

    // bread slot
    & > div:nth-of-type(5):after
      content ''
      height 30%
      width 65%
      background hsl(0, 0%, 15%)
      top 50%
      left 50%
      position absolute
      transform translate(-50%, -50%)

  &__handle
    transform-style preserve-3d
    --height 1
    --width 3
    --depth 1
    position absolute
    top 10%
    left 50%
    transform translate3d(-50%, -50%, calc(var(--height) * 0.5vmin))
    background-color var(--handle)
    transition transform calc(var(--transition) * 1s) ease

    & > div
      background-color var(--handle)

  &__face
    height 25%
    width 50%
    position absolute
    top 50%
    left 50%
    transform translate(-50%, -50%)

  &__toaster
  &__body
  &__leg
    transform-style preserve-3d

  &__toaster
    height calc(var(--toaster-depth) * 1vmin)
    width calc(var(--toaster-width) * 1vmin)
    top 50%
    left 50%
    position absolute
    transform translate3d(-50%, -50%, 0)


.toast
  --depth var(--toast-depth)
  --height var(--toaster-height)
  --width var(--toaster-height)

  position absolute
  top 50%
  left 50%
  transform translate3d(-50%, -50%, calc(var(--toaster-height) * 0.5vmin))

  &__face
    height 30%
    width 80%
    top 50%
    left 50%
    position absolute
    transform translate(-50%, -50%)

  & > div
    background hsl(30, 40%, 80%)

    &:nth-of-type(1)
    &:nth-of-type(2)
      border 0.25vmin solid hsl(30, 80%, 40%)

    &:nth-of-type(3)
    &:nth-of-type(4)
    &:nth-of-type(5)
    &:nth-of-type(6)
      background hsl(30, 80%, 40%)

// Keyframes that need to match up on the double
// Bread jumps out and flips alternate ways
@keyframes jump
  0%, 100%
    transform translate3d(-50%, -50%, 3vmin)
  5%
    transform translate3d(-50%, -50%, 2vmin)
  50%
    transform translate3d(-50%, -50%, 20vmin)

@keyframes switch
  0%, 100%
    transform translate3d(-50%, calc(var(--toaster-height) * 0.4vmin), calc(var(--height) * 0.5vmin))
  5%
    transform translate3d(-50%, calc(var(--toaster-height) * 0.45vmin), calc(var(--height) * 0.5vmin))
  50%
    transform translate3d(-50%, -50%, calc(var(--height) * 0.5vmin))

@keyframes flip
  0%, 10%
    transform translate3d(-50%, -50%, calc(var(--toaster-height) * 0.5vmin)) rotateX(0deg) rotateZ(0deg)
  40%, 60%
    transform translate3d(-50%, -50%, calc(var(--toaster-height) * 0.5vmin)) rotateX(360deg) rotateZ(0deg)
  90%, 100%
    transform translate3d(-50%, -50%, calc(var(--toaster-height) * 0.5vmin)) rotateX(360deg) rotateZ(360deg)

// Toaster squish and then eject
@keyframes rock
  0, 100%
    transform translate3d(-50%, -50%, 1vmin) rotateY(1deg)
  50%
    transform translate3d(-50%, -50%, 1vmin) rotateY(-1deg)

@keyframes squish
  0%, 100%
    transform translate3d(-50%, -50%, 6vmin) scaleZ(1)
  5%
    transform translate3d(-50%, -50%, 6vmin) scaleZ(1.02)
  50%
    transform translate3d(-50%, -50%, 6vmin) scaleZ(0.98)

.toast__wrapper
  height calc(var(--toast-depth) * 1vmin)
  width calc(var(--toaster-height) * 1vmin)
  transform-style preserve-3d
  position absolute
  top 50%
  left 50%
  transform translate3d(-50%, -50%, 6vmin)
  transition transform calc(var(--transition) * 1s) ease
  // animation jump 1s infinite ease-in-out

.eye
  position absolute
  height var(--eye-size)
  width var(--eye-size)
  border-radius 50%
  background #000
  top 0
  animation blink 5s infinite

  &:after
    content ''
    height 25%
    width 25%
    position absolute
    top 10%
    left 10%
    border-radius 50%
    background hsl(0, 0%, 100%)

  &--left
    left 0

  &--right
    right 0

@keyframes blink
  0%, 49%, 51%, 100%
    transform scaleY(1)
  50%
    transform scaleY(0)

.mouth
  height 2.5vmin
  width 2.5vmin
  position absolute
  bottom 0
  left 50%
  transform translate(-50%, 0)
  -webkit-clip-path inset(50% 0 0 0)
  clip-path inset(50% 0 0 0)
  background #000
  border-radius 50%
  overflow hidden
  border 0.5vmin solid #000
  background transparent

  &:after
    // content ''
    height 60%
    width 60%
    position absolute
    top 75%
    left 50%
    background red
    border-radius 50%

  &:before
    height 50%
    width 50%
    border-radius 50%
    background #000
    top 50%
    position absolute
    left 50%
    transform translate(-50%, 0)
View Compiled
// JavaScript is toast in this demo 😅

// Click the toaster for bread flips!
// Hover page edges to rotate!
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.