Last active
February 7, 2025 15:57
-
-
Save mrpelz/5341045ad38d0fd1043625d4d8453e86 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>MOBA-Time</title> | |
<style> | |
* { | |
cursor: none !important; | |
pointer-events: none !important; | |
user-select: none !important; | |
} | |
:root { | |
background-color: black; | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
} | |
body { | |
color: white; | |
height: 100vh; | |
margin: unset; | |
overflow: hidden; | |
padding: unset; | |
width: 100vw; | |
} | |
.clock { | |
height: 100vh; | |
position: absolute; | |
transform-origin: center; | |
width: 100vw; | |
} | |
#hand-hours { | |
fill: var(--hours-hand-color); | |
stroke: none; | |
} | |
#hand-minutes { | |
fill: var(--minutes-hand-color); | |
stroke: none; | |
} | |
#hand-seconds { | |
fill: var(--seconds-hand-color); | |
stroke: none; | |
} | |
#face-full { | |
fill: var(--face-color); | |
stroke: none; | |
} | |
#label { | |
fill: var(--label-color); | |
stroke: none; | |
} | |
#base { | |
fill: var(--fill-color); | |
stroke: var(--border-color); | |
stroke-width: var(--border-width); | |
} | |
#center { | |
fill: var(--center-color); | |
stroke: none; | |
} | |
</style> | |
<script> | |
/** | |
* @typedef ClockOptions | |
* @type {{ | |
* style: { | |
* borderColor: string, | |
* borderWidth: number, | |
* centerColor: string, | |
* faceColor: string, | |
* fillColor: string, | |
* hoursHandColor: string, | |
* labelColor: string, | |
* minutesHandColor: string, | |
* secondsHandColor: string | |
* }, | |
* config: { | |
* msDSTStepDuration: number, | |
* msSyncPause: number, | |
* msTransitionDuration: number, | |
* trackHoursHand: 'hours' | 'minutes' | 'seconds' | 'frames', | |
* trackMinutesHand: 'minutes' | 'seconds' | 'frames', | |
* trackSecondsHand: 'seconds' | 'frames' | |
* } | |
* }} | |
*/ | |
const DEG_CIRCLE = 360; | |
const MS_12_HOURS = 43200000; | |
const MS_1_HOUR = 3600000; | |
const MS_1_MINUTE = 60000; | |
const MS_1_SECOND = 1000; | |
const MS_DST_BACKWARD = 39600000; | |
const MS_DST_FORWARD = 3600000; | |
/** | |
* @type {ClockOptions} | |
*/ | |
const options = { | |
style: { | |
borderColor: 'lightgrey', | |
borderWidth: 64, | |
centerColor: 'gold', | |
faceColor: 'black', | |
fillColor: 'white', | |
hoursHandColor: 'black', | |
labelColor: 'black', | |
minutesHandColor: 'black', | |
secondsHandColor: '#BD2420' | |
}, | |
config: { | |
msDSTStepDuration: 500, | |
msSyncPause: 1500, | |
msTransitionDuration: 250, | |
trackHoursHand: 'minutes', | |
trackMinutesHand: 'minutes', | |
trackSecondsHand: 'frames' | |
} | |
}; | |
/** | |
* @param {number} input | |
*/ | |
function wrap(input) { | |
const wrapped = input % 1; | |
return wrapped < 0 ? wrapped + 1 : wrapped; | |
} | |
/** | |
* @param {Date} time | |
*/ | |
function dstType(time) { | |
const reference = new Date(); | |
reference.setTime(time.getTime()); | |
reference.setUTCHours(time.getUTCHours() - 1); | |
const offset = time.getTimezoneOffset(); | |
const offsetReference = reference.getTimezoneOffset(); | |
if (offset === offsetReference) return null; | |
return (offsetReference - offset) * MS_1_MINUTE; | |
} | |
// adapted from https://github.com/MrRaindrop/cubicbezier | |
/** | |
* @param {number} p1x | |
* @param {number} p1y | |
* @param {number} p2x | |
* @param {number} p2y | |
*/ | |
function cubicBezier(p1x, p1y, p2x, p2y) { | |
const ZERO_LIMIT = 1e-6; | |
// Calculate the polynomial coefficients, | |
// implicit first and last control points are (0,0) and (1,1). | |
const ax = (3 * p1x) - (3 * p2x) + 1; | |
const bx = (3 * p2x) - (6 * p1x); | |
const cx = 3 * p1x; | |
const ay = (3 * p1y) - (3 * p2y) + 1; | |
const by = (3 * p2y) - (6 * p1y); | |
const cy = 3 * p1y; | |
/** | |
* @param {number} t | |
*/ | |
const sampleCurveDerivativeX = (t) => { | |
// `ax t^3 + bx t^2 + cx t' expanded using Horner 's rule. | |
return (((3 * ax * t) + (2 * bx)) * t) + cx; | |
}; | |
/** | |
* @param {number} t | |
*/ | |
const sampleCurveX = (t) => { | |
return ((((ax * t) + bx) * t) + cx) * t; | |
}; | |
/** | |
* @param {number} t | |
*/ | |
const sampleCurveY = (t) => { | |
return ((((ay * t) + by) * t) + cy) * t; | |
}; | |
/** | |
* Given an x value, find a parametric value it came from. | |
* @param {number} x | |
*/ | |
const solveCurveX = (x) => { | |
let t2 = x; | |
let derivative; | |
let x2; | |
// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation | |
// First try a few iterations of Newton's method -- normally very fast. | |
// http://en.wikipedia.org/wiki/Newton's_method | |
for (let i = 0; i < 8; i += 1) { | |
// f(t)-x=0 | |
x2 = sampleCurveX(t2) - x; | |
if (Math.abs(x2) < ZERO_LIMIT) { | |
return t2; | |
} | |
derivative = sampleCurveDerivativeX(t2); | |
// == 0, failure | |
if (Math.abs(derivative) < ZERO_LIMIT) { | |
break; | |
} | |
t2 -= x2 / derivative; | |
} | |
// Fall back to the bisection method for reliability. | |
// bisection | |
// http://en.wikipedia.org/wiki/Bisection_method | |
let t1 = 1; | |
let t0 = 0; | |
t2 = x; | |
while (t1 > t0) { | |
x2 = sampleCurveX(t2) - x; | |
if (Math.abs(x2) < ZERO_LIMIT) { | |
return t2; | |
} | |
if (x2 > 0) { | |
t1 = t2; | |
} else { | |
t0 = t2; | |
} | |
t2 = (t1 + t0) / 2; | |
} | |
// Failure | |
return t2; | |
}; | |
/** | |
* @param {number} x | |
*/ | |
const solve = (x) => { | |
return sampleCurveY(solveCurveX(x)); | |
}; | |
return solve; | |
} | |
const transitionSlowHands = cubicBezier(0.4, 2.08, 0.55, 0.44); | |
const transitionSecondsHandStop = cubicBezier(0, 0, 0.58, 1); | |
const transitionSecondsHandStart = cubicBezier(0.42, 0, 1, 1); | |
addEventListener('DOMContentLoaded', () => { | |
const element = document.body; | |
const { | |
borderColor, | |
borderWidth, | |
centerColor, | |
faceColor, | |
fillColor, | |
hoursHandColor, | |
labelColor, | |
minutesHandColor, | |
secondsHandColor | |
} = options.style; | |
element.style.setProperty('--border-color', borderColor || 'none'); | |
element.style.setProperty('--border-width', borderWidth.toString() || '0'); | |
element.style.setProperty('--center-color', centerColor || 'none'); | |
element.style.setProperty('--face-color', faceColor || 'none'); | |
element.style.setProperty('--fill-color', fillColor || 'none'); | |
element.style.setProperty('--label-color', labelColor || 'none'); | |
element.style.setProperty('--hours-hand-color', hoursHandColor || 'none'); | |
element.style.setProperty('--minutes-hand-color', minutesHandColor || 'none'); | |
element.style.setProperty('--seconds-hand-color', secondsHandColor || 'none'); | |
const elementSecondsHand = document.getElementById('handle-seconds'); | |
const elementMinutesHand = document.getElementById('handle-minutes'); | |
const elementHoursHand = document.getElementById('handle-hours'); | |
const checkTime = () => { | |
const { | |
msDSTStepDuration, | |
msSyncPause, | |
msTransitionDuration, | |
trackHoursHand, | |
trackMinutesHand, | |
trackSecondsHand | |
} = options.config; | |
const time = new Date(); | |
const dst = dstType(time); | |
const ms = time.getMilliseconds(); | |
const s = time.getSeconds(); | |
const msSeconds = s * MS_1_SECOND; | |
const msSecondsFrames = msSeconds + ms; | |
const mRaw = time.getMinutes(); | |
const msDSTTransitionBase = ((mRaw * MS_1_MINUTE) + msSecondsFrames); | |
const msDSTTransitionTime = (() => { | |
if (!msDSTStepDuration || !dst) return null; | |
const handTransition = ( | |
(dst > 0 ? MS_DST_FORWARD : MS_DST_BACKWARD) / MS_1_MINUTE | |
) * msDSTStepDuration; | |
const handDrift = ( | |
handTransition / MS_1_MINUTE | |
) * msDSTStepDuration; | |
const result = handTransition + handDrift; | |
return msDSTTransitionBase > result ? null : result; | |
})(); | |
const m = (() => { | |
if ( | |
!msDSTTransitionTime | |
|| msDSTTransitionBase > msDSTTransitionTime | |
) return mRaw; | |
const result = Math.floor( | |
( | |
msDSTTransitionBase / msDSTTransitionTime | |
) * ( | |
msDSTTransitionTime / MS_1_MINUTE | |
) * ( | |
MS_1_MINUTE / msDSTStepDuration | |
) | |
); | |
return result; | |
})(); | |
const msMinutes = m * MS_1_MINUTE; | |
const hRaw = time.getHours(); | |
const h = (() => { | |
if ( | |
!msDSTTransitionTime | |
|| msDSTTransitionBase > msDSTTransitionTime | |
) return hRaw; | |
return dst > 0 ? hRaw - 1 : hRaw + 1; | |
})(); | |
const msHours = h * MS_1_HOUR; | |
/** | |
* @param {number} from | |
* @param {number} to | |
* @param {number} timeBase | |
*/ | |
const animate = ( | |
from, | |
to, | |
timeBase | |
) => { | |
if (!msTransitionDuration || timeBase > msTransitionDuration) return to; | |
const pAnimation = transitionSlowHands( | |
timeBase / msTransitionDuration | |
); | |
return from + (Math.abs(from - to) * pAnimation); | |
}; | |
/** | |
* @param {number} timeBase | |
*/ | |
const animationTimeDST = (timeBase) => ( | |
msDSTTransitionTime | |
? (timeBase % msDSTStepDuration) | |
: timeBase | |
); | |
const p = { | |
get seconds() { | |
const msSecondsHandTraversal = MS_1_MINUTE - msSyncPause; | |
if (msSyncPause) { | |
const pTransitionPath = msTransitionDuration / msSecondsHandTraversal; | |
if (msSecondsFrames < msTransitionDuration) { | |
const pAnimation = transitionSecondsHandStart( | |
msSecondsFrames / msTransitionDuration | |
); | |
return pTransitionPath * pAnimation; | |
} | |
if (msSecondsFrames > msSecondsHandTraversal) { | |
return 0; | |
} | |
if (msSecondsFrames > (msSecondsHandTraversal - msTransitionDuration)) { | |
const pAnimation = transitionSecondsHandStop( | |
( | |
msSecondsFrames - (msSecondsHandTraversal - msTransitionDuration) | |
) / msTransitionDuration | |
); | |
return ( | |
( | |
msSecondsHandTraversal - msTransitionDuration | |
) / msSecondsHandTraversal | |
) + (pTransitionPath * pAnimation); | |
} | |
} | |
switch (trackSecondsHand) { | |
case 'seconds': | |
return animate( | |
((s - 1) * MS_1_SECOND) / msSecondsHandTraversal, | |
msSeconds / msSecondsHandTraversal, | |
ms | |
); | |
case 'frames': | |
return msSecondsFrames / msSecondsHandTraversal; | |
} | |
}, | |
get minutes() { | |
switch (trackMinutesHand) { | |
case 'minutes': | |
return animate( | |
((m - 1) * MS_1_MINUTE) / MS_1_HOUR, | |
msMinutes / MS_1_HOUR, | |
animationTimeDST(msSecondsFrames) | |
); | |
case 'seconds': | |
return animate( | |
(msMinutes + ((s - 1) * MS_1_SECOND)) / MS_1_HOUR, | |
(msMinutes + msSeconds) / MS_1_HOUR, | |
animationTimeDST(ms) | |
); | |
case 'frames': | |
return (msMinutes + msSecondsFrames) / MS_1_HOUR; | |
} | |
}, | |
get hours() { | |
/** | |
* @param {number} pHours | |
*/ | |
const hoursHand = (pHours) => (pHours < 1 ? pHours : pHours - 1); | |
return hoursHand((() => { | |
switch (trackHoursHand) { | |
case 'hours': | |
return animate( | |
((h - 1) * MS_1_HOUR) / MS_12_HOURS, | |
msHours / MS_12_HOURS, | |
animationTimeDST(msMinutes + msSecondsFrames) | |
); | |
case 'minutes': | |
return animate( | |
(msHours + ((m - 1) * MS_1_MINUTE)) / MS_12_HOURS, | |
(msHours + msMinutes) / MS_12_HOURS, | |
animationTimeDST(msSecondsFrames) | |
); | |
case 'seconds': | |
return animate( | |
(msHours + msMinutes + ((s - 1) * MS_1_SECOND)) / MS_12_HOURS, | |
(msHours + msMinutes + msSeconds) / MS_12_HOURS, | |
animationTimeDST(ms) | |
); | |
case 'frames': | |
return (msHours + msMinutes + msSecondsFrames) / MS_12_HOURS; | |
} | |
})()); | |
} | |
}; | |
const degSeconds = wrap(p.seconds) * DEG_CIRCLE; | |
const degMinutes = wrap(p.minutes) * DEG_CIRCLE; | |
const degHours = wrap(p.hours) * DEG_CIRCLE; | |
elementSecondsHand.style.transform = `rotate3d(0,0,1,${degSeconds}deg)`; | |
elementMinutesHand.style.transform = `rotate3d(0,0,1,${degMinutes}deg)`; | |
elementHoursHand.style.transform = `rotate3d(0,0,1,${degHours}deg)`; | |
requestAnimationFrame(checkTime); | |
}; | |
checkTime(); | |
}); | |
</script> | |
</head> | |
<body> | |
<!-- https://commons.wikimedia.org/wiki/File:Swiss_railway_clock_1.svg --> | |
<svg class="clock" viewBox="-1136 -1136 2272 2272" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
<defs> | |
<path id="mark-large" d="M -40,-1000 l 80,0 0,245 -80,0 z" /> | |
<path id="mark-small" d="M -15,-1000 l 30,0 0,80 -30,0 z" /> | |
<path id="hand-hours" d="M -50,-650 l 100,0 10,890 -120,0 z" /> | |
<path id="hand-minutes" d="M -40,-950 l 80,0 10,1200 -100,0 z" /> | |
<path id="hand-seconds" d="m 0,-750 a 105,105 0 0 1 0,210 105,105 0 0 1 0,-210 z m -20,200 h 30 l 7,890 h -30 z" /> | |
<g id="face-5min"> | |
<use href="#mark-large" /> | |
<use href="#mark-small" transform="rotate(06)" /> | |
<use href="#mark-small" transform="rotate(12)" /> | |
<use href="#mark-small" transform="rotate(18)" /> | |
<use href="#mark-small" transform="rotate(24)" /> | |
</g> | |
<g id="face-15min"> | |
<use href="#face-5min" /> | |
<use href="#face-5min" transform="rotate(30)" /> | |
<use href="#face-5min" transform="rotate(60)" /> | |
</g> | |
<g id="face-full"> | |
<use href="#face-15min" /> | |
<use href="#face-15min" transform="rotate(90)" /> | |
<use href="#face-15min" transform="rotate(180)" /> | |
<use href="#face-15min" transform="rotate(270)" /> | |
</g> | |
<g id="label"> | |
<path | |
d="m 93.9,489 0,3.8 5,0 -5,-3.8 z m -6.7,-4.9 0,8.7 3.3,0 0,-6.2 -3.3,-2.5 z m -6.7,-4.9 0,13.7 3.3,0 0,-11.2 -3.3,-2.5 z m -6.6,-5 0,18.6 3.3,0 0,-16.1 -3.3,-2.5 z m -73.3,-3 0,21.6 c 1.2,0 2,0 3.3,-0.1 l 0,-21.7 c -1.1,0.1 -2.2,0.2 -3.3,0.2 m 6.6,-0.7 0,22 c 1.3,-0.1 1.9,-0.2 3.3,-0.4 l 0,-22.2 c -1,0.2 -2.1,0.5 -3.3,0.6 m 59.9,-1.4 0,23.6 3.4,0 0,-21.1 -3.4,-2.5 z m -53.2,-0.3 0,22.6 c 1,-0.2 2.2,-0.5 3.3,-0.8 l 0,-23.1 c -1.3,0.6 -2.1,0.9 -3.3,1.2 m 6.6,-2.8 0,23.6 c 1.1,-0.4 2.1,-0.7 3.3,-1.2 l 0,-24.4 c -1.3,0.8 -2.1,1.3 -3.3,1.9 m 39.9,-1.9 0,26.4 3.3,2.2 0,-26.1 -3.3,-2.5 z m -33.3,-2.3 0,25.2 c 1.1,-0.5 2.2,-1.1 3.3,-1.8 l 0,-26.1 c -1.3,1.2 -2.2,1.8 -3.3,2.7 m 26.6,-2.6 0,26.9 3.3,2.2 0,-26.6 -3.3,-2.5 z m -6.6,-4.9 0,27.4 3.3,2.2 0,-27.1 -3.3,-2.5 z m -10,-2 c -1.4,1.7 -1.9,2.1 -3.3,3.7 l 0,27.1 c 1.2,-1 2.2,-1.8 3.3,-2.8 l 0,-28 z m 3.3,-2.9 0,27.9 3.3,2.2 0,-27.7 -3.3,-2.5 z m -136.5,39.7 0,3.8 -5,0 5,-3.8 z m 6.7,-4.9 -0,8.7 -3.3,0 0,-6.2 3.4,-2.5 z m 6.6,-4.9 0,13.7 -3.3,0 0,-11.2 3.3,-2.5 z m 6.7,-5 -0,18.6 -3.3,0 0,-16.1 3.3,-2.5 z m 73.3,-3 0,21.6 c -1.2,0 -2,0 -3.3,-0.1 l 0,-21.7 c 1.1,0.1 2.2,0.2 3.3,0.2 m -6.6,-0.7 0,22 c -1.3,-0.1 -2,-0.2 -3.3,-0.4 l 0,-22.2 c 1,0.2 2.1,0.5 3.3,0.6 m -59.9,-1.4 -0,23.6 -3.4,0 -0,-21.1 3.4,-2.5 z m 53.2,-0.3 0,22.6 c -1,-0.2 -2.2,-0.5 -3.3,-0.8 l 0,-23.1 c 1.3,0.6 2.1,0.9 3.3,1.2 m -6.6,-2.8 0,23.6 c -1.1,-0.4 -2.1,-0.7 -3.3,-1.2 l 0,-24.4 c 1.4,0.8 2.1,1.3 3.3,1.9 m -39.9,-1.9 0,26.4 -3.3,2.2 0,-26.1 3.3,-2.5 z m 33.3,-2.3 0,25.2 c -1.1,-0.5 -2.2,-1.1 -3.3,-1.8 l 0,-26.1 c 1.3,1.2 2.1,1.8 3.3,2.7 m -26.6,-2.6 0,26.9 -3.3,2.2 0,-26.6 3.3,-2.5 z m 6.7,-4.9 -0,27.4 -3.3,2.2 0,-27.1 3.3,-2.5 z m 9.9,-2 c 1.4,1.7 1.9,2.1 3.3,3.7 l 0,27.1 c -1.2,-1 -2.2,-1.8 -3.3,-2.8 l 0,-28 z m -3.3,-2.9 0,27.9 -3.3,2.2 0,-27.7 3.3,-2.5 z m 38.6,-94.5 c 52,0 84.5,46.2 66.9,95.6 L 44.2,438 c 7.5,-28.5 -10.7,-61.1 -46.7,-60.8 -31.6,2 -50.9,28.6 -44.1,60.4 l -18.6,12 c -18.5,-46.6 13.5,-91.8 61.2,-94.8" | |
/> | |
<path | |
d="m -0.5,502.6 -5.7,0 -9,23.4 5.6,0 1.8,-3.6 9.1,0 1.8,3.5 5.4,0 -8.9,-23.4 z m -2.8,6.5 2.7,9.1 -5.3,0 2.6,-9.1 z m -36.4,-6.5 -8.4,0 0,23.4 10.2,0 c 2.6,0 5.9,-2.5 6.5,-5.7 0.1,-3.4 -1.4,-6.4 -3.9,-6.8 1.4,-0.6 2.4,-2.9 2.2,-5.9 -0.4,-3 -3.7,-5.2 -6.6,-5 m 1.6,5.7 c 0.6,1 0.2,2 -0.1,2.8 -1.1,1.2 -3,1.1 -4.5,1.1 l 0,-5.1 c 1.4,0 3.7,-0.2 4.6,1.2 m 1,8.8 c 0.6,0.9 0.5,2.7 0.2,3.3 -1.4,1.5 -3.5,1.5 -5.7,1.2 l 0,-5.5 c 2.6,-0.2 4,-0.1 5.5,1 m -71,-14.5 3.9,23.3 -4.9,0.1 -2.3,-13.6 -6.1,13.6 -1.5,-0.1 -6,-13 -2.5,13.1 -5.2,-0.1 3.8,-23.4 5.1,0 5.1,13.4 5.5,-13.3 4.9,0 z m 31.2,17.9 c -3.7,0 -6.8,-3.1 -6.8,-6.8 0,-3.7 3.1,-6.8 6.8,-6.8 3.7,0 6.8,3.1 6.8,6.8 0,3.7 -3.1,6.8 -6.8,6.8 m 0,5.5 c 6.9,0 12.6,-5.5 12.6,-12.3 0,-6.8 -5.7,-12.3 -12.6,-12.3 -6.9,0 -12.6,5.5 -12.6,12.3 0,6.8 5.7,12.3 12.6,12.3 m 104.3,-22.4 13.9,0 -0.5,2.6 -5.7,0 -3.1,19.7 -2.8,0 3.1,-19.7 -5.5,0 0.5,-2.6 z m 30.2,0 2.8,0 -3.6,22.3 -2.8,0 3.5,-22.3 z m 39.7,-1.6 1.2,23.9 -3.2,0 -0.2,-15.2 -9.6,15.7 -4.4,-15.7 -5.1,15.2 -3.2,0 8.8,-23.9 4.9,18.1 10.8,-18.1 z m 29.6,1.6 -0.7,2.6 -9.2,0 -1,6.3 9,0 -0.5,2.5 -8.9,0 -1.4,8.4 9.4,0 -0.5,2.6 -12.2,0 3.5,-22.3 12.5,0 z" | |
/> | |
</g> | |
<circle id="base" r="1104" /> | |
<circle id="center" r="5" /> | |
</defs> | |
<use href="#base" /> | |
<use href="#label" /> | |
<use href="#face-full" /> | |
</svg> | |
<svg class="clock" id="handle-hours" viewBox="-1136 -1136 2272 2272" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
<use href="#hand-hours" /> | |
</svg> | |
<svg class="clock" id="handle-minutes" viewBox="-1136 -1136 2272 2272" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
<use href="#hand-minutes" /> | |
</svg> | |
<svg class="clock" id="handle-seconds" viewBox="-1136 -1136 2272 2272" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
<use href="#hand-seconds" /> | |
</svg> | |
<svg class="clock" viewBox="-1136 -1136 2272 2272" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | |
<use href="#center" /> | |
</svg> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment