.game
  form
    .board
      - for(let g = 1; g < 5; g++)
        svg.board__line(style=`--x: ${g < 3 ? g : 0}; --y: ${g < 3 ? 0 : g - 2}`)
          path(d='M 5 5 L 295 5', fill='none', stroke-width='10', stroke-linecap='round', stroke-dasharray='300', stroke-dashoffset='300')
      - for(let x = 0; x < 9; x++)
        input(type='checkbox', id=`x--${x}`, style=`--col: ${x % 3}; --row: ${Math.floor(x / 3)}`)
        span(style=`--col: ${x % 3}; --row: ${Math.floor(x / 3)}`)
          svg.x
            path.cross(d='M 20 20 L 80 80', fill='none', stroke-width='10', stroke-linecap='round', stroke-dasharray='100', stroke-dashoffset='100')
            path.cross(d='M 80 20 L 20 80', fill='none', stroke-width='10', stroke-linecap='round', stroke-dasharray='100', stroke-dashoffset='100')
        input(type='checkbox', id=`o--${x}`, style=`--col: ${x % 3}; --row: ${Math.floor(x / 3)}`)
        span(style=`--col: ${x % 3}; --row: ${Math.floor(x / 3)}`)
          svg.o
            circle.naught(cx='50', cy='50', r='30', fill='none', stroke-width='10', stroke-dasharray='200', stroke-dashoffset='200', stroke-linecap='round')
      - for(let y = 0; y < 9; y++)
        label(for=`x--${y}`, style=`--col: ${y % 3}; --row: ${Math.floor(y / 3)}`)
        label(for=`o--${y}`, style=`--col: ${y % 3}; --row: ${Math.floor(y / 3)}`)
      .board__result.board__result--x.result
        dl.result__content
          dt.result__title Winner!
          dd.result__details
            svg.x.result__icon
              path.cross(d='M 20 20 L 80 80', fill='none', stroke-width='10', stroke-linecap='round', stroke-dasharray='100', stroke-dashoffset='100')
              path.cross(d='M 80 20 L 20 80', fill='none', stroke-width='10', stroke-linecap='round', stroke-dasharray='100', stroke-dashoffset='100')
          button.result__reset(type='reset') Play again
        //- .result__shield
        - for (let c = 0; c < 30; c++)
          .result__firework-icon(style=`--y: ${Math.floor(Math.random() * 300) + 200}; --r: ${Math.floor(Math.random() * 360)}`) 🎉
      .board__result.board__result--o.result
        dl.result__content
          dt.result__title Winner!
          dd.result__details
            svg.o.result__icon
              circle.naught(cx='50', cy='50', r='30', fill='none', stroke-width='10', stroke-dasharray='200', stroke-dashoffset='200', stroke-linecap='round')
          button.result__reset(type='reset') Play again
        //- .result__shield
        - for (let c = 0; c < 30; c++)
          .result__firework-icon(style=`--y: ${Math.floor(Math.random() * 300) + 200}; --r: ${Math.floor(Math.random() * 360)}`) 🎉
      .board__result.board__result--draw
        dl.result__content
          dt.result__title Draw!
          dd.result__details
            .result__emoji 😭
          button.result__reset(type='reset') Play again
View Compiled
*
  box-sizing border-box

$bg = #1f3a93
$naught = #f62459
$cross = #36dbd7
$line = #fafafa
$size = 300px
$animDuration = .5s

body
html
  align-items center
  background-color $bg
  color #fafafa
  display flex
  flex-direction column
  font-family 'Arial', sans-serif
  justify-content center
  height 100vh
  width 100vw
  overflow hidden
  position relative

svg
  height ($size / 3)
  width ($size / 3)

// hide all inputs from being visible
input
  opacity 0

label
  cursor pointer
  height ($size / 3)
  width ($size / 3)
  z-index 2

// position each cells elements using css variables
input
span
label
  left "calc(var(--col) * %s)" % ($size / 3)
  position absolute
  top "calc(var(--row) * %s)" % ($size / 3)

// give svg elements animation properties
circle
path
  animation-fill-mode forwards
  animation-name draw

circle
  animation-duration $animDuration
  stroke $naught

path
  animation-duration ($animDuration / 2)
  stroke $cross

button
  cursor pointer
  position absolute
  bottom 20px
  padding 8px 24px
  border-radius 4px
  border 0
  background $bg
  font-size .75rem
  color #fafafa
  filter drop-shadow(0 5px 5px black)
  transition transform .1s, filter .1s

  &:hover
    filter drop-shadow(0 5px 4px black)
    transform translateY(1px)

  &:active
    transform translateY(5px)
    filter drop-shadow(0 0 0 transparent)

// span displays which player is occupying a cell
span
  display none
  transform translate3d(0, 0, 3px)
// only once an input is checked for a cell, show the appropriate span
input:checked + span
  display block

.o
  transform rotateY(180deg) rotate(-35deg)

