252 lines
8.8 KiB
HTML
252 lines
8.8 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Modern Responsive Analog Clock</title>
|
||
|
|
<style>
|
||
|
|
/* CSS Variables for easy theming and sizing */
|
||
|
|
:root {
|
||
|
|
--bg-color: #ffffff;
|
||
|
|
--clock-face: #f9f9f9;
|
||
|
|
--text-color: #333333;
|
||
|
|
--accent-color: #e74c3c; /* Red for second hand */
|
||
|
|
--hand-color: #2c3e50;
|
||
|
|
--shadow-light: rgba(255, 255, 255, 0.8);
|
||
|
|
--shadow-dark: rgba(0, 0, 0, 0.1);
|
||
|
|
--clock-size: min(80vw, 80vh); /* Responsive unit */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Reset and Base Layout */
|
||
|
|
* {
|
||
|
|
box-sizing: border-box;
|
||
|
|
margin: 0;
|
||
|
|
padding: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
body {
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
align-items: center;
|
||
|
|
min-height: 100vh;
|
||
|
|
background-color: var(--bg-color);
|
||
|
|
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||
|
|
overflow: hidden; /* Prevent scrollbars if zoomed */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Main Clock Container */
|
||
|
|
.clock-container {
|
||
|
|
position: relative;
|
||
|
|
width: var(--clock-size);
|
||
|
|
height: var(--clock-size);
|
||
|
|
border-radius: 50%;
|
||
|
|
background: var(--clock-face);
|
||
|
|
/* Soft neumorphic shadow */
|
||
|
|
box-shadow:
|
||
|
|
20px 20px 60px var(--shadow-dark),
|
||
|
|
-20px -20px 60px var(--shadow-light),
|
||
|
|
inset 5px 5px 10px var(--shadow-dark),
|
||
|
|
inset -5px -5px 10px var(--shadow-light);
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Clock Face Numbers */
|
||
|
|
.clock-face {
|
||
|
|
position: relative;
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
text-align: center;
|
||
|
|
color: var(--text-color);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Number positioning using transforms */
|
||
|
|
.number {
|
||
|
|
position: absolute;
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
text-align: center;
|
||
|
|
/* Rotate the container to the angle */
|
||
|
|
transform: rotate(calc(var(--rotation) * 1deg));
|
||
|
|
pointer-events: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.number span {
|
||
|
|
display: block;
|
||
|
|
font-size: calc(var(--clock-size) * 0.08);
|
||
|
|
font-weight: 600;
|
||
|
|
/* Counter-rotate the text so it stays upright */
|
||
|
|
transform: rotate(calc(var(--rotation) * -1deg));
|
||
|
|
padding-top: 5%; /* Push number away from absolute center edge */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Hands Styles */
|
||
|
|
.hand {
|
||
|
|
position: absolute;
|
||
|
|
bottom: 50%;
|
||
|
|
left: 50%;
|
||
|
|
transform-origin: bottom center;
|
||
|
|
border-radius: 4px;
|
||
|
|
z-index: 10;
|
||
|
|
/* Using CSS variables updated by JS */
|
||
|
|
transform: translateX(-50%) rotate(var(--deg));
|
||
|
|
will-change: transform; /* Performance optimization */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Hour Hand */
|
||
|
|
.hand.hour {
|
||
|
|
width: calc(var(--clock-size) * 0.025);
|
||
|
|
height: calc(var(--clock-size) * 0.25);
|
||
|
|
background: var(--hand-color);
|
||
|
|
z-index: 11;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Minute Hand */
|
||
|
|
.hand.min {
|
||
|
|
width: calc(var(--clock-size) * 0.015);
|
||
|
|
height: calc(var(--clock-size) * 0.35);
|
||
|
|
background: var(--hand-color);
|
||
|
|
z-index: 12;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Second Hand */
|
||
|
|
.hand.second {
|
||
|
|
width: calc(var(--clock-size) * 0.008);
|
||
|
|
height: calc(var(--clock-size) * 0.40);
|
||
|
|
background: var(--accent-color);
|
||
|
|
z-index: 13;
|
||
|
|
/* Add a tail to the second hand */
|
||
|
|
}
|
||
|
|
|
||
|
|
.hand.second::after {
|
||
|
|
content: '';
|
||
|
|
position: absolute;
|
||
|
|
width: 100%;
|
||
|
|
height: 20%;
|
||
|
|
bottom: -20%;
|
||
|
|
left: 0;
|
||
|
|
background: var(--accent-color);
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Center Nut/Pin */
|
||
|
|
.center-nut {
|
||
|
|
position: absolute;
|
||
|
|
width: calc(var(--clock-size) * 0.04);
|
||
|
|
height: calc(var(--clock-size) * 0.04);
|
||
|
|
background: var(--accent-color);
|
||
|
|
border: 2px solid #fff;
|
||
|
|
border-radius: 50%;
|
||
|
|
z-index: 14;
|
||
|
|
top: 50%;
|
||
|
|
left: 50%;
|
||
|
|
transform: translate(-50%, -50%);
|
||
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Digital Time Display (Optional, for accessibility/clarity) */
|
||
|
|
.digital-readout {
|
||
|
|
position: absolute;
|
||
|
|
bottom: 25%;
|
||
|
|
font-size: calc(var(--clock-size) * 0.05);
|
||
|
|
font-family: 'Courier New', monospace;
|
||
|
|
color: var(--text-color);
|
||
|
|
opacity: 0.5;
|
||
|
|
font-weight: bold;
|
||
|
|
letter-spacing: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
|
||
|
|
<!-- Semantic Clock Container -->
|
||
|
|
<main class="clock-container" id="clock" aria-label="Analog Clock displaying current time">
|
||
|
|
|
||
|
|
<div class="clock-face">
|
||
|
|
<!-- Numbers 1-12 generated by loop logic below for cleaner HTML -->
|
||
|
|
<!-- Manually placing 12, 3, 6, 9 for structure, others via JS -->
|
||
|
|
<div class="number" style="--rotation: 30"><span>1</span></div>
|
||
|
|
<div class="number" style="--rotation: 60"><span>2</span></div>
|
||
|
|
<div class="number" style="--rotation: 90"><span>3</span></div>
|
||
|
|
<div class="number" style="--rotation: 120"><span>4</span></div>
|
||
|
|
<div class="number" style="--rotation: 150"><span>5</span></div>
|
||
|
|
<div class="number" style="--rotation: 180"><span>6</span></div>
|
||
|
|
<div class="number" style="--rotation: 210"><span>7</span></div>
|
||
|
|
<div class="number" style="--rotation: 240"><span>8</span></div>
|
||
|
|
<div class="number" style="--rotation: 270"><span>9</span></div>
|
||
|
|
<div class="number" style="--rotation: 300"><span>10</span></div>
|
||
|
|
<div class="number" style="--rotation: 330"><span>11</span></div>
|
||
|
|
<div class="number" style="--rotation: 0"><span>12</span></div>
|
||
|
|
|
||
|
|
<!-- Hands -->
|
||
|
|
<div class="hand hour" id="hour-hand"></div>
|
||
|
|
<div class="hand min" id="min-hand"></div>
|
||
|
|
<div class="hand second" id="sec-hand"></div>
|
||
|
|
|
||
|
|
<!-- Center Pin -->
|
||
|
|
<div class="center-nut"></div>
|
||
|
|
|
||
|
|
<!-- Optional small digital readout -->
|
||
|
|
<div class="digital-readout" id="digital-time">00:00:00</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
/**
|
||
|
|
* Clock Logic
|
||
|
|
* Uses requestAnimationFrame for a smooth, high-performance update loop
|
||
|
|
* rather than a simple setInterval, ensuring the second hand sweeps smoothly.
|
||
|
|
*/
|
||
|
|
|
||
|
|
const hourHand = document.getElementById('hour-hand');
|
||
|
|
const minHand = document.getElementById('min-hand');
|
||
|
|
const secHand = document.getElementById('sec-hand');
|
||
|
|
const clock = document.getElementById('clock');
|
||
|
|
const digitalReadout = document.getElementById('digital-time');
|
||
|
|
|
||
|
|
function updateClock() {
|
||
|
|
const now = new Date();
|
||
|
|
|
||
|
|
// Get precise time components
|
||
|
|
const seconds = now.getSeconds();
|
||
|
|
const milliseconds = now.getMilliseconds();
|
||
|
|
const minutes = now.getMinutes();
|
||
|
|
const hours = now.getHours();
|
||
|
|
|
||
|
|
// Calculate degrees
|
||
|
|
|
||
|
|
// Second hand: includes milliseconds for a "sweep" animation
|
||
|
|
// Formula: (seconds + ms/1000) / 60 * 360
|
||
|
|
const secDegrees = ((seconds + (milliseconds / 1000)) / 60) * 360;
|
||
|
|
|
||
|
|
// Minute hand: includes seconds fraction for smooth movement between ticks
|
||
|
|
const minDegrees = ((minutes + seconds / 60) / 60) * 360;
|
||
|
|
|
||
|
|
// Hour hand: includes minutes fraction
|
||
|
|
const hourDegrees = ((hours % 12 + minutes / 60) / 12) * 360;
|
||
|
|
|
||
|
|
// Apply CSS variables to rotate the hands
|
||
|
|
// We set the variable --deg on the specific elements
|
||
|
|
secHand.style.setProperty('--deg', `${secDegrees}deg`);
|
||
|
|
minHand.style.setProperty('--deg', `${minDegrees}deg`);
|
||
|
|
hourHand.style.setProperty('--deg', `${hourDegrees}deg`);
|
||
|
|
|
||
|
|
// Update Accessibility Label (optional but good practice)
|
||
|
|
const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||
|
|
clock.setAttribute('aria-label', `Current time is ${timeString}`);
|
||
|
|
|
||
|
|
// Update small digital readout
|
||
|
|
digitalReadout.textContent = timeString;
|
||
|
|
|
||
|
|
// Request next frame
|
||
|
|
requestAnimationFrame(updateClock);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize the clock
|
||
|
|
requestAnimationFrame(updateClock);
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|