09Clock widget whose sky and greeting follow the real hour
Each minute tick quietly advances the clock while that hour's sky and greeting take over — and the screen reader is spared a shout every second.
Best forClock and greeting atop dashboards, kiosk wait screens
setIntervalaria-livetabular-nums

<main class="stage stage--ink">
<section class="lc is-demo" id="lc" aria-label="Live clock with day theme">
<div class="lc__skywrap" aria-hidden="true">
<span class="lc__band lc__band--morning"></span>
<span class="lc__band lc__band--noon"></span>
<span class="lc__band lc__band--evening"></span>
<span class="lc__band lc__band--night"></span>
<span class="lc__orb lc__orb--sun"><svg viewBox="0 0 256 256" width="22" height="22" fill="currentColor"><path d="M184,128a56,56,0,1,1-56-56A56,56,0,0,1,184,128Z" opacity="0.2"/><path d="M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z"/></svg></span>
<span class="lc__orb lc__orb--moon"><svg viewBox="0 0 256 256" width="20" height="20" fill="currentColor"><path d="M210.69,158.18A88,88,0,1,1,97.82,45.31,96.08,96.08,0,0,0,192,160,96.78,96.78,0,0,0,210.69,158.18Z" opacity="0.2"/><path d="M240,96a8,8,0,0,1-8,8H216v16a8,8,0,0,1-16,0V104H184a8,8,0,0,1,0-16h16V72a8,8,0,0,1,16,0V88h16A8,8,0,0,1,240,96ZM144,56h8v8a8,8,0,0,0,16,0V56h8a8,8,0,0,0,0-16h-8V32a8,8,0,0,0-16,0v8h-8a8,8,0,0,0,0,16Zm72.77,97a8,8,0,0,1,1.43,8A96,96,0,1,1,95.07,37.8a8,8,0,0,1,10.6,9.06A88.07,88.07,0,0,0,209.14,150.33,8,8,0,0,1,216.77,153Zm-19.39,14.88c-1.79.09-3.59.14-5.38.14A104.11,104.11,0,0,1,88,64c0-1.79,0-3.59.14-5.38A80,80,0,1,0,197.38,167.86Z"/></svg></span>
</div>
<div class="lc__body">
<p class="lc__clock" role="timer" aria-live="off" aria-label="Current time"><span class="lc__swap"><span class="lc__v" id="lc-now">14:32</span><span class="lc__alt" aria-hidden="true">14:33</span></span></p>
<p class="lc__date" id="lc-date">Sat · 9/12</p>
<p class="lc__hi"><span class="lc__swap"><span class="lc__v" id="lc-hi-v">Good afternoon</span><span class="lc__alt" aria-hidden="true">Good evening</span></span></p>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 실시간 시계 — 분이 바뀌는 순간만 글자가 바뀌고, 시간대가 하늘 색과 인사말을 이어 받는다.
// 하늘·인사말 교체는 steps(1, end) 컷, 시계 글자도 컷 — 반투명 유령 프레임을 만들지 않는다.
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
@include stage($stage-ink);
.lc {
box-sizing: border-box;
display: grid;
width: min(240px, 92vw);
overflow: hidden;
border-radius: $r-card;
background: $subject-cream;
color: $subject-ink;
box-shadow: $shadow-raised;
}
.lc * { box-sizing: border-box; }
.lc__skywrap {
position: relative;
height: 64px;
}
.lc__band {
position: absolute;
inset: 0;
opacity: 0;
}
.lc__band--morning { background: linear-gradient(180deg, #ffd9a3 0%, #ffe9c8 100%); }
.lc__band--noon { background: linear-gradient(180deg, #bfe3ff 0%, #dff0ff 100%); }
.lc__band--evening { background: linear-gradient(180deg, #ddc9b9 0%, #ffd9b8 100%); }
.lc__band--night { background: linear-gradient(180deg, #3d4460 0%, #6c7794 100%); }
.lc.is-morning .lc__band--morning { opacity: 1; }
.lc.is-noon .lc__band--noon { opacity: 1; }
.lc.is-evening .lc__band--evening { opacity: 1; }
.lc.is-night .lc__band--night { opacity: 1; }
// 해와 달 — 시간대가 바뀌면 맞바뀐다. 해는 광선이 늘 돌고 달은 살짝 떠 있는다(2초·1초 = 2s 약수)
.lc__orb {
position: absolute;
top: $sp-2;
display: grid;
place-items: center;
}
.lc__orb svg { display: block; }
.lc__orb--sun { right: $sp-3; color: #ba9f72; }
.lc__orb--sun svg {
animation-name: lc-spin;
animation-duration: $duration;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes lc-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.lc__orb--moon { right: $sp-3; color: #f4f0e4; opacity: 0; }
.lc__orb--moon svg {
animation-name: lc-bob;
animation-duration: 1s;
animation-timing-function: $ease-out;
animation-iteration-count: infinite;
}
@keyframes lc-bob {
0%, 100% { transform: translateY(-2px); }
50% { transform: translateY(2px); }
}
.lc.is-night .lc__orb--sun { opacity: 0; }
.lc.is-night .lc__orb--moon { opacity: 1; }
.lc.is-evening .lc__orb--sun { opacity: 0; }
.lc.is-evening .lc__orb--moon { opacity: 1; }
.lc__body {
display: grid;
gap: $sp-1;
padding: $sp-2 $sp-3 $sp-3;
}
.lc__clock {
margin: 0;
font-size: 36px;
font-weight: 800;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.lc__date {
margin: 0;
font-size: 11px;
font-weight: 500;
line-height: 1.3;
color: rgba($subject-ink, .72);
}
.lc__hi {
margin: 0;
font-size: 13px;
font-weight: 700;
line-height: 1.3;
}
// 겹쳐 둔 두 번째 값 — 미리보기 루프에서만 보인다
.lc__swap { position: relative; display: inline-block; }
.lc__alt {
position: absolute;
inset: 0;
opacity: 0;
}
// ── 격자 미리보기 루프 (2초) ──
// 낮(0~33%) → 저녁(34~66%) → 밤(67~87%) → 낮 복귀(88%, 90% 안). 분 글자는 45% 에 한 번 컷.
@keyframes lc-noon { 0%, 33% { opacity: 1; } 34%, 87% { opacity: 0; } 88%, 100% { opacity: 1; } }
@keyframes lc-evening { 0%, 33% { opacity: 0; } 34%, 66% { opacity: 1; } 67%, 100% { opacity: 0; } }
@keyframes lc-night { 0%, 66% { opacity: 0; } 67%, 87% { opacity: 1; } 88%, 100% { opacity: 0; } }
@keyframes lc-va { 0%, 44% { opacity: 1; } 45%, 87% { opacity: 0; } 88%, 100% { opacity: 1; } }
@keyframes lc-vb { 0%, 44% { opacity: 0; } 45%, 87% { opacity: 1; } 88%, 100% { opacity: 0; } }
@keyframes lc-suna { 0%, 33% { opacity: 1; } 34%, 100% { opacity: 0; } }
@keyframes lc-moonb { 0%, 33% { opacity: 0; } 34%, 66% { opacity: 0; } 67%, 87% { opacity: 1; } 88%, 100% { opacity: 0; } }
.lc.is-demo {
.lc__band--noon { animation-name: lc-noon; animation-duration: $duration; animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-fill-mode: backwards; }
.lc__band--evening { animation-name: lc-evening; animation-duration: $duration; animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-fill-mode: backwards; }
.lc__band--night { animation-name: lc-night; animation-duration: $duration; animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-fill-mode: backwards; }
.lc__band--morning { opacity: 0; }
.lc__orb--sun { animation-name: lc-suna; animation-duration: $duration; animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-fill-mode: backwards; }
.lc__orb--moon { animation-name: lc-moonb; animation-duration: $duration; animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-fill-mode: backwards; }
.lc__swap .lc__v { animation-name: lc-va; }
.lc__swap .lc__alt { animation-name: lc-vb; }
.lc__swap .lc__v, .lc__swap .lc__alt {
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
}
@media (max-width: 340px) {
.lc__body { padding: $sp-2 $sp-2 $sp-2; }
.lc__clock { font-size: 30px; }
}
@media (prefers-reduced-motion: reduce) {
.lc.is-demo .lc__band,
.lc.is-demo .lc__orb,
.lc.is-demo .lc__swap .lc__v,
.lc.is-demo .lc__swap .lc__alt {
animation: none !important;
}
.lc__orb--sun svg, .lc__orb--moon svg { animation: none !important; transform: none !important; }
// 정지 상태: 실제 조작(시간 경과)이 정해 둔 시간대가 보인다
.lc.is-demo .lc__swap .lc__v { opacity: 1 !important; }
.lc.is-demo .lc__swap .lc__alt { opacity: 0 !important; }
// 해·달은 시간대 반 클래스를 따른다 — 밤에 해를 붙박아 두면 정지 화면이 거짓말을 한다
.lc.is-demo:not(.is-evening):not(.is-night) .lc__orb--sun { opacity: 1 !important; }
.lc.is-demo:not(.is-evening):not(.is-night) .lc__orb--moon { opacity: 0 !important; }
.lc.is-demo.is-evening .lc__orb--sun,
.lc.is-demo.is-night .lc__orb--sun { opacity: 0 !important; }
.lc.is-demo.is-evening .lc__orb--moon,
.lc.is-demo.is-night .lc__orb--moon { opacity: 1 !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.lc {
box-sizing: border-box;
display: grid;
width: min(240px, 92vw);
overflow: hidden;
border-radius: 16px;
background: #fff7e6;
color: #17141a;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.lc * {
box-sizing: border-box;
}
.lc__skywrap {
position: relative;
height: 64px;
}
.lc__band {
position: absolute;
inset: 0;
opacity: 0;
}
.lc__band--morning {
background: linear-gradient(180deg, #ffd9a3 0%, #ffe9c8 100%);
}
.lc__band--noon {
background: linear-gradient(180deg, #bfe3ff 0%, #dff0ff 100%);
}
.lc__band--evening {
background: linear-gradient(180deg, #ddc9b9 0%, #ffd9b8 100%);
}
.lc__band--night {
background: linear-gradient(180deg, #3d4460 0%, #6c7794 100%);
}
.lc.is-morning .lc__band--morning {
opacity: 1;
}
.lc.is-noon .lc__band--noon {
opacity: 1;
}
.lc.is-evening .lc__band--evening {
opacity: 1;
}
.lc.is-night .lc__band--night {
opacity: 1;
}
.lc__orb {
position: absolute;
top: 8px;
display: grid;
place-items: center;
}
.lc__orb svg {
display: block;
}
.lc__orb--sun {
right: 12px;
color: #ba9f72;
}
.lc__orb--sun svg {
animation-name: lc-spin;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes lc-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.lc__orb--moon {
right: 12px;
color: #f4f0e4;
opacity: 0;
}
.lc__orb--moon svg {
animation-name: lc-bob;
animation-duration: 1s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
}
@keyframes lc-bob {
0%, 100% {
transform: translateY(-2px);
}
50% {
transform: translateY(2px);
}
}
.lc.is-night .lc__orb--sun {
opacity: 0;
}
.lc.is-night .lc__orb--moon {
opacity: 1;
}
.lc.is-evening .lc__orb--sun {
opacity: 0;
}
.lc.is-evening .lc__orb--moon {
opacity: 1;
}
.lc__body {
display: grid;
gap: 4px;
padding: 8px 12px 12px;
}
.lc__clock {
margin: 0;
font-size: 36px;
font-weight: 800;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.lc__date {
margin: 0;
font-size: 11px;
font-weight: 500;
line-height: 1.3;
color: rgba(23, 20, 26, 0.72);
}
.lc__hi {
margin: 0;
font-size: 13px;
font-weight: 700;
line-height: 1.3;
}
.lc__swap {
position: relative;
display: inline-block;
}
.lc__alt {
position: absolute;
inset: 0;
opacity: 0;
}
@keyframes lc-noon {
0%, 33% {
opacity: 1;
}
34%, 87% {
opacity: 0;
}
88%, 100% {
opacity: 1;
}
}
@keyframes lc-evening {
0%, 33% {
opacity: 0;
}
34%, 66% {
opacity: 1;
}
67%, 100% {
opacity: 0;
}
}
@keyframes lc-night {
0%, 66% {
opacity: 0;
}
67%, 87% {
opacity: 1;
}
88%, 100% {
opacity: 0;
}
}
@keyframes lc-va {
0%, 44% {
opacity: 1;
}
45%, 87% {
opacity: 0;
}
88%, 100% {
opacity: 1;
}
}
@keyframes lc-vb {
0%, 44% {
opacity: 0;
}
45%, 87% {
opacity: 1;
}
88%, 100% {
opacity: 0;
}
}
@keyframes lc-suna {
0%, 33% {
opacity: 1;
}
34%, 100% {
opacity: 0;
}
}
@keyframes lc-moonb {
0%, 33% {
opacity: 0;
}
34%, 66% {
opacity: 0;
}
67%, 87% {
opacity: 1;
}
88%, 100% {
opacity: 0;
}
}
.lc.is-demo .lc__band--noon {
animation-name: lc-noon;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.lc.is-demo .lc__band--evening {
animation-name: lc-evening;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.lc.is-demo .lc__band--night {
animation-name: lc-night;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.lc.is-demo .lc__band--morning {
opacity: 0;
}
.lc.is-demo .lc__orb--sun {
animation-name: lc-suna;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.lc.is-demo .lc__orb--moon {
animation-name: lc-moonb;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.lc.is-demo .lc__swap .lc__v {
animation-name: lc-va;
}
.lc.is-demo .lc__swap .lc__alt {
animation-name: lc-vb;
}
.lc.is-demo .lc__swap .lc__v, .lc.is-demo .lc__swap .lc__alt {
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
@media (max-width: 340px) {
.lc__body {
padding: 8px 8px 8px;
}
.lc__clock {
font-size: 30px;
}
}
@media (prefers-reduced-motion: reduce) {
.lc.is-demo .lc__band,
.lc.is-demo .lc__orb,
.lc.is-demo .lc__swap .lc__v,
.lc.is-demo .lc__swap .lc__alt {
animation: none !important;
}
.lc__orb--sun svg, .lc__orb--moon svg {
animation: none !important;
transform: none !important;
}
.lc.is-demo .lc__swap .lc__v {
opacity: 1 !important;
}
.lc.is-demo .lc__swap .lc__alt {
opacity: 0 !important;
}
.lc.is-demo:not(.is-evening):not(.is-night) .lc__orb--sun {
opacity: 1 !important;
}
.lc.is-demo:not(.is-evening):not(.is-night) .lc__orb--moon {
opacity: 0 !important;
}
.lc.is-demo.is-evening .lc__orb--sun,
.lc.is-demo.is-night .lc__orb--sun {
opacity: 0 !important;
}
.lc.is-demo.is-evening .lc__orb--moon,
.lc.is-demo.is-night .lc__orb--moon {
opacity: 1 !important;
}
}// 실제 시계 — 매초 돌되 분이 바뀔 때만 글자를 고친다.
// 낭독기가 매초 소리치지 않게 role="timer" + aria-live="off" 로 묵음한다.
// 인사말·요일은 전체 문장 배열에서 뽑는다 — 언어팩 사본에서는 배열 안 글자가 통째로 바뀐다.
var lcRoot = document.getElementById('lc');
var lcNow = document.getElementById('lc-now');
var lcDate = document.getElementById('lc-date');
var lcHi = document.getElementById('lc-hi-v');
var lcWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
var lcHiWords = ['Good morning', 'Good afternoon', 'Good evening', 'Good night'];
var lcLastMinute = -1;
function lcTheme(h) {
if (h >= 5 && h < 12) return 'is-morning';
if (h < 18) return 'is-noon';
if (h < 22) return 'is-evening';
return 'is-night';
}
function lcGreeting(h) {
if (h >= 5 && h < 12) return lcHiWords[0];
if (h < 18) return lcHiWords[1];
if (h < 22) return lcHiWords[2];
return lcHiWords[3];
}
function lcPad(n) { return n < 10 ? '0' + n : String(n); }
function lcTick() {
var d = new Date();
if (d.getMinutes() !== lcLastMinute) {
lcLastMinute = d.getMinutes();
lcNow.textContent = lcPad(d.getHours()) + ':' + lcPad(d.getMinutes());
lcDate.textContent = lcWeek[d.getDay()] + ' · ' + (d.getMonth() + 1) + '/' + d.getDate();
lcHi.textContent = lcGreeting(d.getHours());
lcRoot.classList.remove('is-morning', 'is-noon', 'is-evening', 'is-night');
lcRoot.classList.add(lcTheme(d.getHours()));
// 격자 루프에 겹쳐 둔 다음 분 글자도 진짜 시각에서 만든다 — 맞지 않는 시간이 화면에 남지 않게
var nx = new Date(d.getTime() + 60000);
var alt = lcRoot.querySelector('.lc__swap .lc__alt');
if (alt) { alt.textContent = lcPad(nx.getHours()) + ':' + lcPad(nx.getMinutes()); }
}
}
setInterval(lcTick, 1000);
lcTick();import { useEffect, useState } from "react";
import type { CSSProperties } from "react";
import styles from "./WeatherLiveClockTheme.module.scss";
// vanilla 판(run/281/demo/09-weather-live-clock-theme/index.html) 의 React 포트.
// 실제 시계 — 매초 돌되 분이 바뀔 때만 글자를 고친다.
// 낭독기가 매초 소리치지 않게 role="timer" + aria-live="off" 로 묵음한다.
export type WeatherLiveClockThemeProps = {
groupLabel?: string;
clockLabel?: string;
/** 요일 낱말 — 일요일부터. 언어팩이 배열째로 바꾼다 */
weekWords?: string[];
/** 인사말 — 아침·오후·저녁·밤 순 */
greetings?: string[];
/** 날짜 표기 사이 구분 글자 */
dateSep?: string;
/** 격자 미리보기 2초 자동 루프 */
demo?: boolean;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const DEFAULT_WEEK: string[] = ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"];
const DEFAULT_GREETINGS: string[] = ["좋은 아침이에요", "좋은 오후예요", "좋은 저녁이에요", "좋은 밤이에요"];
const THEME_CLASS: { [k: string]: string } = {
morning: styles.isMorning,
noon: styles.isNoon,
evening: styles.isEvening,
night: styles.isNight,
};
function pad(n: number): string {
return n < 10 ? `0${n}` : String(n);
}
function themeOf(h: number): string {
if (h >= 5 && h < 12) return "morning";
if (h < 18) return "noon";
if (h < 22) return "evening";
return "night";
}
function greetingIndex(h: number): number {
if (h >= 5 && h < 12) return 0;
if (h < 18) return 1;
if (h < 22) return 2;
return 3;
}
type ClockState = {
now: string;
/** 겹쳐 둔 다음 분 글자 — 격자 루프에서만 보인다 */
next: string;
date: string;
greeting: string;
theme: string;
};
function readClock(weekWords: string[], greetings: string[], sep: string): ClockState {
const d = new Date();
const nx = new Date(d.getTime() + 60000);
return {
now: `${pad(d.getHours())}:${pad(d.getMinutes())}`,
next: `${pad(nx.getHours())}:${pad(nx.getMinutes())}`,
date: `${weekWords[d.getDay()]} ${sep} ${d.getMonth() + 1}/${d.getDate()}`,
greeting: greetings[greetingIndex(d.getHours())],
theme: themeOf(d.getHours()),
};
}
function SunOrb() {
return (
<svg viewBox="0 0 256 256" width="22" height="22" fill="currentColor"><path d="M184,128a56,56,0,1,1-56-56A56,56,0,0,1,184,128Z" opacity="0.2"/><path d="M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z"/></svg>
);
}
function MoonOrb() {
return (
<svg viewBox="0 0 256 256" width="20" height="20" fill="currentColor"><path d="M210.69,158.18A88,88,0,1,1,97.82,45.31,96.08,96.08,0,0,0,192,160,96.78,96.78,0,0,0,210.69,158.18Z" opacity="0.2"/><path d="M240,96a8,8,0,0,1-8,8H216v16a8,8,0,0,1-16,0V104H184a8,8,0,0,1,0-16h16V72a8,8,0,0,1,16,0V88h16A8,8,0,0,1,240,96ZM144,56h8v8a8,8,0,0,0,16,0V56h8a8,8,0,0,0,0-16h-8V32a8,8,0,0,0-16,0v8h-8a8,8,0,0,0,0,16Zm72.77,97a8,8,0,0,1,1.43,8A96,96,0,1,1,95.07,37.8a8,8,0,0,1,10.6,9.06A88.07,88.07,0,0,0,209.14,150.33,8,8,0,0,1,216.77,153Zm-19.39,14.88c-1.79.09-3.59.14-5.38.14A104.11,104.11,0,0,1,88,64c0-1.79,0-3.59.14-5.38A80,80,0,1,0,197.38,167.86Z"/></svg>
);
}
export default function WeatherLiveClockTheme({
groupLabel = "실시간 시계와 하루 테마",
clockLabel = "현재 시각",
weekWords = DEFAULT_WEEK,
greetings = DEFAULT_GREETINGS,
dateSep = "·",
demo = true,
duration = 2000,
easing = "ease-out",
color = "#2f6df6",
}: WeatherLiveClockThemeProps) {
const [clock, setClock] = useState<ClockState>(() => readClock(weekWords, greetings, dateSep));
// 매초 돌되 분이 바뀔 때만 글자를 고친다
useEffect(() => {
const id = setInterval(() => {
setClock((prev: ClockState) => {
const next = readClock(weekWords, greetings, dateSep);
return next.now === prev.now ? prev : next;
});
}, 1000);
return () => clearInterval(id);
}, []);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const cls = [styles.lc, demo ? styles.isDemo : "", THEME_CLASS[clock.theme] || ""].filter(Boolean).join(" ");
return (
<main className={styles.stage}>
<section className={cls} style={vars} aria-label={groupLabel}>
<div className={styles.skywrap} aria-hidden="true">
<span className={`${styles.band} ${styles["band--morning"]}`} />
<span className={`${styles.band} ${styles["band--noon"]}`} />
<span className={`${styles.band} ${styles["band--evening"]}`} />
<span className={`${styles.band} ${styles["band--night"]}`} />
<span className={`${styles.orb} ${styles["orb--sun"]}`}><SunOrb /></span>
<span className={`${styles.orb} ${styles["orb--moon"]}`}><MoonOrb /></span>
</div>
<div className={styles.body}>
<p className={styles.clock} role="timer" aria-live="off" aria-label={clockLabel}>
<span className={styles.swap}>
<span className={styles.v}>{clock.now}</span>
<span className={styles.alt} aria-hidden="true">{clock.next}</span>
</span>
</p>
<p className={styles.date}>{clock.date}</p>
<p className={styles.hi}>
<span className={styles.swap}>
<span className={styles.v}>{clock.greeting}</span>
<span className={styles.alt} aria-hidden="true">{greetings[2]}</span>
</span>
</p>
</div>
</section>
</main>
);
}09 Clock widget whose sky and greeting follow the real hour — Measured area 11.155% · moving frames 16/23Original by GODRICH · SCSS and React ship in the zip · Weather Widget Design — 9 Widgets, No Library
The post this demo lives in
Weather Widget Design — 9 Widgets, No LibraryWeather widget design is the sky card on a dashboard — temperature, hour strip, week bars. These nine are plain HTML, SC See all demos in this post
If you like this effect - similar demos
Embeds08 The moment an empty feed meets its first activity9 Activity Feed UI Widgets — Sidebar-Ready Lists
Embeds02 Command run and output linesTerminal UI Design: 9 Copy-Paste CLI Parts
Embeds07 X post embed frame — a card whose border brightness is recomputed on a cycle to glow9 YouTube Embed Code UI Patterns (Copy-Paste)