Modern Neon Clock is a free, copy-paste ready analog clock JavaScript component
built with pure HTML, CSS, and vanilla JavaScript — zero external libraries. The clock face is a
450px circle using radial-gradient(circle at 30% 30%, #2a2a2a, #0d0d0d)
with an 8px solid #1a1a1a border, inner rim via ::after pseudo-element,
and a 4-second pulsating @keyframes glow animation cycling outer box-shadow
from 60px to 80px spread.
Hand movement is sub-frame smooth using millisecond interpolation:
secondDeg = (seconds + milliseconds/1000) * 6,
minuteDeg = (minutes + seconds/60) * 6,
hourDeg = (hours + minutes/60) * 30.
setInterval(updateClock, 50) fires 20 times per second; the CSS transition
cubic-bezier(0.4, 0.0, 0.2, 1) smooths between positions for fluid motion.
The hour hand is 10px×120px white gradient with glow; the
minute hand is 8px×160px; the red second hand
is 3px×180px with box-shadow: 0 0 15px rgba(255,68,68,0.6).
A standard clock updating once per second produces a ticking second hand — visible jumps.
The solution: include now.getMilliseconds() in the rotation calculation.
secondDeg = (seconds + milliseconds/1000) * 6 means at exactly 30.5 seconds,
the hand is at 31.5 * 6 = 183° rather than snapping between 180° and 186°.
At 50ms update frequency, each step is only 0.3° — invisible to the eye.
The same fractional interpolation applies to the minute hand (adds
seconds/60 to minutes) and the hour hand (adds
minutes/60 to hours), so all three hands move continuously rather than stepping.
Each hour number is placed using polar-to-Cartesian conversion with a radius of 185px.
For number at index i: angle = (i * 30 - 90) * Math.PI / 180.
The -90 offset rotates the coordinate system so index 0 (12 o'clock) starts
at the top rather than the right. Then: x = 185 * Math.cos(angle) + 205,
y = 185 * Math.sin(angle) + 205. The +205 offset centers each
number in the 450px container (225px center, minus 20px for the 40px number element half-width).
The same math pattern — but using transform-origin: 50% 205px and CSS rotation
— places all 60 tick marks without any positional math at all.
Modern Neon Clock is ideal for: desktop wallpaper web apps, smart home dashboards, new tab page extensions, admin panel time widgets, gaming/stream overlays, kiosk displays, JavaScript learning tutorials (polar math, setInterval, CSS transforms), dark UI portfolios, and any project needing a premium clock widget without a charting library.
Released under Creative Commons Attribution 4.0 International (CC BY 4.0). Free for personal and commercial use with attribution to Coodeverse.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Neon Clock</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0a0a0a 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.clock-container {
position: relative;
width: 450px;
height: 450px;
background: radial-gradient(circle at 30% 30%, #2a2a2a, #0d0d0d);
border-radius: 50%;
box-shadow:
0 0 60px rgba(255, 255, 255, 0.1),
inset 0 0 40px rgba(0, 0, 0, 0.8),
0 20px 50px rgba(0, 0, 0, 0.9);
border: 8px solid #1a1a1a;
}
.clock-container::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: radial-gradient(circle at 50% 50%, transparent 60%, rgba(255, 255, 255, 0.02) 100%);
}
.clock-container::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 92%;
height: 92%;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.05);
}
.number {
position: absolute;
font-size: 32px;
font-weight: 300;
color: #fff;
text-align: center;
width: 40px;
height: 40px;
line-height: 40px;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
z-index: 2;
transition: all 0.3s ease;
}
.tick {
position: absolute;
width: 2px;
height: 12px;
background: rgba(255, 255, 255, 0.3);
left: 50%;
top: 20px;
margin-left: -1px;
transform-origin: 50% 205px;
}
.tick.major {
height: 18px;
width: 3px;
background: rgba(255, 255, 255, 0.5);
margin-left: -1.5px;
}
.clock-hand {
position: absolute;
transform-origin: bottom center;
left: 50%;
bottom: 50%;
transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.8));
}
.hour-hand {
width: 10px;
height: 120px;
margin-left: -5px;
background: linear-gradient(to top, #fff, #e0e0e0);
border-radius: 8px 8px 0 0;
z-index: 5;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.minute-hand {
width: 8px;
height: 160px;
margin-left: -4px;
background: linear-gradient(to top, #fff, #f5f5f5);
border-radius: 6px 6px 0 0;
z-index: 6;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.4);
}
.second-hand {
width: 3px;
height: 180px;
margin-left: -1.5px;
background: linear-gradient(to top, #ff4444, #ff0000);
border-radius: 3px 3px 0 0;
z-index: 7;
box-shadow: 0 0 15px rgba(255, 68, 68, 0.6);
}
.center-circle {
position: absolute;
width: 24px;
height: 24px;
background: radial-gradient(circle, #fff, #ccc);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 8;
box-shadow:
0 0 20px rgba(255, 255, 255, 0.5),
inset 0 2px 5px rgba(0, 0, 0, 0.3);
border: 3px solid #1a1a1a;
}
.center-circle::after {
content: '';
position: absolute;
width: 8px;
height: 8px;
background: #ff4444;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 10px rgba(255, 68, 68, 0.8);
}
.digital-time {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
font-size: 20px;
font-weight: 300;
color: #4CAF50;
text-shadow: 0 0 15px rgba(76, 175, 80, 0.8);
font-family: 'Courier New', monospace;
letter-spacing: 2px;
z-index: 2;
}
.date-display {
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
font-weight: 300;
color: #aaa;
text-transform: uppercase;
letter-spacing: 1px;
z-index: 2;
}
@keyframes glow {
0%, 100% { box-shadow: 0 0 60px rgba(255, 255, 255, 0.1), inset 0 0 40px rgba(0, 0, 0, 0.8), 0 20px 50px rgba(0, 0, 0, 0.9); }
50% { box-shadow: 0 0 80px rgba(255, 255, 255, 0.15), inset 0 0 40px rgba(0, 0, 0, 0.8), 0 20px 50px rgba(0, 0, 0, 0.9); }
}
.clock-container { animation: glow 4s ease-in-out infinite; }
</style>
</head>
<body>
<div class="clock-container">
<div class="date-display" id="dateDisplay"></div>
<div class="digital-time" id="digitalTime"></div>
<div class="clock-hand hour-hand" id="hourHand"></div>
<div class="clock-hand minute-hand" id="minuteHand"></div>
<div class="clock-hand second-hand" id="secondHand"></div>
<div class="center-circle"></div>
</div>
<script>
const clockContainer = document.querySelector('.clock-container');
const radius = 185;
const numbers = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
// Create hour numbers
numbers.forEach((num, index) => {
const angle = (index * 30 - 90) * (Math.PI / 180);
const x = radius * Math.cos(angle) + 205;
const y = radius * Math.sin(angle) + 205;
const numberDiv = document.createElement('div');
numberDiv.className = 'number';
numberDiv.textContent = num;
numberDiv.style.left = x + 'px';
numberDiv.style.top = y + 'px';
clockContainer.appendChild(numberDiv);
});
// Create tick marks
for (let i = 0; i < 60; i++) {
const tick = document.createElement('div');
tick.className = i % 5 === 0 ? 'tick major' : 'tick';
tick.style.transform = `rotate(${i * 6}deg)`;
clockContainer.appendChild(tick);
}
// Update clock
function updateClock() {
const now = new Date();
const hours = now.getHours() % 12;
const minutes = now.getMinutes();
const seconds = now.getSeconds();
const milliseconds = now.getMilliseconds();
// Smooth second hand movement
const secondDeg = (seconds + milliseconds / 1000) * 6;
const minuteDeg = (minutes + seconds / 60) * 6;
const hourDeg = (hours + minutes / 60) * 30;
document.getElementById('hourHand').style.transform = `rotate(${hourDeg}deg)`;
document.getElementById('minuteHand').style.transform = `rotate(${minuteDeg}deg)`;
document.getElementById('secondHand').style.transform = `rotate(${secondDeg}deg)`;
// Update digital time
const timeString = now.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
document.getElementById('digitalTime').textContent = timeString;
// Update date
const dateString = now.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric'
});
document.getElementById('dateDisplay').textContent = dateString;
}
updateClock();
setInterval(updateClock, 50);
</script>
</body>
</html>
Common questions about the free Modern Neon Clock JavaScript analog clock component.
Yes — 100% free under Creative Commons Attribution 4.0. No sign-up, no payment. Click Download Project and use it in personal, commercial, dashboard, or wallpaper projects with attribution to Coodeverse.
Use millisecond interpolation: const ms = now.getMilliseconds(). Then: secondDeg = (seconds + ms/1000) * 6; minuteDeg = (minutes + seconds/60) * 6; hourDeg = (hours + minutes/60) * 30. Run setInterval(updateClock, 50) (20fps). Add CSS transition: transform 0.3s cubic-bezier(0.4,0,0.2,1) on the hand elements for smooth easing. Each update step is only 0.3° — imperceptibly small.
Use polar math: angle = (index * 30 - 90) * Math.PI / 180. The -90 rotates the origin so 12 is at top. Then x = radius * Math.cos(angle) + 205 and y = radius * Math.sin(angle) + 205. The +205 is center offset (450/2 minus half number element width). Set: div.style.left = x + 'px', div.style.top = y + 'px'. Radius is 185 for this 450px clock.
Use box-shadow for outer glow: 0 0 60px rgba(255,255,255,0.1) on the container. Second hand glow: box-shadow: 0 0 15px rgba(255,68,68,0.6). Digital text glow: text-shadow: 0 0 15px rgba(76,175,80,0.8). The pulsating effect: @keyframes glow { 50% { box-shadow: 0 0 80px rgba(255,255,255,0.15)... } } with animation: glow 4s ease-in-out infinite on the container.
Create 60 div elements positioned at the top of the clock face. The key: set transform-origin: 50% 205px on each tick (205px = half of 450px clock height). Then rotate each: tick.style.transform = 'rotate(' + (i * 6) + 'deg)'. Every 5th tick (i % 5 === 0) is major (3px × 18px). Minor ticks are 2px × 12px. CSS transforms do the radial positioning — no position math needed.
Change second hand color (#ff4444) and glow (rgba(255,68,68,0.6)). Change digital display from #4CAF50 to cyan (#00ffff), purple, or blue. To resize, change the 450px width/height and update radius (185) and center offset (205) and tick transform-origin proportionally. Or use transform: scale(0.5) on the outer container for a quick resize without touching JS values.
Browse the full collection at coodeverse.com/projects — Pro Chronometer (stopwatch + CSV), Animated 3D Pie Chart, NEXUS 3D Pure CSS, Neon Toggle Switch, Holographic Login and more. All 100% free with complete HTML CSS JavaScript source code.