link(href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap", rel="stylesheet")
// secret codes
- var codes = ["6543", "1324", "4135", "5614"];
- var rounds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
- var numrounds = 10;
- var slots = [1, 2, 3, 4];
form
label#info(for="hideInit") i
input(id="color-blind", type="checkbox")
label#color-blind-label(for="color-blind") Add letters
// rounds
div#board
h1#mastermind MASTERMIND
each val in codes
input(type="radio", tabindex=-1, value=val, name="code", id="code-"+val, checked=(val === "6543"))
each round in rounds
input(type="checkbox", tabindex=-1, id="completed-"+round, class="completed")
input(type="radio", tabindex=-1, id="round-"+round, name="round")
each val in slots
each color in [1,2,3,4,5,6]
input(type="radio", tabindex=-1, value=color, name="slot-"+val+"-round-"+round, id="slot-"+val+"-round-"+round+"-color-"+color)
div(class="round-container round-"+round)
label(for="completed-"+round)
span NEXT
each num in [1,2,3,4]
a(href="#round-"+round+"-slot-"+num, tabindex=0, class="slot-circle slot-"+num+"-round-"+round+"-trigger")
div(class="valid valid-"+round)
each num in [1,2,3,4]
div(class="correct-"+num)
each num in [1,2,3,4]
div(class="partial-"+num)
each num in [1,2,3,4]
div(class="empty-"+num)
input(type="radio", value=val, name="round", id="round-"+(numrounds+1))
input(type="checkbox", id="hideInit")
#init.popup
div
h1 Let's play CSS Mastermind!
p
|
a(href="https://en.wikipedia.org/wiki/Mastermind_(board_game)",target="_blank") Mastermind
| is a challenging code-breaking game. There are 6 colors and a 4-length code to decipher. Can you break the code?
p Select the colors for each code peg by clicking in an active slot. When you are ready, click on the "Next" button on the left side of the active round.
p The results will be displayed using key pegs on the right. A black key peg indicates that a color is in the right place, while a white key peg indicates that a color is present but not in the right place.
p.games Pick a game to play:
p.games
each val in codes
label(class="close", for="code-"+val, class="game-selector") Game
div#start-button
label(for="round-1", class="green-button") START GAME
label.close(for="hideInit") Close
div.lost.popup
div
h2 Game Over!
p Oh, no! You reach the end of the game before cracking the code... Click on the button to try again!
div
input(type="reset", value="Play again", class="green-button")
div.won.popup
div
h2 Congratulations! You won!
p Yay! You are amazing and cracked the code successfully! Click on the button below to restart the game.
div
input(type="reset", value="Play again", class="green-button")
div#confetti
- n = 1;
while n < 100
div(class="confetti confetti-"+n)
- n++
each round in rounds
div(id="transition-"+round, class="popup transition")
div
h2 Not there yet!
p Review the hints on the right side and make some changes in the next round.
div
label(for="round-"+(round+1), class="close") Next round!
each num in [1,2,3,4]
div(id="round-"+round+"-slot-"+num, class="popup")
div
h2 Round #{round} Slot #{num}
div.color-labels
each color in [1,2,3,4,5,6]
label(for="slot-"+num+"-round-"+round+"-color-"+color,tabindex=1)
a(href="#", class="close") Close
View Compiled
$fullcodes: '6543', '1324', '4135', '5614';
$codes: '6' '5' '4' '3', '1' '3' '2' '4', '4' '1' '3' '5', '5' '6' '1' '4';
$rounds: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
$num-rounds: 10;
$slots: 1, 2, 3, 4;
$color-number: 1, 2, 3, 4, 5, 6;
$colors: '#7dd', '#096', '#f40', '#ee0', '#eee', '#d49';
$color-letters: 'B', 'G', 'O', 'Y', 'W', 'P';
$board-primary: #de8f65;
$board-secondary: #5ccfa0;
* {
font-family: Roboto, Arial, sans-serif;
}
input:not([type=reset]) {
position: absolute;
left: -10000em;
}
html {
display: table-row;
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
}
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
display: table-cell;
text-align: center;
vertical-align: middle;
}
h1 {
margin: 0;
padding: 0;
margin-bottom: 10px;
font-size: 24px;
}
p {
font-weight: 300;
}
.won,
.lost,
.won:is(:visible) ~ .popup {
display: none;
}
#round-#{$num-rounds + 1}:checked ~ .lost {
display: flex;
}
#board {
position: relative;
width: 380px;
overflow: visible;
margin: 35px auto;
display: block;
padding: 80px 0;
background: #{$board-primary};
border-radius: 15px;
box-shadow: inset 1px 1px 3px #{$board-primary}, inset -4px 2px 8px -2px rgba(0,0,0,0.15), inset 4px 4px 8px rgba(255,255,255,0.35), inset -4px -4px 8px -2px rgba(0,0,0,0.15), inset 4px 0 8px -2px rgba(255,255,255,0.35);
&::before {
content: "";
display: block;
position: absolute;
bottom: 0px;
left: 50px;
width: 180px;
height: 60px;
background: #{$board-secondary};
box-sizing: border-box;
border: 1px solid rgba(0,0,0, 0.2);
border-radius: 3px 3px 1px 1px;
box-shadow: inset 0 -4px 6px rgba(0,0,0,0.15), inset 1px 1px rgba(255,255,255,0.3), inset -1px 1px rgba(255,255,255,0.3);
}
}
#color-blind-label {
position: fixed;
top: 10px;
right: 10px;
&::before {
content: "";
display: inline-block;
width: 1em;
height: 1em;
border-radius: 50%;
border: 2px solid #369;
vertical-align: bottom;
margin-right: 0.25em;
box-shadow: inset 0 0 0 9in #fff;
}
}
#color-blind:checked + label::before {
box-shadow: inset 0 0 0 2px #fff, inset 0 0 0 9in #369;
}
#mastermind {
margin: 0;
padding: 0;
width: 520px;
height:100px;
position: absolute;
right: 0;
top: -20px;
transform: rotate(-90deg);
transform-origin: bottom right;
display: flex;
align-items: center;
justify-content: center;
font-size: 70px;
font-weight: bold;
letter-spacing: 2px;
color: lighten($board-secondary, 2.5%);
text-shadow: 1px -1px 1px rgba(255,255,255,0.4), -1px 1px 1px rgba(0,0,0,0.3);
box-sizing: border-box;
background: linear-gradient(45deg, #{$board-secondary}, darken($board-secondary, 5%));
border: 1px solid rgba(0,0,0, 0.2);
border-bottom: 0;
border-radius: 3px 3px 1px 1px;
box-shadow: inset 0 -4px 6px rgba(0,0,0,0.15), inset 1px 1px rgba(255,255,255,0.3), inset -1px 1px rgba(255,255,255,0.3);
&::before, &::after {
content: "";
display: block;
position: absolute;
top: 77px;
right: -5px;
width: 5px;
height: 15px;
background: rgba(0,0,0,0.35);
border-radius: 0 2px 2px 0;
box-shadow: inset -1px 0 2px rgba(0,0,0,0.25);
}
&::after {
right: auto;
left: -5px;
border-radius: 2px 0 0 2px;
box-shadow: inset -1px 0 2px rgba(0,0,0,0.25);
}
}
// confetti effect based on Fionna's https://codepen.io/fionnachan/pen/EvaqOB
#confetti {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: none;
pointer-events: none;
z-index: 3;
.confetti {
position: absolute;
}
}
@for $i from 1 through 99 {
$w: random(12);
$l: random(100);
.confetti-#{$i} {
width: #{$w}px;
height: #{$w*0.6}px;
background-color: #{nth($colors, random(6))};
top: -10%;
left: unquote($l+"%");
opacity: random() + 0.5;
transform: rotate(#{random()*360}deg);
animation: drop-#{$i} unquote(4+random()+"s") unquote(random()+"s") infinite;
animation-delay: -#{random()*10}s;
}
@keyframes drop-#{$i} {
100% {
top: 110%;
left: unquote($l+random(15)+"%");
}
}
}
.round-container {
border-bottom: 1px solid darken($board-primary, 10%);
border-top: 1px solid lighten($board-primary, 10%);
padding: 5px 10px;
position: relative;
width: 260px;
height: 40px;
&.round-1 {
border-top: 1px solid darken($board-primary, 10%);
box-shadow: inset 0 1px lighten($board-primary, 10%);
}
&.round-10 {
border-bottom: 1px solid lighten($board-primary, 10%);
box-shadow: inset 0 -1px darken($board-primary, 10%);
}
& label {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
display: inline-block;
visibility: hidden;
pointer-events: none; // the buttons are not clickable
float: left;
margin-right: 10px;
font-size: 12px;
color: darken($board-primary, 20%);
font-weight: bold;
cursor: pointer;
box-sizing: border-box;
border: 1px solid transparent;
position: relative;
&:hover {
color: darken($board-primary, 25%);
&::before {
content:"";
display: block;
position: absolute;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.2);
clip-path: polygon(50% 100%, 15% 73%, 37% 73%, 37% 0%, 63% 0%, 63% 73%, 85% 73%);
}
}
span {
position: relative;
}
}
.slot-circle {
margin: 10px;
width: 20px;
height: 20px;
border-radius: 50%;
float: left;
background: rgba(0,0,0,0.3);
box-shadow: inset 3px 3px 3px rgba(0,0,0,0.3), inset -1px -1px 2px rgba(0,0,0,0.125);
pointer-events: none;
}
}
.valid {
width: 40px;
height: 40px;
opacity: 1;
overflow: hidden;
padding-left: 10px;
position: relative;
boz-sizing: border-box;
}
[class^=correct], [class^=partial], [class^=empty] {
background: #222;
display: none;
border-radius: 50%;
width: 16px;
height: 16px;
float: left;
box-sizing: border-box;
margin: 2px;
box-shadow: inset -1px -1px 0 rgba(0,0,0,1),inset -1px -1px 4px rgba(255,255,255,0.15), inset 1px 1px 0 rgba(255,255,255,0.2), inset 1px 1px 4px rgba(0,0,0,0.2), 1px 1px 2px rgba(0,0,0,0.4);
}
[class^=empty] {
width: 20px;
height: 20px;
background: rgba(0,0,0,0.4);
display: inline-block;
margin: 0;
border: 4px solid $board-primary;
box-shadow: inset 2px 2px 4px rgba(0,0,0,0.3);;
}
[class^=partial] {
background: white;
box-shadow: inset -1px -1px 0 rgba(0,0,0,0.1), inset 1px 1px 0 #fff, inset 1px 1px 4px rgba(0,0,0,0.2), 1px 1px 2px rgba(0,0,0,0.4);
}
#info {
width: 24px;
height: 24px;
background: #369;
border-radius: 50%;
position: fixed;
top: 5px;
left: 5px;
z-index: 1;
line-height: 24px;
text-align: center;
display: inline-block;
color: white;
font-style: italic;
font-family: serif;
cursor: pointer;
&:hover {
background: #47a;
}
}
#init {
display: flex;
}
.popup > div {
clip-path: polygon(0% 2%, 2% 3%, 0% 0%, 3% 2%, 2% 0%, 50% 1%, 98% 0%, 97% 2%, 100% 0%, 98% 3%, 100% 2%, 99% 50%, 100% 98%, 98% 97%, 100% 100%, 97% 98%, 98% 100%, 50% 99%, 2% 100%, 3% 98%, 0% 100%, 2% 97%, 0% 98%, 1% 50%);
max-height: 80vh;
overflow: auto;
}
#init #start-button + label {
display: none;
}
[id^=round-]:checked ~ #init,
[id^=round-]:checked ~ #init .games,
[id^=round-]:checked ~ #init #start-button {
display: none;
}
#hideInit:checked + .popup,
[id^=round-]:checked ~ #init #start-button + label {
display: flex;
}
.popup {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
z-index: 1;
background: rgba(0,0,0,0.2);
display: none;
&:target {
display: flex;
}
& > div {
background: white;
padding: 30px;
display: inline-block;
text-align: left;
border-radius: 5px;
max-width: 400px;
margin: 10px;
}
& label {
display: inline-block;
width: 40px;
height: 40px;
border-radius: 50%;
background: white;
cursor: pointer;
}
& h2 {
margin: 0;
margin-bottom: 10px;
}
& .close {
display: inline-block;
background: #369;
color: white;
padding: 5px 15px;
text-decoration: none;
border-radius: 2px;
float: right;
margin-top: 10px;
white-space: nowrap;
height: auto;
width: auto;
&:hover {
background: #47a;
}
}
}
.games {
display:flex;
width: 100%;
justify-content: space-between;
flex-wrap: wrap;
& label {
width: auto;
height: auto;
}
}
// all the game buttons will have their own ID
@for $i from 1 through length($fullcodes) {
$code: nth($fullcodes, $i);
.games label:nth-child(#{$i})::after {
content: " #{$i}";
}
#code-#{$code}:checked ~ #init .games label[for="code-#{$code}"] {
box-shadow: 0 0 0 2px white, 0 0 0 4px #369;
}
}
label.green-button,
input.green-button {
border: 0;
font-family: Roboto, Arial, sans-serif;
font-size: 16px;
border-radius: 2px;
padding: 10px 15px;
text-align: center;
box-sizing: border-box;
width: 100%;
height: auto;
margin-top: 10px;
background: #0a0;
color: white;
text-transform: uppercase;
}
// styling of the code pegs in the selection popup
// thanks @racascou for the suggestions!
.color-labels label,
.round-container a {
position: relative;
&::after {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
text-shadow: -0.6px -1px 1px rgba(255,255,255,0.4), 1px 0.25px 1px rgba(0,0,0,0.5);
padding: 4px;
border: 2px dotted rgba(255,255,255,0.15);
border-radius: 50%;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
}
}
@each $color in $color-number {
.color-labels label:nth-child(#{$color}) {
box-shadow: inset 1px 1px 1px rgba(255, 255, 255, 0.3), inset -1px -1px 1px rgba(0, 0, 0, 0.3), inset 0 0 0 4px #{nth($colors, $color)}, inset 3px 3px 6px rgba(0, 0, 0, 0.3), inset -3px -3px 6px rgba(255, 255, 255, 0.3), 0 0 1px black, 1px 1px 2px black;
background: #{nth($colors, $color)};
&::after {
content: "#{nth($color-letters, $color)}";
color: #{nth($colors, $color)};
}
}
}
#color-blind:not(:checked) ~ #board .color-labels label::after {
display: none;
}
/* Game Logic */
@each $round in $rounds {
// make the buttons clickable only if current round
#round-#{$round}:checked ~ div.round-#{$round} {
label, a {
visibility: visible;
pointer-events: auto;
}
}
// detect if a correct answer was given and show win messages
@each $code in $codes {
#code-#{nth($code, 1)}#{nth($code, 2)}#{nth($code, 3)}#{nth($code, 4)}:checked ~ input#completed-#{$round}:checked {
+ input[name=round]:checked ~ [name^="slot-1"][value="#{nth($code, 1)}"]:checked ~ [name^="slot-2"][value="#{nth($code, 2)}"]:checked ~ [name^="slot-3"][value="#{nth($code, 3)}"]:checked ~ [name^="slot-4"][value="#{nth($code, 4)}"]:checked {
~ .popup {
display: none;
}
~ .won {
display: flex;
}
~ #confetti {
display: block;
}
}
@each $slot in $slots {
// detects each correcta answer and displays the black dot
~ [name="slot-#{$slot}-round-#{$round}"][value="#{nth($code, $slot)}"]:checked ~ .round-container .valid-#{$round} .correct-#{$slot} {
display: inline-block;
}
// detetcs partial answers and displays white dot
~ [name="slot-#{$slot}-round-#{$round}"][value="#{nth($code, $slot)}"]:not(:checked) ~ [name^=slot][name$="round-#{$round}"][value="#{nth($code, $slot)}"]:checked ~ .round-container .valid-#{$round},
~ [name^=slot][name$="round-#{$round}"][value="#{nth($code, $slot)}"]:checked ~ [name="slot-#{$slot}-round-#{$round}"][value="#{nth($code, $slot)}"]:not(:checked) ~ .round-container .valid-#{$round} {
.partial-#{$slot} {
display: inline-block;
}
}
}
}
}
}
@each $round in $rounds {
#completed-#{$round}:checked + input[name=round]:checked ~ #transition-#{$round} {
display: flex;
}
input#completed-#{$round}:checked,
input#round-#{$round}:checked {
@each $slot in $slots {
~ [name="slot-#{$slot}-round-#{$round}"]:checked ~ .round-container.round-#{$round} .slot-circle:nth-child(#{$slot + 1}) {
width: 36px;
height: 36px;
margin: 2px;
border: 0;
}
@each $color in $color-number {
~ [name="slot-#{$slot}-round-#{$round}"][value="#{$color}"]:checked ~ .round-container.round-#{$round} .slot-circle:nth-child(#{$slot + 1}) {
box-shadow: inset 1px 1px 1px rgba(255, 255, 255, 0.3), inset -1px -1px 1px rgba(0, 0, 0, 0.3), inset 0 0 0 4px #{nth($colors, $color)}, inset 3px 3px 6px rgba(0, 0, 0, 0.3), inset -3px -3px 6px rgba(255, 255, 255, 0.3), 0 0 2px rgba(0,0,0,0.35), 1px 1px 2px rgba(0,0,0,0.5);
background: #{nth($colors, $color)};
#color-blind:checked ~ #board &::after {
content: "#{nth($color-letters, $color)}";
color: #{nth($colors, $color)};
}
}
}
}
}
}
View Compiled
// No JS :)
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.