Created September 2010. jQuery removed, replaced with vanilla JS 2024.
CSS3 Smiley
Figure
Ground
HTML
<div id="smiley-wrapper" class="figure">
<div class="smiley ground">
<div class="left-eye eye figure"> </div>
<div class="right-eye eye figure"> </div>
<div class="mouth figure"> </div>
<div class="mouth-cover ground"> </div>
</div>
</div>
<div id="controls">
<div class="segment">
<h2>Figure <code id="figureText"></code></h2>
<div class="r">
<label><input type="radio" value="0" name="fr" checked> 0</label>
<label><input type="radio" value="3" name="fr"> 3</label>
<label><input type="radio" value="6" name="fr"> 6</label>
<label><input type="radio" value="9" name="fr"> 9</label>
<label><input type="radio" value="C" name="fr"> C</label>
<label><input type="radio" value="F" name="fr"> F</label>
</div>
<div class="g">
<label><input type="radio" value="0" name="fg" checked> 0</label>
<label><input type="radio" value="3" name="fg"> 3</label>
<label><input type="radio" value="6" name="fg"> 6</label>
<label><input type="radio" value="9" name="fg"> 9</label>
<label><input type="radio" value="C" name="fg"> C</label>
<label><input type="radio" value="F" name="fg"> F</label>
</div>
<div class="b">
<label><input type="radio" value="0" name="bg" checked> 0</label>
<label><input type="radio" value="3" name="bg"> 3</label>
<label><input type="radio" value="6" name="bg"> 6</label>
<label><input type="radio" value="9" name="bg"> 9</label>
<label><input type="radio" value="C" name="bg"> C</label>
<label><input type="radio" value="F" name="bg"> F</label>
</div>
</div>
<div class="segment">
<h2>Ground <code id="groundText"></code></h2>
<div class="r">
<label><input type="radio" value="0" name="gr"> 0</label>
<label><input type="radio" value="3" name="gr"> 3</label>
<label><input type="radio" value="6" name="gr"> 6</label>
<label><input type="radio" value="9" name="gr"> 9</label>
<label><input type="radio" value="C" name="gr"> C</label>
<label><input type="radio" value="F" name="gr" checked> F</label>
</div>
<div class="g">
<label><input type="radio" value="0" name="gg"> 0</label>
<label><input type="radio" value="3" name="gg"> 3</label>
<label><input type="radio" value="6" name="gg"> 6</label>
<label><input type="radio" value="9" name="gg"> 9</label>
<label><input type="radio" value="C" name="gg"> C</label>
<label><input type="radio" value="F" name="gg" checked> F</label>
</div>
<div class="b">
<label><input type="radio" value="0" name="gb"> 0</label>
<label><input type="radio" value="3" name="gb"> 3</label>
<label><input type="radio" value="6" name="gb"> 6</label>
<label><input type="radio" value="9" name="gb"> 9</label>
<label><input type="radio" value="C" name="gb"> C</label>
<label><input type="radio" value="F" name="gb" checked> F</label>
</div>
</div>
</div>
SCSS
div#smiley-wrapper {
box-sizing: border-box;
width: 200px;
padding: 50px;
margin: 0 auto;
}
.smiley {
height: 100px;
width: 100px;
position: relative;
border-radius: 50px;
.eye {
height: 12px;
width: 12px;
border-radius: 6px;
position: absolute;
overflow: hidden;
}
.left-eye {
top: 20px;
left: 30px;
}
.right-eye {
top: 20px;
right: 30px;
}
.mouth {
border-bottom-right-radius: 25px;
border-bottom-left-radius: 25px;
height: 25px;
position: absolute;
width: 50px;
left: 25px;
top: 60px;
}
.mouth-cover {
border-bottom-right-radius: 25px;
border-bottom-left-radius: 25px;
height: 25px;
position: absolute;
width: 40px;
left: 30px;
top: 50px;
}
}
/* controls */
div#controls {
text-align: center;
display: flex;
flex-wrap: wrap;
width: 100%;
justify-content: center;
align-items: center;
gap: 1rem;
> * {
padding: 1rem;
a {
display: inline-block;
}
}
h2 {
margin: 0;
}
.segment {
display: flex;
flex-wrap: wrap;
gap: 1rem;
width: 20rem;
> * {
width: 100%;
}
}
label {
background-color: #666;
color: #fff;
padding: 5px 10px;
cursor: pointer;
input[type="radio"] {
display: none;
}
&:has(input[type="radio"]:checked) {
background-color: #0f0;
color: #fff;
}
}
}
.figure {
background-color: #000;
}
.ground {
background-color: #fff;
}
#wrapper, #controls, #credits {
width: 300px;
margin: 0 auto;
}
JavaScript
document.addEventListener('DOMContentLoaded', function () {
const labels = document.querySelectorAll('#controls div.segment div label');
for (var i = 0; i < labels.length; i++) {
labels[i].addEventListener('click', updateColors);
labels[i].addEventListener('mouseover', function () {
this.click();
});
labels[i].addEventListener('touchstart', function () {
this.click();
});
}
if (document.location.hash.length > 1 && document.location.hash.split('#')[1].length === 6) {
updateInputsFromHash(document.location.hash);
}
updateColors();
});
// listen for popstate event
window.addEventListener('popstate', function (event) {
if (event.state && event.state.color) {
updateInputsFromHash('#' + event.state.color);
updateColors();
}
});
const updateColors = function () {
var figureColor = '#';
var groundColor = '#';
var inputs = document.querySelectorAll('#controls div.segment div input[type="radio"]:checked');
for (var i = 0; i < inputs.length; i++) {
if (i < 3) {
figureColor += String(inputs[i].value);
} else {
groundColor += String(inputs[i].value);
}
}
document.querySelectorAll('.ground').forEach(function (element) {
element.style.backgroundColor = groundColor;
element.style.color = groundColor;
});
document.querySelectorAll('.figure').forEach(function (element) {
element.style.backgroundColor = figureColor;
element.style.color = figureColor;
});
document.querySelector('#figureText').textContent = figureColor;
document.querySelector('#groundText').textContent = groundColor;
// new pushState
var combinedHexValue = '#' + figureColor.replace('#', '') + groundColor.replace('#', '');
var newUrl = document.location.href.split('#')[0] + combinedHexValue;
history.pushState({
color: combinedHexValue
}, 'Color State', newUrl);
}
const updateInputsFromHash = function (colorUrl) {
if (!colorUrl) {
colorUrl = document.location.href;
}
if (colorUrl.length && colorUrl.split('#')[1].length > 1) {
let colors = colorUrl.split('#')[1].toUpperCase();
colors = colors.replace(/[^0369CF]/g, '0');
var divs = document.querySelectorAll('#controls div.segment div');
for (var i = 0; i < colors.length; i++) {
divs[i].querySelector('input[type="radio"][value="' + colors[i] + '"]').checked = true;
}
}
};