<main>
<digital-clock class="digital-clock"></digital-clock>
<digital-clock label="New York" lang="en-US" date="short" timezone="-4" time="12hour" class="newyork"></digital-clock>
<digital-clock label="Tokyo" lang="ja-JP" date="narrow" timezone="+9" class="tokyo"></digital-clock>
<digital-clock label="Sydney" lang="en-AU" date="short" timezone="+10" time="12hour short" class="sydney"></digital-clock>
<digital-clock label="東京" lang="ja-JP" date="short" number-system="hiragana" timezone="+9" class="hiragana"></digital-clock>
<digital-clock label="الرياض" lang="ar-SA" date="full" number-system="arabic-indic" timezone="+3" class="riyadh"></digital-clock>
<digital-clock label="北京" lang="zh-CN" date="full" number-system="cjk-decimal" timezone="+8" class="beijing"></digital-clock>
<digital-clock label="กรุงเทพฯ" lang="th" date="short" number-system="thai" timezone="+7" class="bangkok"></digital-clock>
<digital-clock label="ROMA" number-system="upper-roman" timezone="+2" time="short" class="roma"></digital-clock>
</main>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Serif+SC&family=Victor+Mono:ital,wght@0,700;1,700&display=swap');
body {
background: hsl(0, 10%, 10%);
padding: 1em;
}
main {
display: flex;
flex-wrap: wrap;
gap: 2ch;
justify-content: center;
margin-inline: auto;
max-inline-size: 1000px;
}
digital-clock {
border-radius: .25em;
display: grid;
font-family: monospace;
font-size: 1rem;
grid-template-columns: 1fr 1fr;
padding: 1em 2em;
&::part(date) { place-self: start end; }
&::part(time) {
grid-column: span 2;
font-size: 250%;
place-self: center;
}
}
.bangkok {
background: linear-gradient(135deg, #273c75, #e84118, #44bd32);
border: 2px solid #fbc531;
border-radius: 6px;
box-shadow: 0 0 15px rgba(253, 203, 110, 0.6);
color: #f5f6fa;
&::part(time) {
font-size: 350%;
text-shadow: 0 0 8px #fbc531, 0 0 12px rgba(255, 255, 255, 0.2);
letter-spacing: 2px;
}
&::part(date),
&::part(label) {
color: #ffeaa7;
font-weight: 700;
}
}
.beijing {
background: linear-gradient(to right, #8a0000, #b80000);
border-radius: 6px;
color: #ffcc00;
font-family: "Noto Sans SC", "Noto Sans", sans-serif;
&::part(time) {
text-shadow: 0 0 6px rgba(255, 204, 0, 0.3);
font-weight: 500;
}
&::part(date),
&::part(label) {
color: #ffe680;
}
}
.digital-clock {
background: #0f1824;
border: 2ch solid #29313d;
color: #CACACA;
font-family: "Victor Mono", monospace;
}
.hiragana {
background: #080808;
color: #ff1a1a;
font-family: ui-sans-serif, system-ui;
width: 300px;
&::part(time) {
text-shadow: 0 0 5px rgba(255, 26, 26, 0.7);
}
}
.newyork {
background-color: #242625;
color: #3085E6;
font-family: Bahnschrift, 'DIN Alternate', 'Franklin Gothic Medium', 'Nimbus Sans Narrow', sans-serif-condensed, sans-serif;
&::part(time) {
font-size: 350%;
margin-block: .25ch;
}
&::part(ampm) { font-size: 50%; place-self: end; }
&::part(ampm), &::part(hours), &::part(minutes), &::part(seconds) {
text-box: cap alphabetic;
}
}
.riyadh {
background: #1b5e20;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
color: #f8f8f8;
font-family: "Noto Sans Arabic", "Noto Sans", sans-serif;
&::part(time) {
font-size: 350%;
letter-spacing: 1px;
}
&::part(hours), &::part(minutes), &::part(seconds) {
text-box: cap alphabetic;
}
&::part(date),
&::part(label) {
color: #e6d2b5;
}
padding: 1.5em;
position: relative;
&::before {
content: '';
position: absolute;
inset: 6px;
border: 1px solid rgba(230, 210, 181, 0.3);
border-radius: 4px;
}
}
.roma {
background: antiquewhite;
font-family: 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif;
&::part(label) {
font-size: 150%;
grid-column: span 2;
letter-spacing: 1ch;
place-self: center;
}
}
.sydney {
background: #fefefe;
border: 1px solid #DDd;
font-family: Optima, Candara, 'Noto Sans', source-sans-pro, sans-serif;
}
.tokyo {
background: #DDD;
color: #272E38;
grid-template-columns: 1fr 1fr;
padding: 1em 2em;
}
CSS.registerProperty({
name: '--seconds',
syntax: '<integer>',
initialValue: '0',
inherits: false
});
CSS.registerProperty({
name: '--minutes',
syntax: '<integer>',
initialValue: '0',
inherits: false
});
CSS.registerProperty({
name: '--hours',
syntax: '<integer>',
initialValue: '0',
inherits: false
});
const styles = new CSSStyleSheet();
styles.replaceSync(`
:host {
background-color: var(--digital-clock-bg, var(--CanvasGray));
border-radius: var(--digital-clock-bdrs, var(--input-bdrs));
box-sizing: border-box;
color-scheme: light dark;
display: flex;
direction: ltr;
gap: var(--digital-clock-gap, 1ch);
inline-size: min-content;
padding: var(--digital-clock-p, .75ch 1.5ch);
}
:host::part(ampm)::after {
content: counter(hours, am-pm);
}
:host::part(date) {
font-family: var(--digital-clock-date-ff, inherit);
font-size: var(--digital-clock-date-fs, inherit);
font-weight: var(--digital-clock-date-fw, inherit);
text-wrap: nowrap;
}
:host::part(label) {
font-family: var(--digital-clock-label-ff, inherit);
font-size: var(--digital-clock-label-fs, inherit);
font-weight: var(--digital-clock-label-fw, inherit);
text-wrap: nowrap;
}
:host::part(time) {
all: unset;
display: grid;
font-size: var(--digital-clock-fs, inherit);
font-variant-numeric: tabular-nums;
font-weight: var(--digital-clock-fw, inherit);
grid-auto-flow: column;
inline-size: min-content;
list-style: none;
text-wrap: nowrap;
}
:host::part(ampm),
:host::part(hours) {
animation: hours 86400s steps(24, end) infinite;
animation-delay: var(--delay-hours, 0s);
counter-reset: hours var(--hours);
}
:host::part(hours)::after {
content: counter(hours, var(--number-system, decimal-leading-zero)) ' ';
}
:host::part(minutes){
animation: minutes 3600s steps(60, end) infinite;
animation-delay: var(--delay-minutes, 0s);
counter-reset: minutes var(--minutes);
}
:host::part(minutes)::before {
content: ':';
}
:host::part(minutes)::after {
content: counter(minutes, var(--number-system, decimal-leading-zero)) ' ';
}
:host::part(seconds) {
animation: seconds 60s steps(60, end) infinite;
animation-delay: var(--delay-seconds, 0s);
counter-reset: seconds var(--seconds);
}
:host::part(seconds)::before {
content: ':';
}
:host::part(seconds)::after {
content: counter(seconds, var(--number-system, decimal-leading-zero)) ' ';
}
:host([time*="12hour"])::part(hours) {
counter-reset: hours calc(mod(var(--hours) - 1, 12) + 1);
}
@keyframes hours {
from { --hours: 0; }
to { --hours: 24; }
}
@keyframes minutes {
from { --minutes: 0; }
to { --minutes: 60; }
}
@keyframes seconds {
from { --seconds: 0;}
to { --seconds: 60; }
}
`);
let counterStyleInjected = false;
class DigitalClock extends HTMLElement {
#root;
#date;
#label;
constructor() {
super();
this.#root = this.attachShadow({ mode: 'open' });
this.#root.adoptedStyleSheets = [styles];
const hasDate = this.hasAttribute('date');
const hasLabel = this.hasAttribute('label');
const time = this.getAttribute('time');
const is12Hour = time?.includes('12hour');
this.#root.innerHTML = `
${hasLabel ? `<span part="label"></span>`:''}
${hasDate ? `<span part="date"></span>`:''}
<ol part="time">
<li part="hours"></li>
<li part="minutes"></li>
${time?.includes('short') ? '':`<li part="seconds"></li>`}
${is12Hour ? `<li part="ampm"></li>` : ''}
</ol>
`;
if (hasDate) {
this.#date = this.#root.querySelector('[part=date]');
}
if (hasLabel) {
this.#label = this.#root.querySelector('[part=label]');
this.#label.textContent = this.getAttribute('label') || '';
}
this.#updateClock(hasDate);
if (!counterStyleInjected) {
/* Safari Hack: Safari has issues with counter-style in shadow DOM */
this.#injectCounterStyle();
counterStyleInjected = true;
}
}
#formatDate(date, format) {
const lang = this.getAttribute('lang') || document.documentElement.lang || navigator.language;
let options;
switch (format) {
case 'narrow':
options = {
day: 'numeric',
month: 'numeric',
year: '2-digit'
};
break;
case 'short':
options = {
day: 'numeric',
month: 'short',
year: '2-digit'
};
break;
case 'full':
default:
options = {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
};
}
return new Intl.DateTimeFormat(lang, options).format(date);
}
#injectCounterStyle() {
const style = document.createElement('style');
style.textContent = `
@counter-style am-pm {
system: cyclic;
symbols: "am" "am" "am" "am" "am" "am" "am" "am" "am" "am" "am" "pm" "pm" "pm" "pm" "pm" "pm" "pm" "pm" "pm" "pm" "pm" "pm" "am";
}
`;
document.head.appendChild(style);
}
#roundTzOffset(offset) {
return Math.round((parseFloat(offset) || 0) * 4) / 4
};
#updateClock(hasDate) {
const time = new Date();
const tzOffset = this.#roundTzOffset(this.getAttribute('timezone') || '0');
const utc = time.getTime() + (time.getTimezoneOffset() * 60000);
const tzTime = new Date(utc + (3600000 * tzOffset));
const hours = tzTime.getHours() * 3600;
const minutes = tzTime.getMinutes() * 60;
const seconds = tzTime.getSeconds();
this.style.setProperty('--delay-hours', `-${hours + minutes + seconds}s`);
this.style.setProperty('--delay-minutes', `-${minutes + seconds}s`);
this.style.setProperty('--delay-seconds', `-${seconds}s`);
this.style.setProperty('--number-system', this.getAttribute('number-system') || 'decimal-leading-zero');
if (hasDate) this.#updateDate(tzTime);
}
#updateDate(tzTime) {
const dateAttr = this.getAttribute('date');
if (dateAttr) {
this.#date.textContent = this.#formatDate(tzTime, dateAttr);
this.#date.hidden = false;
} else {
this.#date.hidden = true;
}
}
}
customElements.define('digital-clock', DigitalClock);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.