.x
  path:nth-of-type(2)
    animation-delay ($animDuration / 2)

.board
  height $size
  left 50%
  position absolute
  top 50%
  transform translate(-50%, -50%)
  transform-style preserve-3d
  width $size

  &__line
    height 10px
    left "calc(var(--x) * %s)" % ($size / 3)
    position absolute
    top "calc(var(--y) * %s)" % ($size / 3)
    width $size

    path
      stroke $line

    &:nth-of-type(1)
    &:nth-of-type(2)
      transform rotate(90deg) translate(-5px, 0)
      transform-origin left center
    &:nth-of-type(3)
    &:nth-of-type(4)
      transform translate(0, -50%)

    for $line in (1..4)
      &:nth-of-type({$line}) path
        animation-delay ($line * ($animDuration / 2))

  &__result
    animation fadeBg .25s $animDuration
    animation-fill-mode backwards
    background rgba(31, 58, 147, 1)
    display none
    height 100%
    left 0
    margin 0
    padding 0
    position absolute
    top 0
    transform translate3d(0, 0, 4px)
    width 100%

.result
  &__content
    align-items center
    animation flyIn .25s (.25s + $animDuration)
    animation-fill-mode backwards
    background #fafafa
    border-radius 10px
    color $bg
    display flex
    filter drop-shadow(0 10px 10px black)
    flex-direction column
    height 100%
    justify-content center
    left 0
    margin 0
    position absolute
    top 0
    width 100%
    z-index 3

  &__title
    font-size 1.5rem
    font-weight bolder
    text-transform uppercase

  &__details
    margin 0

  &__shield
    animation fadeBg .25s $animDuration
    animation-fill-mode backwards
    background rgba(31, 58, 147, 1)
    height 100%
    left 0
    position absolute
    top 0
    width 100%
    z-index 2

  &__emoji
    font-size 5rem

  &__icon
    path:nth-of-type(1)
      animation-delay (.5s + $animDuration)
    path:nth-of-type(2)
      animation-delay (0.9s + $animDuration)

    circle
      animation-delay (.5s + $animDuration)

  &__firework-icon
    animation fly .75s (.5s + $animDuration)
    animation-fill-mode backwards
    animation-timing-function cubic-bezier(0,.64,.25,1.01)
    font-size 2rem
    height 2rem
    left 50%
    margin-left -1rem
    margin-top -1rem
    opacity 0
    position absolute
    top 50%
    transform rotate(calc(var(--r) * 1deg)) translateY(calc(var(--y) * -1px))
    width 2rem

// animations
@keyframes draw
  to
    stroke-dashoffset 0

@keyframes fadeBg
  from
    background rgba(31, 58, 147, 0)

@keyframes fly
  0%
    opacity 0
    transform translateY(0)
  5%, 50%
    opacity 1
  100%
    opacity 0

@keyframes flyIn
  from
    transform translateY(100%) scale(0)

// there are 9 moves to make alternating between even and odd labels showing
:checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(even)
  transform translate3d(0, 0, 2px)

:checked ~ label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(even)
:checked ~ :checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ label:nth-of-type(odd)
  transform translate3d(0, 0, -1px)

:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked
  // if the last move is played and there isn't a winner, show a draw
  ~ .board__result--draw
    display block

// winning combos
#x--0:checked ~ #x--1:checked ~ #x--2:checked ~ .board__result--x
#x--3:checked ~ #x--4:checked ~ #x--5:checked ~ .board__result--x
#x--6:checked ~ #x--7:checked ~ #x--8:checked ~ .board__result--x
#x--0:checked ~ #x--3:checked ~ #x--6:checked ~ .board__result--x
#x--1:checked ~ #x--4:checked ~ #x--7:checked ~ .board__result--x
#x--2:checked ~ #x--5:checked ~ #x--8:checked ~ .board__result--x
#x--0:checked ~ #x--4:checked ~ #x--8:checked ~ .board__result--x
#x--2:checked ~ #x--4:checked ~ #x--6:checked ~ .board__result--x
#o--0:checked ~ #o--1:checked ~ #o--2:checked ~ .board__result--o
#o--3:checked ~ #o--4:checked ~ #o--5:checked ~ .board__result--o
#o--6:checked ~ #o--7:checked ~ #o--8:checked ~ .board__result--o
#o--0:checked ~ #o--3:checked ~ #o--6:checked ~ .board__result--o
#o--1:checked ~ #o--4:checked ~ #o--7:checked ~ .board__result--o
#o--2:checked ~ #o--5:checked ~ #o--8:checked ~ .board__result--o
#o--0:checked ~ #o--4:checked ~ #o--8:checked ~ .board__result--o
#o--2:checked ~ #o--4:checked ~ #o--6:checked ~ .board__result--o
  display block
  // if there's a winner on the last move, don't show a draw!
  ~ .board__result--draw
    display none
View Compiled
// NOT FOUND
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.