/* Fullscreen button — subtle, only appears on hover / tap, but always available */ .fs-toggle position: fixed; bottom: 25px; right: 25px; background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px); border: none; color: #ffefcf; font-size: 1.6rem; width: 52px; height: 52px; border-radius: 60px; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 30; transition: all 0.2s ease; font-weight: bold; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 245, 180, 0.5); pointer-events: auto;
/* optional smooth fade for idle mode (not interfering with fullscreen) */ @keyframes gentlePulse 0% text-shadow: 0 0 0px rgba(255,235,150,0); 100% text-shadow: 0 0 2px rgba(255,235,150,0.5); reloj analógico online pantalla completa
/* For touch devices - always visible slightly */ @media (max-width: 768px) .fs-toggle width: 48px; height: 48px; font-size: 1.4rem; bottom: 20px; right: 20px; .info-bar font-size: 0.7rem; bottom: 12px; padding: 6px 15px; white-space: nowrap; .digital-time font-size: 0.9rem; /* Fullscreen button — subtle, only appears on
<script> (function() // --- DOM elements --- const canvas = document.getElementById('analogCanvas'); const ctx = canvas.getContext('2d'); const digitalSpan = document.getElementById('liveDigitalClock'); // --- Clock geometry & sizing --- let size = 800; // base logical resolution (high quality) let centerX = 400, centerY = 400; let radius = 380; // clock face radius (inner) // update derived dimensions based on logical canvas (always 800x800) function updateGeometry() // canvas resolution fixed 800x800 for crisp lines canvas.width = 800; canvas.height = 800; size = 800; centerX = 400; centerY = 400; radius = 370; // leave margin for decorations updateGeometry(); // --- Helper: draw beautiful analog clock (full details) --- function drawClock() if (!ctx) return; const now = new Date(); const hoursRaw = now.getHours(); const minutes = now.getMinutes(); const seconds = now.getSeconds(); const milliseconds = now.getMilliseconds(); // precise seconds for smooth sweep second hand const secondsExact = seconds + milliseconds / 1000; // minutes with seconds precision for minute hand smoothness (optional) const minutesExact = minutes + secondsExact / 60; // hours with minute precision const hoursExact = (hoursRaw % 12) + minutesExact / 60; // angles in radians (0 degree = 12 o'clock, so we subtract 90deg offset) const secondAngle = (secondsExact * 6) * Math.PI / 180; // 360/60 = 6 deg per sec const minuteAngle = (minutesExact * 6) * Math.PI / 180; const hourAngle = (hoursExact * 30) * Math.PI / 180; // 360/12 = 30 deg per hour // ---- DRAW FACE BACKGROUND ---- ctx.clearRect(0, 0, size, size); // Outer metallic ring gradient const gradRing = ctx.createLinearGradient(20, 20, size-20, size-20); gradRing.addColorStop(0, '#f7e9c3'); gradRing.addColorStop(0.5, '#e2cba4'); gradRing.addColorStop(1, '#c7a86b'); ctx.beginPath(); ctx.arc(centerX, centerY, radius + 12, 0, 2 * Math.PI); ctx.fillStyle = gradRing; ctx.fill(); ctx.shadowBlur = 0; // main face (radial gradient for depth) const faceGrad = ctx.createRadialGradient(centerX-25, centerY-25, 30, centerX, centerY, radius); faceGrad.addColorStop(0, '#fffaf0'); faceGrad.addColorStop(0.8, '#f7e9cf'); faceGrad.addColorStop(1, '#e9d9b4'); ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); ctx.fillStyle = faceGrad; ctx.fill(); ctx.shadowBlur = 0; // inner subtle ring ctx.beginPath(); ctx.arc(centerX, centerY, radius-8, 0, 2 * Math.PI); ctx.strokeStyle = '#c0a16b'; ctx.lineWidth = 2.5; ctx.stroke(); // ---- DRAW HOUR MARKERS (roman or elegant? use classic ticks) ---- for (let i = 1; i <= 12; i++) let angle = (i * 30) * Math.PI / 180; // 30 deg each hour let startRad = radius - 18; let endRad = radius - 4; // for 12, 3, 6, 9 make them slightly longer if (i % 3 === 0) startRad = radius - 28; endRad = radius - 6; const xStart = centerX + startRad * Math.sin(angle); const yStart = centerY - startRad * Math.cos(angle); const xEnd = centerX + endRad * Math.sin(angle); const yEnd = centerY - endRad * Math.cos(angle); ctx.beginPath(); ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yEnd); ctx.lineWidth = (i % 3 === 0) ? 4 : 2.5; ctx.strokeStyle = '#3e2a1f'; ctx.stroke(); // ---- MINUTE TICKS (60 marks) ---- for (let i = 0; i < 60; i++) let angle = (i * 6) * Math.PI / 180; let startRad = radius - 12; let endRad = radius - 3; // every 5th minute (already hour marker but still subtle extra) if (i % 5 === 0) continue; // skip positions with hour markers to avoid overlap const xStart = centerX + startRad * Math.sin(angle); const yStart = centerY - startRad * Math.cos(angle); const xEnd = centerX + endRad * Math.sin(angle); const yEnd = centerY - endRad * Math.cos(angle); ctx.beginPath(); ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yEnd); ctx.lineWidth = 1.8; ctx.strokeStyle = '#8b6946'; ctx.stroke(); // ---- NUMERALS (Elegant serif style) ---- ctx.font = `bold $Math.floor(radius * 0.13)px "Segoe UI", "Georgia", serif`; ctx.fillStyle = '#2c2118'; ctx.shadowBlur = 0; ctx.textAlign = "center"; ctx.textBaseline = "middle"; const numerals = ["XII", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI"]; for (let i = 0; i < 12; i++) let angle = (i * 30) * Math.PI / 180; // angle offset for placement let textRadius = radius - 38; let x = centerX + textRadius * Math.sin(angle); let y = centerY - textRadius * Math.cos(angle); ctx.fillStyle = '#2c1a0c'; ctx.fillText(numerals[i], x, y); // add subtle shadow for depth ctx.fillStyle = '#f9e7c0'; ctx.fillText(numerals[i], x-1, y-1); ctx.fillStyle = '#2c1a0c'; ctx.fillText(numerals[i], x, y); // ---- draw brand / subtle mark ---- ctx.font = `400 $Math.floor(radius * 0.055)px "Courier New", monospace`; ctx.fillStyle = '#b8865b'; ctx.fillText("⚙ RELOJ", centerX, centerY + radius * 0.62); // ---- HANDS: HOUR ---- const hourLen = radius * 0.52; const hourWidth = 9; drawHand(ctx, centerX, centerY, hourAngle, hourLen, hourWidth, '#1f2c1c', 0.25); // ---- MINUTE HAND ---- const minuteLen = radius * 0.72; const minuteWidth = 6; drawHand(ctx, centerX, centerY, minuteAngle, minuteLen, minuteWidth, '#2c3e2b', 0.2); // ---- SECOND HAND (elegant red with needle tip, smooth motion) ---- const secondLen = radius * 0.82; const secondWidth = 2.5; drawSecondHand(ctx, centerX, centerY, secondAngle, secondLen, secondWidth, '#c9232e', 0.1); // ---- CENTER CAP / AXLE ---- ctx.beginPath(); ctx.arc(centerX, centerY, 14, 0, 2 * Math.PI); ctx.fillStyle = '#cfa668'; ctx.shadowBlur = 2; ctx.fill(); ctx.beginPath(); ctx.arc(centerX, centerY, 7, 0, 2 * Math.PI); ctx.fillStyle = '#e7cfa1'; ctx.fill(); ctx.beginPath(); ctx.arc(centerX, centerY, 3, 0, 2 * Math.PI); ctx.fillStyle = '#5a3e2a'; ctx.fill(); ctx.shadowBlur = 0; // ---- glass reflection effect (tiny highlight) ---- ctx.beginPath(); ctx.ellipse(centerX - 40, centerY - 45, 40, 28, 0.4, 0, 2 * Math.PI); ctx.fillStyle = 'rgba(255, 255, 240, 0.18)'; ctx.fill(); // Helper: draw standard hand (hour/minute) with rounded ends & gradient function drawHand(ctx, cx, cy, angle, length, width, color, shadowIntensity = 0.2) ctx.save(); ctx.shadowBlur = 4; ctx.shadowColor = `rgba(0,0,0,$shadowIntensity)`; ctx.beginPath(); // hand shape: polygon from base to tip const tipX = cx + length * Math.sin(angle); const tipY = cy - length * Math.cos(angle); const backX = cx - length * 0.18 * Math.sin(angle); const backY = cy + length * 0.18 * Math.cos(angle); const perpX = (width / 2) * Math.cos(angle); const perpY = (width / 2) * Math.sin(angle); ctx.moveTo(cx + perpX, cy - perpY); ctx.lineTo(tipX, tipY); ctx.lineTo(cx - perpX, cy + perpY); ctx.lineTo(backX, backY); ctx.closePath(); // fill gradient for metallic look const grad = ctx.createLinearGradient(cx - 10, cy - 5, cx + 10, cy + 8); grad.addColorStop(0, color); grad.addColorStop(1, '#1a1f15'); ctx.fillStyle = grad; ctx.fill(); ctx.shadowBlur = 0; ctx.restore(); // Special second hand with counterweight and thinner design function drawSecondHand(ctx, cx, cy, angle, length, width, color, shadowIntensity = 0.15) ctx.save(); ctx.shadowBlur = 3; ctx.shadowColor = `rgba(0,0,0,$shadowIntensity)`; const tipX = cx + length * Math.sin(angle); const tipY = cy - length * Math.cos(angle); // counterweight behind center const tailLen = length * 0.22; const tailX = cx - tailLen * Math.sin(angle); const tailY = cy + tailLen * Math.cos(angle); ctx.beginPath(); ctx.moveTo(cx + (width/1.8) * Math.cos(angle), cy - (width/1.8) * Math.sin(angle)); ctx.lineTo(tipX, tipY); ctx.lineTo(cx - (width/1.8) * Math.cos(angle), cy + (width/1.8) * Math.sin(angle)); ctx.lineTo(tailX, tailY); ctx.closePath(); const gradSec = ctx.createLinearGradient(cx - 6, cy, cx + 6, cy + 12); gradSec.addColorStop(0, '#e05a5a'); gradSec.addColorStop(1, '#a1222b'); ctx.fillStyle = gradSec; ctx.fill(); // add a small dot at tip ctx.beginPath(); ctx.arc(tipX, tipY, 3, 0, 2 * Math.PI); ctx.fillStyle = '#cf5a2e'; ctx.fill(); ctx.restore(); // Update digital time in bottom bar function updateDigitalDisplay() const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0'); digitalSpan.innerText = `$hours:$minutes:$seconds`; // Animation loop: draw clock 60fps + update digital let animationFrameId = null; function tick() drawClock(); updateDigitalDisplay(); animationFrameId = requestAnimationFrame(tick); // --- RESIZE HANDLER (maintain canvas visual size relative to CSS) --- function handleCanvasScale() // No need to resize logical canvas (800x800). But we ensure CSS scaling preserves crispness. // Re-draw once after any layout shift (like fullscreen change) if (canvas) // Force a redraw to adjust any subtle scaling artifacts drawClock(); updateDigitalDisplay(); // --- FULLSCREEN API with cross-browser support --- const fullscreenBtn = document.getElementById('fullscreenBtn'); function toggleFullscreen() const elem = document.documentElement; // entire page fullscreen if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) elem.webkitRequestFullscreen else document.msExitFullscreen; if (exitMethod) exitMethod.call(document); fullscreenBtn.addEventListener('click', (e) => e.stopPropagation(); toggleFullscreen(); ); // Listen for fullscreen change events to redraw if needed (resize artifacts) document.addEventListener('fullscreenchange', () => setTimeout(() => handleCanvasScale(); drawClock(); updateDigitalDisplay(); , 50); ); document.addEventListener('webkitfullscreenchange', () => setTimeout(() => handleCanvasScale(); drawClock(); updateDigitalDisplay(); , 50); ); document.addEventListener('mozfullscreenchange', () => setTimeout(() => handleCanvasScale(); drawClock(); updateDigitalDisplay(); , 50); ); // Also handle window resize to keep CSS proportions perfect window.addEventListener('resize', () => // re-draw ensures visual sync if (canvas) drawClock(); updateDigitalDisplay(); ); // optional: touch or click on canvas just gives a nice feel (no action, but we can add haptic? just for experience) canvas.addEventListener('click', () => // subtle visual feedback: light flash? optional; but no need, but adds user awareness canvas.style.transition = 'transform 0.05s'; canvas.style.transform = 'scale(0.99)'; setTimeout(() => canvas.style.transform = ''; , 100); ); // init clock, start animation and first draw function init() updateGeometry(); drawClock(); updateDigitalDisplay(); tick(); // start smooth sweep & live digital init(); // Extra: when returning from background, ensure precision document.addEventListener('visibilitychange', () => if (!document.hidden) drawClock(); updateDigitalDisplay(); ); // Fix for devices with notch or orientation change: reflow canvas window.addEventListener('orientationchange', () => setTimeout(() => drawClock(); updateDigitalDisplay(); , 30); ); )(); </script> </body> </html> But we ensure CSS scaling preserves crispness
/* small digital time inside info bar */ .digital-time font-family: 'Courier New', 'Fira Code', monospace; font-weight: bold; font-size: 1.1rem; background: #00000066; padding: 4px 12px; border-radius: 40px; display: inline-block; letter-spacing: 1px;
<div class="info-bar"> 🕰️ RELOJ ANALÓGICO | <span class="digital-time" id="liveDigitalClock">--:--:--</span> ⏻ </div>