<body>
<div class="row">
<form id="translate-form">
<input type="number" id="numeral" name="numeral" value="2025" min="1" max="3999">
<input type="submit" id="translate-button" value="Translate" />
</form>
</div>
<div class="row">
<div id="marble-block">
<p id="roman-numeral">MMXXV</p>
<div id="numeral-reveal-bar"></div>
<div id="dust-cloud">
<svg class="dust" id="dust-1" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-2" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-3" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-4" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-5" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-6" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-7" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
<svg class="dust" id="dust-8" width="10" height="10" overflow="visible">
<circle cx="5" cy="5" r="5" />
</svg>
</div>
<div id="hammer-and-chisel">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229 226" overflow="visible">
<g fill="none" fill-rule="nonzero">
<g id="chisel" transform="translate(13 109)" fill="#555">
<rect x="6" y="9" width="18" height="108" rx="8"/>
<rect width="30" height="19" rx="8"/>
</g>
<g id="hammer" transform="rotate(-90 54.5 54.5)" stroke="#979797">
<rect fill="#D6B588" x="42.5" y="49.5" width="24" height="179" rx="8"/>
<rect fill="#D8D8D8" x=".5" y=".5" width="108" height="56" rx="8"/>
</g>
</g>
</svg>
</div>
</div>
</div>
</body>
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
row-gap: 60px;
font-family: "Times New Roman", serif;
background-color: #1b1b1b;
min-height: 100vh;
}
form {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
}
input {
display: block;
font-weight: bold;
border: none;
padding: 10px 20px;
margin-right: 20px;
font-size: 42px;
color: #666;
border-radius: 10px;
}
#translate-button {
cursor: pointer;
font-size: 16px;
border: 2px solid gold;
transition: background-color 0.3s ease, color 0.3s ease;
}
#translate-button:hover {
background-color: #fff;
}
#translate-button:disabled {
cursor: not-allowed;
background-color: #aaa;
}
#marble-block {
position: relative;
padding: 80px;
border-radius: 10px;
box-shadow: 10px 10px #888888;
background: #F1F2FA;
}
#hammer-and-chisel {
position: absolute;
width: 164px;
display: none;
left: 20;
top: 0;
}
#dust-cloud {
position: absolute;
top: 0;
left: 20;
width: 200px;
display: none;
}
.dust {
position: absolute;
top: 0;
left: 0;
width: 10px;
}
#roman-numeral {
padding: 0;
margin: 0;
font-size: 108px;
font-family: "Times New Roman", serif;
background: #666;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
text-shadow: 3px 5px 1px rgba(245, 245, 245, 0.5);
}
#numeral-reveal-bar {
position: absolute;
right: 0;
top: 0;
margin: 0;
padding: 0;
border-radius: 10px;
background: #F1F2FA;
}
@media only screen and (max-width: 600px) {
#hammer-and-chisel {
width: 112px;
}
#roman-numeral {
font-size: 54px;
}
}
let numberDisplay = document.getElementById("roman-numeral");
let inputEl = document.getElementById('numeral');
let translateButton = document.getElementById('translate-button');
let translateForm = document.getElementById('translate-form');
let hammerAndChisel = document.getElementById('hammer-and-chisel');
let numeralRevealBar = document.getElementById('numeral-reveal-bar');
let marbleBlock = document.getElementById('marble-block');
let dustCloud = document.getElementById('dust-cloud');
let dustParticles = document.getElementsByClassName("dust");
var intToRoman = function(num) {
const symbolVals = { 'M': 1000, 'CM': 900, 'D': 500, 'CD': 400,
'C': 100, 'XC': 90, 'L': 50, 'XL': 40, 'X': 10, 'IX': 9,
'V': 5, 'IV': 4, 'I': 1 }
const symbols = Object.keys(symbolVals)
let roman = ""
let currentSymbolIdx = 0
while (num > 0) {
let currentVal = symbolVals[symbols[currentSymbolIdx]]
if (num - currentVal >= 0) {
roman += symbols[currentSymbolIdx]
num -= currentVal
} else {
currentSymbolIdx += 1
}
}
return roman
};
translateForm.addEventListener('submit', function(event) {
event.preventDefault();
let inputVal = parseInt(inputEl.value, 10);
if (Number.isInteger(inputVal)) {
if (inputVal >= 1 && inputVal <= 3999) {
let romanNum = intToRoman(inputVal);
numberDisplay.innerHTML = romanNum;
hammerAndChisel.style.display = 'block';
dustCloud.style.display = 'block';
translateButton.disabled = true;
runLoadingAnim();
}
}
});
function runLoadingAnim() {
let tl = gsap.timeline({defaults: {ease: 'none', repeat: -1, duration: 0.25, yoyo: true }})
tl.set("#chisel", {
rotate: 15,
})
tl.set("#hammer", {
y: numberDisplay.offsetHeight + 80,
transformOrigin: '50% 100%',
rotate: -60,
})
tl.to('#hammer', {
y: numberDisplay.offsetHeight + 50,
transformOrigin: '50% 100%',
rotate: 0
})
let leftToRight = gsap.timeline({defaults: { ease: 'none', duration: 2 }})
leftToRight.add('start')
leftToRight.set('#dust-cloud', {
y: marbleBlock.offsetHeight / 2 + 15,
})
leftToRight.set('#numeral-reveal-bar', {
width: marbleBlock.offsetWidth,
height: marbleBlock.offsetHeight,
})
runDustCloudAnimation(leftToRight)
leftToRight.to('#numeral-reveal-bar', {
width: 0,
delay: 0.1
}, 'start')
leftToRight.to('#hammer-and-chisel', {
x: numberDisplay.offsetWidth,
onComplete: () => {
hammerAndChisel.style.display = 'none';
gsap.set("#hammer-and-chisel", { x: 20 });
}
}, 'start')
leftToRight.to('#dust-cloud', {
x: numberDisplay.offsetWidth,
onComplete: () => {
dustCloud.style.display = 'none';
gsap.set("#dust-cloud", { x: 20 });
translateButton.disabled = false;
}
}, 'start')
}
window.onload = function() {
hammerAndChisel.style.display = 'block';
dustCloud.style.display = 'block';
translateButton.disabled = true;
runLoadingAnim();
}
function runDustCloudAnimation(timeline) {
const commonProps = { ease: 'power1.out', opacity: 0, duration: 0.25, repeat: 4, repeatDelay: 0.25 }
const dustResetProps = { x: 0, y: 0, opacity: 1 }
timeline.to('#dust-1', {
y: -marbleBlock.offsetHeight / 2,
...commonProps,
onComplete: () => {
gsap.set('#dust-1', dustResetProps)
}
}, 'start')
timeline.to('#dust-2', {
x: -marbleBlock.offsetHeight / 4,
y: -marbleBlock.offsetHeight / 4,
...commonProps,
onComplete: () => {
gsap.set('#dust-2', dustResetProps)
}
}, 'start')
timeline.to('#dust-3', {
y: marbleBlock.offsetHeight / 2,
...commonProps,
onComplete: () => {
gsap.set('#dust-3', dustResetProps)
}
}, 'start')
timeline.to('#dust-4', {
x: marbleBlock.offsetHeight / 4,
y: marbleBlock.offsetHeight / 4,
...commonProps,
onComplete: () => {
gsap.set('#dust-4', dustResetProps)
}
}, 'start')
timeline.to('#dust-5', {
x: -marbleBlock.offsetHeight / 2,
...commonProps,
onComplete: () => {
gsap.set('#dust-5', dustResetProps)
}
}, 'start')
timeline.to('#dust-6', {
x: -marbleBlock.offsetHeight / 4,
y: marbleBlock.offsetHeight / 4,
...commonProps,
onComplete: () => {
gsap.set('#dust-6', dustResetProps)
}
}, 'start')
timeline.to('#dust-7', {
x: marbleBlock.offsetHeight / 2,
...commonProps,
onComplete: () => {
gsap.set('#dust-7', dustResetProps)
}
}, 'start')
timeline.to('#dust-8', {
x: marbleBlock.offsetHeight / 4,
y: -marbleBlock.offsetHeight / 4,
...commonProps,
onComplete: () => {
gsap.set('#dust-8', dustResetProps)
}
}, 'start')
}
This Pen doesn't use any external CSS resources.