<div id="root"></div>
$light: #fff;
$dark: #222;
$blue: #2196f3;
$red: #fb3838;
body {
box-sizing: border-box;
height: 100%;
}
/* Form
----------------------------*/
form {
overflow: hidden;
width: 300px;
margin: 1em auto;
select,
input {
font-weight: 500;
font-size: 1em;
line-height: 1em;
width: calc(100% / 3 - 0.4em);
height: 2.5em;
float: left;
padding: 0.5em;
margin: 0 0.2em;
border: 1px solid lighten($dark, 80%);
border-width: 0 0 2px 0;
background: transparent;
color: $dark;
outline: none;
transition: all 500ms ease-in-out;
&:hover,
&:focus {
cursor: pointer;
outline: none;
transition: all 500ms ease-in-out;
}
}
input[type="submit"] {
border: 1px solid $blue;
background: $blue;
color: $light;
&:hover,
&:focus {
border-color: lighten($blue, 20%);
background: lighten($blue, 20%);
}
}
option {
color: $dark;
}
option:disabled {
color: lighten($dark, 60%);
}
}
/* Grid
----------------------------*/
%equal-heights {
display: flex;
flex-wrap: wrap;
}
.boxes {
@extend %equal-heights;
}
.box {
margin:1em 0.5em;
width: calc(100% / 4 - 1em);
@media (max-width: 960px) {
width: calc(100% / 3 - 1em);
}
@media (max-width: 767px) {
width: calc(100% / 2 - 1em);
}
@media (max-width: 600px) {
width: calc(100% - 1em);
}
}
/* Thumbs
----------------------------*/
.thumb {
padding: 1em;
text-align: center;
position: relative;
width: calc(100% - 2em);
max-width: 12em;
margin: auto;
border: 1px solid darken($light, 10%);
background: $light;
box-shadow: 0px 4px 4px -4px $dark;
border-radius: 4px;
transition: background 800ms ease;
opacity: 1;
transform: scale(1);
animation: show 500ms 1 ease-in-out;
@keyframes show {
from {
transform:scale(0.5);
opacity: 0;
}
}
img {
max-width: 100%;
padding: 1em;
width: calc(100% - 2em);
}
.img-loading {
width: 100%;
margin: auto;
text-align:center;
position:relative;
color: $blue;
div{
display: block;
width: 100%;
min-height: 190px;
margin: auto;
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PCEtLSBHZW5lcmF0b3I6IEdyYXZpdC5pbyAtLT48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHN0eWxlPSJpc29sYXRpb246aXNvbGF0ZSIgdmlld0JveD0iMCAwIDg0IDE1MCIgd2lkdGg9Ijg0cHQiIGhlaWdodD0iMTUwcHQiPjxtYXNrIGlkPSJfbWFza194dWZtVmJjR1hRUTY3cTIweEpvZlNPZnVDajdNclZ3YiIgeD0iLTIwMCUiIHk9Ii0yMDAlIiB3aWR0aD0iNDAwJSIgaGVpZ2h0PSI0MDAlIj48cmVjdCB4PSItMjAwJSIgeT0iLTIwMCUiIHdpZHRoPSI0MDAlIiBoZWlnaHQ9IjQwMCUiIHN0eWxlPSJmaWxsOndoaXRlOyIvPjxsaW5lIHgxPSI4LjU3MyIgeTE9IjE3LjUiIHgyPSI3NC41NzMiIHkyPSIxNy41IiBmaWxsPSJibGFjayIgc3Ryb2tlPSJub25lIi8+PC9tYXNrPjxsaW5lIHgxPSI4LjU3MyIgeTE9IjE3LjUiIHgyPSI3NC41NzMiIHkyPSIxNy41IiBtYXNrPSJ1cmwoI19tYXNrX3h1Zm1WYmNHWFFRNjdxMjB4Sm9mU09mdUNqN01yVndiKSIgdmVjdG9yLWVmZmVjdD0ibm9uLXNjYWxpbmctc3Ryb2tlIiBzdHJva2Utd2lkdGg9IjEwIiBzdHJva2U9InJnYigzNCwzNCwzNCkiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjQiLz48bWFzayBpZD0iX21hc2tfa2xIUU5OQ2xZNGpUeVhtdUR0cEVqdnJ0OWlPUnFvbmQiIHg9Ii0yMDAlIiB5PSItMjAwJSIgd2lkdGg9IjQwMCUiIGhlaWdodD0iNDAwJSI+PHJlY3QgeD0iLTIwMCUiIHk9Ii0yMDAlIiB3aWR0aD0iNDAwJSIgaGVpZ2h0PSI0MDAlIiBzdHlsZT0iZmlsbDp3aGl0ZTsiLz48cGF0aCBkPSIgTSA4LjU3MyA0OC4wMzMgTCA3NC41NzMgNDguMDMzIE0gOC41NzMgNzQuNjY1IEwgNzQuNTczIDc0LjY2NSBNIDguNTczIDk5Ljk2NSBMIDc0LjU3MyA5OS45NjUgTSA4LjU3MyAxMjQgTCA3NC41NzMgMTI0IE0gOC41NzMgMjAgTCA4LjU3MyAxMjkgTSAzMC41NzMgMjAgTCAzMC41NzMgMTI5IE0gNTIuNTczIDIwIEwgNTIuNTczIDEyOSBNIDc0LjU3MyAyMCBMIDc0LjU3MyAxMjkiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iYmxhY2siIHN0cm9rZT0ibm9uZSIvPjwvbWFzaz48cGF0aCBkPSIgTSA4LjU3MyA0OC4wMzMgTCA3NC41NzMgNDguMDMzIE0gOC41NzMgNzQuNjY1IEwgNzQuNTczIDc0LjY2NSBNIDguNTczIDk5Ljk2NSBMIDc0LjU3MyA5OS45NjUgTSA4LjU3MyAxMjQgTCA3NC41NzMgMTI0IE0gOC41NzMgMjAgTCA4LjU3MyAxMjkgTSAzMC41NzMgMjAgTCAzMC41NzMgMTI5IE0gNTIuNTczIDIwIEwgNTIuNTczIDEyOSBNIDc0LjU3MyAyMCBMIDc0LjU3MyAxMjkiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0icmdiKDAsMCwwKSIgbWFzaz0idXJsKCNfbWFza19rbEhRTk5DbFk0alR5WG11RHRwRWp2cnQ5aU9ScW9uZCkiIHZlY3Rvci1lZmZlY3Q9Im5vbi1zY2FsaW5nLXN0cm9rZSIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2U9InJnYigzNCwzNCwzNCkiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjQiLz48cGF0aCBkPSIgTSA4LjU3MyA0OC4wMzMgTCA3NC41NzMgNDguMDMzIE0gOC41NzMgNzQuNjY1IEwgNzQuNTczIDc0LjY2NSBNIDguNTczIDk5Ljk2NSBMIDc0LjU3MyA5OS45NjUgTSA4LjU3MyAxMjQgTCA3NC41NzMgMTI0IE0gOC41NzMgMjAgTCA4LjU3MyAxMjkgTSAzMC41NzMgMjAgTCAzMC41NzMgMTI5IE0gNTIuNTczIDIwIEwgNTIuNTczIDEyOSBNIDc0LjU3MyAyMCBMIDc0LjU3MyAxMjkiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0icmdiKDAsMCwwKSIvPjxwYXRoIGQ9IiBNIDQ5LjU3MyA4IEMgNDkuNTczIDYuMzQ0IDUwLjkxNyA1IDUyLjU3MyA1IEMgNTQuMjI5IDUgNTUuNTczIDYuMzQ0IDU1LjU3MyA4IEMgNTUuNTczIDkuNjU2IDU0LjIyOSAxMSA1Mi41NzMgMTEgQyA1MC45MTcgMTEgNDkuNTczIDkuNjU2IDQ5LjU3MyA4IFogIE0gMjcuNTczIDggQyAyNy41NzMgNi4zNDQgMjguOTE3IDUgMzAuNTczIDUgQyAzMi4yMjkgNSAzMy41NzMgNi4zNDQgMzMuNTczIDggQyAzMy41NzMgOS42NTYgMzIuMjI5IDExIDMwLjU3MyAxMSBDIDI4LjkxNyAxMSAyNy41NzMgOS42NTYgMjcuNTczIDggWiAgTSA2LjA3MyA4IEMgNi4wNzMgNi4zNDQgNy40MTcgNSA5LjA3MyA1IEMgMTAuNzI5IDUgMTIuMDczIDYuMzQ0IDEyLjA3MyA4IEMgMTIuMDczIDkuNjU2IDEwLjcyOSAxMSA5LjA3MyAxMSBDIDcuNDE3IDExIDYuMDczIDkuNjU2IDYuMDczIDggWiAgTSA3MS41NzMgOCBDIDcxLjU3MyA2LjM0NCA3Mi45MTcgNSA3NC41NzMgNSBDIDc2LjIyOSA1IDc3LjU3MyA2LjM0NCA3Ny41NzMgOCBDIDc3LjU3MyA5LjY1NiA3Ni4yMjkgMTEgNzQuNTczIDExIEMgNzIuOTE3IDExIDcxLjU3MyA5LjY1NiA3MS41NzMgOCBaICIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSJub25lIiB2ZWN0b3ItZWZmZWN0PSJub24tc2NhbGluZy1zdHJva2UiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlPSJyZ2IoMCwwLDApIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLW1pdGVybGltaXQ9IjQiLz48cGF0aCBkPSIgTSAxMC42OTggMTM5LjI3OSBMIDEwLjY5OCAxMzkuMjc5IEwgMTAuNjk4IDEzOS4yNzkgUSAxMC42OTggMTQwLjk0NCAxMC4xNzMgMTQxLjc2NiBMIDEwLjE3MyAxNDEuNzY2IEwgMTAuMTczIDE0MS43NjYgUSA5LjY0NyAxNDIuNTg4IDguNTY2IDE0Mi41ODggTCA4LjU2NiAxNDIuNTg4IEwgOC41NjYgMTQyLjU4OCBRIDcuNTI5IDE0Mi41ODggNi45ODkgMTQxLjc0NiBMIDYuOTg5IDE0MS43NDYgTCA2Ljk4OSAxNDEuNzQ2IFEgNi40NDggMTQwLjkwNSA2LjQ0OCAxMzkuMjc5IEwgNi40NDggMTM5LjI3OSBMIDYuNDQ4IDEzOS4yNzkgUSA2LjQ0OCAxMzcuNiA2Ljk3MSAxMzYuNzg3IEwgNi45NzEgMTM2Ljc4NyBMIDYuOTcxIDEzNi43ODcgUSA3LjQ5NCAxMzUuOTc0IDguNTY2IDEzNS45NzQgTCA4LjU2NiAxMzUuOTc0IEwgOC41NjYgMTM1Ljk3NCBRIDkuNjEyIDEzNS45NzQgMTAuMTU1IDEzNi44MjIgTCAxMC4xNTUgMTM2LjgyMiBMIDEwLjE1NSAxMzYuODIyIFEgMTAuNjk4IDEzNy42NyAxMC42OTggMTM5LjI3OSBaICBNIDcuMTg3IDEzOS4yNzkgTCA3LjE4NyAxMzkuMjc5IEwgNy4xODcgMTM5LjI3OSBRIDcuMTg3IDE0MC42ODEgNy41MTYgMTQxLjMyIEwgNy41MTYgMTQxLjMyIEwgNy41MTYgMTQxLjMyIFEgNy44NDYgMTQxLjk1OSA4LjU2NiAxNDEuOTU5IEwgOC41NjYgMTQxLjk1OSBMIDguNTY2IDE0MS45NTkgUSA5LjI5NiAxNDEuOTU5IDkuNjIzIDE0MS4zMTEgTCA5LjYyMyAxNDEuMzExIEwgOS42MjMgMTQxLjMxMSBRIDkuOTUxIDE0MC42NjMgOS45NTEgMTM5LjI3OSBMIDkuOTUxIDEzOS4yNzkgTCA5Ljk1MSAxMzkuMjc5IFEgOS45NTEgMTM3Ljg5NSA5LjYyMyAxMzcuMjUxIEwgOS42MjMgMTM3LjI1MSBMIDkuNjIzIDEzNy4yNTEgUSA5LjI5NiAxMzYuNjA3IDguNTY2IDEzNi42MDcgTCA4LjU2NiAxMzYuNjA3IEwgOC41NjYgMTM2LjYwNyBRIDcuODQ2IDEzNi42MDcgNy41MTYgMTM3LjI0MiBMIDcuNTE2IDEzNy4yNDIgTCA3LjUxNiAxMzcuMjQyIFEgNy4xODcgMTM3Ljg3NyA3LjE4NyAxMzkuMjc5IFogIE0gMzIuNjk4IDEzOS4yNzkgTCAzMi42OTggMTM5LjI3OSBMIDMyLjY5OCAxMzkuMjc5IFEgMzIuNjk4IDE0MC45NDQgMzIuMTczIDE0MS43NjYgTCAzMi4xNzMgMTQxLjc2NiBMIDMyLjE3MyAxNDEuNzY2IFEgMzEuNjQ3IDE0Mi41ODggMzAuNTY2IDE0Mi41ODggTCAzMC41NjYgMTQyLjU4OCBMIDMwLjU2NiAxNDIuNTg4IFEgMjkuNTI5IDE0Mi41ODggMjguOTg5IDE0MS43NDYgTCAyOC45ODkgMTQxLjc0NiBMIDI4Ljk4OSAxNDEuNzQ2IFEgMjguNDQ4IDE0MC45MDUgMjguNDQ4IDEzOS4yNzkgTCAyOC40NDggMTM5LjI3OSBMIDI4LjQ0OCAxMzkuMjc5IFEgMjguNDQ4IDEzNy42IDI4Ljk3MSAxMzYuNzg3IEwgMjguOTcxIDEzNi43ODcgTCAyOC45NzEgMTM2Ljc4NyBRIDI5LjQ5NCAxMzUuOTc0IDMwLjU2NiAxMzUuOTc0IEwgMzAuNTY2IDEzNS45NzQgTCAzMC41NjYgMTM1Ljk3NCBRIDMxLjYxMiAxMzUuOTc0IDMyLjE1NSAxMzYuODIyIEwgMzIuMTU1IDEzNi44MjIgTCAzMi4xNTUgMTM2LjgyMiBRIDMyLjY5OCAxMzcuNjcgMzIuNjk4IDEzOS4yNzkgWiAgTSAyOS4xODcgMTM5LjI3OSBMIDI5LjE4NyAxMzkuMjc5IEwgMjkuMTg3IDEzOS4yNzkgUSAyOS4xODcgMTQwLjY4MSAyOS41MTYgMTQxLjMyIEwgMjkuNTE2IDE0MS4zMiBMIDI5LjUxNiAxNDEuMzIgUSAyOS44NDYgMTQxLjk1OSAzMC41NjYgMTQxLjk1OSBMIDMwLjU2NiAxNDEuOTU5IEwgMzAuNTY2IDE0MS45NTkgUSAzMS4yOTYgMTQxLjk1OSAzMS42MjMgMTQxLjMxMSBMIDMxLjYyMyAxNDEuMzExIEwgMzEuNjIzIDE0MS4zMTEgUSAzMS45NTEgMTQwLjY2MyAzMS45NTEgMTM5LjI3OSBMIDMxLjk1MSAxMzkuMjc5IEwgMzEuOTUxIDEzOS4yNzkgUSAzMS45NTEgMTM3Ljg5NSAzMS42MjMgMTM3LjI1MSBMIDMxLjYyMyAxMzcuMjUxIEwgMzEuNjIzIDEzNy4yNTEgUSAzMS4yOTYgMTM2LjYwNyAzMC41NjYgMTM2LjYwNyBMIDMwLjU2NiAxMzYuNjA3IEwgMzAuNTY2IDEzNi42MDcgUSAyOS44NDYgMTM2LjYwNyAyOS41MTYgMTM3LjI0MiBMIDI5LjUxNiAxMzcuMjQyIEwgMjkuNTE2IDEzNy4yNDIgUSAyOS4xODcgMTM3Ljg3NyAyOS4xODcgMTM5LjI3OSBaICBNIDU0LjY5OCAxMzkuMjc5IEwgNTQuNjk4IDEzOS4yNzkgTCA1NC42OTggMTM5LjI3OSBRIDU0LjY5OCAxNDAuOTQ0IDU0LjE3MyAxNDEuNzY2IEwgNTQuMTczIDE0MS43NjYgTCA1NC4xNzMgMTQxLjc2NiBRIDUzLjY0NyAxNDIuNTg4IDUyLjU2NiAxNDIuNTg4IEwgNTIuNTY2IDE0Mi41ODggTCA1Mi41NjYgMTQyLjU4OCBRIDUxLjUyOSAxNDIuNTg4IDUwLjk4OSAxNDEuNzQ2IEwgNTAuOTg5IDE0MS43NDYgTCA1MC45ODkgMTQxLjc0NiBRIDUwLjQ0OCAxNDAuOTA1IDUwLjQ0OCAxMzkuMjc5IEwgNTAuNDQ4IDEzOS4yNzkgTCA1MC40NDggMTM5LjI3OSBRIDUwLjQ0OCAxMzcuNiA1MC45NzEgMTM2Ljc4NyBMIDUwLjk3MSAxMzYuNzg3IEwgNTAuOTcxIDEzNi43ODcgUSA1MS40OTQgMTM1Ljk3NCA1Mi41NjYgMTM1Ljk3NCBMIDUyLjU2NiAxMzUuOTc0IEwgNTIuNTY2IDEzNS45NzQgUSA1My42MTIgMTM1Ljk3NCA1NC4xNTUgMTM2LjgyMiBMIDU0LjE1NSAxMzYuODIyIEwgNTQuMTU1IDEzNi44MjIgUSA1NC42OTggMTM3LjY3IDU0LjY5OCAxMzkuMjc5IFogIE0gNTEuMTg3IDEzOS4yNzkgTCA1MS4xODcgMTM5LjI3OSBMIDUxLjE4NyAxMzkuMjc5IFEgNTEuMTg3IDE0MC42ODEgNTEuNTE2IDE0MS4zMiBMIDUxLjUxNiAxNDEuMzIgTCA1MS41MTYgMTQxLjMyIFEgNTEuODQ2IDE0MS45NTkgNTIuNTY2IDE0MS45NTkgTCA1Mi41NjYgMTQxLjk1OSBMIDUyLjU2NiAxNDEuOTU5IFEgNTMuMjk2IDE0MS45NTkgNTMuNjIzIDE0MS4zMTEgTCA1My42MjMgMTQxLjMxMSBMIDUzLjYyMyAxNDEuMzExIFEgNTMuOTUxIDE0MC42NjMgNTMuOTUxIDEzOS4yNzkgTCA1My45NTEgMTM5LjI3OSBMIDUzLjk1MSAxMzkuMjc5IFEgNTMuOTUxIDEzNy44OTUgNTMuNjIzIDEzNy4yNTEgTCA1My42MjMgMTM3LjI1MSBMIDUzLjYyMyAxMzcuMjUxIFEgNTMuMjk2IDEzNi42MDcgNTIuNTY2IDEzNi42MDcgTCA1Mi41NjYgMTM2LjYwNyBMIDUyLjU2NiAxMzYuNjA3IFEgNTEuODQ2IDEzNi42MDcgNTEuNTE2IDEzNy4yNDIgTCA1MS41MTYgMTM3LjI0MiBMIDUxLjUxNiAxMzcuMjQyIFEgNTEuMTg3IDEzNy44NzcgNTEuMTg3IDEzOS4yNzkgWiAgTSA3Ni42OTggMTM5LjI3OSBMIDc2LjY5OCAxMzkuMjc5IEwgNzYuNjk4IDEzOS4yNzkgUSA3Ni42OTggMTQwLjk0NCA3Ni4xNzMgMTQxLjc2NiBMIDc2LjE3MyAxNDEuNzY2IEwgNzYuMTczIDE0MS43NjYgUSA3NS42NDcgMTQyLjU4OCA3NC41NjYgMTQyLjU4OCBMIDc0LjU2NiAxNDIuNTg4IEwgNzQuNTY2IDE0Mi41ODggUSA3My41MjkgMTQyLjU4OCA3Mi45ODkgMTQxLjc0NiBMIDcyLjk4OSAxNDEuNzQ2IEwgNzIuOTg5IDE0MS43NDYgUSA3Mi40NDggMTQwLjkwNSA3Mi40NDggMTM5LjI3OSBMIDcyLjQ0OCAxMzkuMjc5IEwgNzIuNDQ4IDEzOS4yNzkgUSA3Mi40NDggMTM3LjYgNzIuOTcxIDEzNi43ODcgTCA3Mi45NzEgMTM2Ljc4NyBMIDcyLjk3MSAxMzYuNzg3IFEgNzMuNDk0IDEzNS45NzQgNzQuNTY2IDEzNS45NzQgTCA3NC41NjYgMTM1Ljk3NCBMIDc0LjU2NiAxMzUuOTc0IFEgNzUuNjEyIDEzNS45NzQgNzYuMTU1IDEzNi44MjIgTCA3Ni4xNTUgMTM2LjgyMiBMIDc2LjE1NSAxMzYuODIyIFEgNzYuNjk4IDEzNy42NyA3Ni42OTggMTM5LjI3OSBaICBNIDczLjE4NyAxMzkuMjc5IEwgNzMuMTg3IDEzOS4yNzkgTCA3My4xODcgMTM5LjI3OSBRIDczLjE4NyAxNDAuNjgxIDczLjUxNiAxNDEuMzIgTCA3My41MTYgMTQxLjMyIEwgNzMuNTE2IDE0MS4zMiBRIDczLjg0NiAxNDEuOTU5IDc0LjU2NiAxNDEuOTU5IEwgNzQuNTY2IDE0MS45NTkgTCA3NC41NjYgMTQxLjk1OSBRIDc1LjI5NiAxNDEuOTU5IDc1LjYyMyAxNDEuMzExIEwgNzUuNjIzIDE0MS4zMTEgTCA3NS42MjMgMTQxLjMxMSBRIDc1Ljk1MSAxNDAuNjYzIDc1Ljk1MSAxMzkuMjc5IEwgNzUuOTUxIDEzOS4yNzkgTCA3NS45NTEgMTM5LjI3OSBRIDc1Ljk1MSAxMzcuODk1IDc1LjYyMyAxMzcuMjUxIEwgNzUuNjIzIDEzNy4yNTEgTCA3NS42MjMgMTM3LjI1MSBRIDc1LjI5NiAxMzYuNjA3IDc0LjU2NiAxMzYuNjA3IEwgNzQuNTY2IDEzNi42MDcgTCA3NC41NjYgMTM2LjYwNyBRIDczLjg0NiAxMzYuNjA3IDczLjUxNiAxMzcuMjQyIEwgNzMuNTE2IDEzNy4yNDIgTCA3My41MTYgMTM3LjI0MiBRIDczLjE4NyAxMzcuODc3IDczLjE4NyAxMzkuMjc5IFogIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9InJnYigwLDAsMCkiLz48L3N2Zz4=)
no-repeat center center transparent;
animation: flash 800ms infinite ease-in-out;
@keyframes flash {
from {
opacity: 0;
}
}
}
}
figcaption {
margin-top: 0.2em;
font-size: 12px;
font-weight: 700;
color: darken($light, 30%);
}
&.hide {
opacity: 0;
transform: scale(0.2);
animation: hideElement 200ms 1 ease-in-out;
@keyframes hideElement {
from {
transform:scale(1);
opacity: 1;
}
}
}
.img-close {
position: absolute;
top: -8px;
right: -8px;
width: 30px;
height: 30px;
line-height: 1.2;
font-size: 25px;
font-weight: bold;
border-radius: 100%;
cursor: pointer;
background: $red;
border: 1px solid darken($red, 20%);
color: $light;
}
}
.exists{
border:1px solid $red;
background:lighten($red,20%);
transition: background 800ms ease;
figcaption{
color:$light;
}
}
/* layout
----------------------------*/
.header {
text-align: center;
h3 {
margin: 0;
font-size: 2em;
color: darken($light, 50%);
}
}
View Compiled
const {useState, useEffect, Fragment } = React;
const assetsUrl = "http://ukulala.surge.sh/chords";
/**
* Storage theme settings
*
* set Storage.val = {color: 'blue'}
* get Storage.val
*/
const Storage = {
get val() {
this.data = window.localStorage.getItem("demo_data");
return JSON.parse(this.data);
},
set val(value) {
this.data = JSON.stringify(value);
window.localStorage.setItem("demo_data", this.data);
}
};
// check array duplicates
const hasDuplicates = array => {
return (new Set(array)).size !== array.length;
}
/* Notes
-----------------------------*/
const Notes = {
C: "Do",
Db: "Reb/Do#",
D: "Re",
Eb: "Mib/Re#",
E: "Mi",
F: "Fa",
Gb: "Solb/Fa#",
G: "Sol",
Ab: "Lab/Sol#",
A: "La",
Bb: "Sib/La#",
B: "Si"
};
/* Tonalityes
-----------------------------*/
const Tonalityes = {
"": "",
m: "m",
_: "+",
"5": "5",
"6": "6",
maj7: "maj7",
m7: "m7",
m6: "m6",
m7_b5_: "m7(b5)",
dim7: "dim7",
"7": "7",
"9": "9",
"7_b9_": "7(b9)",
"7__5_": "7(#5)"
};
/* Simple forms component
-----------------------------*/
const Form = (props) => <form onSubmit={props.fn}>{props.children}</form>;
const Select = (props) => {
let data = props.data;
let notes = {};
if (props.sort) notes = Object.entries(data).sort();
else notes = Object.entries(data);
return (
<select onChange={props.fn}>
{notes.map(([key, value]) => (
<option key={key} value={key}>
{value}
</option>
))}
</select>
);
};
const Submit = (props) => <input type="submit" value={props.val || "Get"} />;
/* Columns
-----------------------------*/
const Boxes = (props) => <div className="boxes">{props.children}</div>;
const Box = (props) => <div className="box">{props.children}</div>;
const ImgLoader = () => <div className="img-loading"><div></div></div>
/* Image
-----------------------------*/
const Image = (props) => {
const [src, setSrc] = useState("");
// use timeout and show image
useEffect(() => {
let timerid = false;
if (timerid) {
clearTimeout(timerid);
}
timerid = setTimeout(() => {
setSrc(`${assetsUrl}/${props.data}.svg`);
}, 100);
}, [props.data]);
return (
<figure className={`thumb ${props.data}`}>
<span className="img-close" onClick={props.fn}>×</span>
{/* check if exists src */}
{src ? <img src={src} /> : <ImgLoader/>}
<figcaption>
{props.data.substring(0, 2).charAt(1) === "b"
? Notes[props.data.substring(0, 2)] +
" " +
Tonalityes[props.data.substring(2, props.data.length)]
: Notes[props.data.substring(0, 1)] +
" " +
Tonalityes[props.data.substring(1, props.data.length)]}
</figcaption>
</figure>
);
};
const Header = (props) => (
<header className="header">
<h3>{props.children}</h3>
</header>
);
const App = () => {
// last array
let lastSaved = Storage.val ? Storage.val : ["C"];
// data of chords
const [data, setData] = useState(["C"]);
// note & tonality
const [note, setNote] = useState("C");
const [tonality, setTonality] = useState("");
// on init get last data saved
useEffect(() => setData(lastSaved), []);
// Add submit
const handleSubmit = (e) => {
e.preventDefault();
let arr = note + tonality;
let newArr = [...data, arr];
if (hasDuplicates(newArr)) {
// if chord exists show red color
let img = document.querySelector(`.${arr}`);
img.classList.add("exists");
let w = setTimeout(() => {
img.classList.remove("exists");
clearTimeout(w);
}, 800);
}
// update array of unique elements
setData([...new Set(newArr)]);
Storage.val = [...new Set(newArr)];
return false;
};
// delete data
const removeData = (evt, index) => {
let element = evt.target.parentNode;
element.classList.add("hide");
let w = setTimeout(() => {
// remove array by index
let arr = [...data];
arr.splice(index, 1);
// remove duplicates
let savedArr = [...new Set(arr)];
// update
setData(savedArr);
// storage saved
Storage.val = savedArr;
clearTimeout(w);
}, 500);
};
return (
<Fragment>
<Header>Ukelele Componser</Header>
<Form fn={handleSubmit}>
<Select
sort={false}
fn={(e) => setNote(e.target.value)}
data={Notes} />
<Select
sort={true}
fn={(e) => setTonality(e.target.value)}
data={Tonalityes}
/>
<Submit val="+" />
</Form>
<Boxes>
{data.map((item, index) => (
<Box key={index}>
<Image
fn={(evt) => removeData(evt, index)}
data={item} />
</Box>
))}
</Boxes>
</Fragment>
);
};
ReactDOM.render(<App />, window.root);
View Compiled
This Pen doesn't use any external CSS resources.