mixin confetti()
- let c = 0
while c < 10
.confetti(style=`--rotation: ${(Math.random() * 180) - 90}; --travel: ${Math.random() * -100};`) 🎉
- c++
mixin cross()
svg(style!=attributes.style class!=attributes.class viewBox="0 0 100 100")
path.cross(d="M 80 20 L 20 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100")
path.cross(d="M 20 20 L 80 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100")
mixin naught()
svg(class!=attributes.class style!=attributes.style viewBox="0 0 100 100")
circle(cx="50" cy="50" r="30" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="200" stroke-dashoffset="200")
form.game
- for (let i = 0; i < 9; i++)
input(type="checkbox" id=`x-${i}`)
- const x = i % 3
- const y = Math.floor(i / 3)
+cross()(class="x board__x" style=`--x: ${x}; --y: ${y};`)
path.cross(d="M 80 20 L 20 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100")
path.cross(d="M 20 20 L 80 80" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="100" stroke-dashoffset="100")
input(type="checkbox" id=`o-${i}`)
+naught()(class="o board__o" style=`--x: ${x}; --y: ${y};`)
.board
- const DELAYS = [0.75, 0.5, 1, 0.25]
- for (let l = 0; l < 4; l++)
- const rotate = l % 2
- const shift = 2 % (l + 1) ? 1 : -1
- const delay = DELAYS[l]
svg.board__line(viewBox="0 0 10 300" style=`--rotate: ${rotate}; --shift: ${shift}; --delay: ${delay};`)
path(d="M 5 5 L 5 295" stroke-width="10" stroke-linecap="round" stroke-dasharray="300" stroke-dashoffset="300")
- for (let i = 0; i < 9; i++)
.board__cell
label(for=`x-${i}`)
+cross()(class="x ghost")
label(for=`o-${i}`)
+naught()(class="o ghost")
.game__result.game__result--x.result
+confetti()
.result__content
.result__title Winner!
+cross()(class="x result__winner")
.game__result.game__result--o.result
+confetti()
.result__content
.result__title Winner!
+naught()(class="o result__winner")
.game__result.game__result--draw.result
.result__content
.result__title Draw...
svg.zzz.result__winner(viewBox="0 0 24 24")
path(d="M23,12H17V10L20.39,6H17V4H23V6L19.62,10H23V12M15,16H9V14L12.39,10H9V8H15V10L11.62,14H15V16M7,20H1V18L4.39,14H1V12H7V14L3.62,18H7V20Z")
button(type="reset" title="Reset Board")
svg.reset(viewBox="0 0 24 24")
path(d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z")
View Compiled
@font-face {
font-family: Cyber;
src: url("https://assets.codepen.io/605876/Blender-Pro-Bold.otf");
font-display: swap;
}
*
box-sizing border-box
:root
--size 300px
--piece-size calc(var(--size) / 3)
--line hsl(0, 0%, 90%)
--bg hsl(210, 50%, 5%)
--naught hsl(150, 80%, 70%)
--naught-alpha hsla(150, 80%, 70%, 0.5)
--cross hsl(280, 80%, 70%)
--cross-alpha hsla(280, 80%, 70%, 0.5)
--draw-speed 0.15
--color hsl(0, 10%, 15%)
@media(min-width 768px)
--size 50vmin
@media(max-height 500px)
--size 300px
body
min-height 100vh
display grid
place-items center
margin 0
overflow hidden
background var(--bg)
color var(--color)
font-family 'Cyber', sans-serif
text-transform uppercase
svg
filter drop-shadow(0 -0.25vmin 0.25vmin hsl(0, 10%, 0%)) drop-shadow(0 0 0.5vmin var(--alpha)) drop-shadow(0 0 1vmin var(--alpha)) drop-shadow(0 0 5vmin var(--stroke)) brightness(1.2)
stroke var(--stroke)
form
.board
display grid
place-items center
position relative
.game__result
display none
position absolute
width calc(var(--size) * 1.5)
height calc(var(--size) * 1.5)
transform translate(-50%, -50%)
top 50%
left 50%
label
.o
.x
position absolute
display inline-block
height var(--piece-size)
width var(--piece-size)
label
cursor pointer
&:hover .ghost
opacity 0.5
.ghost
opacity 0
transition opacity calc(var(--draw-speed) * 1s)
.o
--alpha var(--naught-alpha)
--stroke var(--naught)
transform rotateX(180deg)
.x
--alpha var(--cross-alpha)
--stroke var(--cross)
path:nth-of-type(2)
--delay var(--draw-speed)
:checked + .x
display block
:checked + .o
display block
.board
height var(--size)
width var(--size)
grid-template-columns repeat(3, 1fr)
grid-template-rows repeat(3, 1fr)
&__x
&__o
display none
left calc(var(--x) * (100% / 3))
top calc(var(--y) * (100% / 3))
z-index 2
position absolute
&__line
--stroke var(--line)
--alpha hsla(0, 0%, 90%, 0.5)
width calc(var(--size) * 0.05)
height var(--size)
position absolute
top 50%
left 50%
transform translate(-50%, -50%) rotate(calc(var(--rotate) * -90deg)) translate(calc(var(--shift) * ((var(--size) / 3) * 0.5)), 0)
&__cell
height var(--piece-size)
width var(--piece-size)
input
button
position absolute
button
top 125%
background transparent
border 0
padding 0
height 5vmin
width 5vmin
min-height 48px
min-width 48px
outline transparent
cursor pointer
display none
transition transform calc(var(--draw-speed) * 1s)
animation fadeIn calc(var(--draw-speed) * 4s) calc(var(--draw-speed) * 2s) both
&:hover
transform translate(0, -4%)
&:active
transform translate(0, 2%) scale(0.8)
.reset
height 100%
fill var(--line)
input
position fixed
left 100%
.result
animation flyIn calc(var(--draw-speed) * 3s) ease-in both
backdrop-filter blur(25px)
z-index 10
&__content
height 40%
width 40%
top 50%
left 50%
transform translate(-50%, -50%)
position absolute
border-radius 15%
background hsla(210, 30%, 20%, 0.8)
color hsl(0, 0%, 100%)
align-items center
display flex
justify-content center
flex-direction column
font-weight bold
font-size 2rem
box-shadow 0 3vmin 2.5vmin -2.5vmin hsl(0, 0%, 0%)
&__winner
position static
height calc(var(--size) / 3)
.zzz
--stroke hsl(210, 80%, 50%)
fill var(--stroke)
@keyframes fadeIn
from
opacity 0
@keyframes flyIn
from
opacity 0
transform translate(-50%, 250%) scale(0)
// This part only cares about showing/hiding the right labels for each move
:checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
// opacity 1
display block
:checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(even)
:checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ .board .board__cell label:nth-of-type(odd)
// opacity 0.25
display none
// Winning combos
#x-0:checked ~ #x-1:checked ~ #x-2:checked ~ .game__result--x
#x-3:checked ~ #x-4:checked ~ #x-5:checked ~ .game__result--x
#x-6:checked ~ #x-7:checked ~ #x-8:checked ~ .game__result--x
#x-0:checked ~ #x-3:checked ~ #x-6:checked ~ .game__result--x
#x-1:checked ~ #x-4:checked ~ #x-7:checked ~ .game__result--x
#x-2:checked ~ #x-5:checked ~ #x-8:checked ~ .game__result--x
#x-0:checked ~ #x-4:checked ~ #x-8:checked ~ .game__result--x
#x-2:checked ~ #x-4:checked ~ #x-6:checked ~ .game__result--x
#o-0:checked ~ #o-1:checked ~ #o-2:checked ~ .game__result--o
#o-3:checked ~ #o-4:checked ~ #o-5:checked ~ .game__result--o
#o-6:checked ~ #o-7:checked ~ #o-8:checked ~ .game__result--o
#o-0:checked ~ #o-3:checked ~ #o-6:checked ~ .game__result--o
#o-1:checked ~ #o-4:checked ~ #o-7:checked ~ .game__result--o
#o-2:checked ~ #o-5:checked ~ #o-8:checked ~ .game__result--o
#o-0:checked ~ #o-4:checked ~ #o-8:checked ~ .game__result--o
#o-2:checked ~ #o-4:checked ~ #o-6:checked ~ .game__result--o
display flex
// Edge case if the last move is a winning move. Don't show the draw.
& ~ .game__result--draw
display none
& ~ button
display block
:checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked ~ :checked
// if the last move is played and there isn't a winner, show a draw
~ .game__result--draw
display block
~ button
display block
.board__line path
.o circle
.x path
animation draw calc(var(--draw-speed) * 1s) calc(var(--delay, 0) * 1s) ease-in both
@keyframes draw
to
stroke-dashoffset 0
.confetti
position absolute
top 50%
left 50%
font-size 2rem
animation celebrate 1s forwards, fadeOut calc(var(--draw-speed) * 1s) calc((1 - var(--draw-speed)) * 1s) forwards
@keyframes fadeOut
to
opacity 0
@keyframes celebrate
from
transform translate(-50%, -50%) rotate(calc(var(--rotation) * 1deg)) scale(0) translate(0, 0)
to
transform translate(-50%, -50%) rotate(calc(var(--rotation) * 1deg)) scale(1) translate(0, calc(var(--travel) * 1vmin))
View Compiled
// 404
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.