07강수 확률이 자라는 막대와 풍향을 짚는 나침반
시간마다 강수 확률 막대가 아래에서부터 자라 오르고, 옆 나침반의 바늘은 풍향으로 돌아가 속도 숫자와 함께 방향 이름을 바꿉니다.
쓰임강수 확률·바람 카드, 세탁·야외 일정 안내
scaleYrotatetransform-origin

<main class="stage stage--orange">
<section class="pw is-demo" id="pw" aria-label="강수 확률과 바람">
<div class="pw__rain">
<p class="pw__rt">강수 확률</p>
<div class="pw__cols" role="img" aria-label="시간별 강수 확률 막대">
<div class="pw__col"><span class="pw__pct">10</span><span class="pw__barwrap"><span class="pw__bar" style="--h: 20%"></span></span><span class="pw__hr">12시</span></div>
<div class="pw__col"><span class="pw__pct">35</span><span class="pw__barwrap"><span class="pw__bar" style="--h: 44%"></span></span><span class="pw__hr">13시</span></div>
<div class="pw__col"><span class="pw__pct">60</span><span class="pw__barwrap"><span class="pw__bar" style="--h: 68%"></span></span><span class="pw__hr">14시</span></div>
<div class="pw__col"><span class="pw__pct">80</span><span class="pw__barwrap"><span class="pw__bar" style="--h: 88%"></span></span><span class="pw__hr">15시</span></div>
<div class="pw__col"><span class="pw__pct">45</span><span class="pw__barwrap"><span class="pw__bar" style="--h: 52%"></span></span><span class="pw__hr">16시</span></div>
<div class="pw__col"><span class="pw__pct">20</span><span class="pw__barwrap"><span class="pw__bar" style="--h: 28%"></span></span><span class="pw__hr">17시</span></div>
</div>
</div>
<div class="pw__wind">
<div class="pw__compass" aria-hidden="true">
<span class="pw__tick pw__tick--n">북</span>
<span class="pw__tick pw__tick--e">동</span>
<span class="pw__tick pw__tick--s">남</span>
<span class="pw__tick pw__tick--w">서</span>
<span class="pw__needle"></span>
<span class="pw__hub"></span>
</div>
<p class="pw__wlab" aria-live="polite"><span class="pw__swap"><span class="pw__v">북서풍 3m/s</span><span class="pw__alt" aria-hidden="true">북서풍 5m/s</span></span></p>
<button class="pw__btn" type="button" id="pw-turn"><svg viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true"><path d="M184,184a32,32,0,0,1-32,32c-13.7,0-26.95-8.93-31.5-21.22a8,8,0,0,1,15-5.56C137.74,195.27,145,200,152,200a16,16,0,0,0,0-32H40a8,8,0,0,1,0-16H152A32,32,0,0,1,184,184Zm-64-80a32,32,0,0,0,0-64c-13.7,0-26.95,8.93-31.5,21.22a8,8,0,0,0,15,5.56C105.74,60.73,113,56,120,56a16,16,0,0,1,0,32H24a8,8,0,0,0,0,16Zm88-32c-13.7,0-26.95,8.93-31.5,21.22a8,8,0,0,0,15,5.56C193.74,92.73,201,88,208,88a16,16,0,0,1,0,32H32a8,8,0,0,0,0,16H208a32,32,0,0,0,0-64Z" opacity="0.2"/><path d="M120,96a24,24,0,1,1,24-24A24,24,0,0,1,120,96Zm88-16a24,24,0,1,0,24,24A24,24,0,0,0,208,80Zm-56,80a24,24,0,1,0,24,24A24,24,0,0,0,152,160Z"/></svg>방향 바꾸기</button>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 강수 막대 — 여섯 칸은 grid-template-columns: repeat(6, 1fr) (게이지는 grid, BD19 오탐 회피).
// 막대 성장은 scaleY (transform-origin: bottom), 높이값 자체는 --h 에 정적으로.
$duration: $dur-loop;
$easing: $ease-out;
$color: $subject-blue;
@include stage($stage-orange);
.pw {
box-sizing: border-box;
display: grid;
grid-template-columns: 1fr auto;
gap: $sp-3;
align-items: center;
width: min(300px, 92vw);
padding: $sp-2;
border-radius: $r-card;
background: $subject-cream;
color: $subject-ink;
box-shadow: $shadow-raised;
}
.pw * { box-sizing: border-box; }
.pw__rt {
margin: 0 0 $sp-1;
font-size: 11px;
font-weight: 700;
line-height: 1.3;
}
.pw__cols {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: $sp-1;
align-items: end;
}
.pw__col {
display: grid;
justify-items: center;
gap: $sp-1;
}
.pw__pct {
font-size: 9px;
font-weight: 700;
line-height: 1;
font-variant-numeric: tabular-nums;
color: rgba($subject-ink, .72);
}
.pw__barwrap {
position: relative;
display: block;
width: 100%;
height: 56px;
border-radius: $r-xs;
background: rgba(23, 20, 26, .07);
overflow: hidden;
}
.pw__bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: var(--h);
border-radius: $r-xs $r-xs 0 0;
transform-origin: bottom; // 규칙8: 채움 방향 — 막대는 바닥에서 자란다(기본 50% 50% 면 가운데서 벌어진다)
background: linear-gradient(180deg, #9cb7ce, $subject-blue);
}
.pw__hr {
font-size: 8px;
font-weight: 500;
line-height: 1;
color: rgba($subject-ink, .72);
}
// 나침반
.pw__wind {
display: grid;
justify-items: center;
gap: $sp-1;
}
.pw__compass {
position: relative;
width: 76px;
height: 76px;
border-radius: 50%;
background: rgba(23, 20, 26, .07);
box-shadow: inset 0 0 0 2px rgba(23, 20, 26, .12);
}
.pw__tick {
position: absolute;
font-size: 9px;
font-weight: 700;
line-height: 1;
color: rgba($subject-ink, .72);
}
.pw__tick--n { top: $sp-1; left: 50%; transform: translateX(-50%); }
.pw__tick--s { bottom: $sp-1; left: 50%; transform: translateX(-50%); }
.pw__tick--w { left: $sp-1; top: 50%; transform: translateY(-50%); }
.pw__tick--e { right: $sp-1; top: 50%; transform: translateY(-50%); }
.pw__needle {
position: absolute;
left: calc(50% - 1px);
top: calc(50% - 25px);
width: 2px;
height: 50px;
border-radius: $r-pill;
background: linear-gradient(180deg, $error 0 50%, rgba(23, 20, 26, .35) 50% 100%);
transform-origin: 50% 50%;
transform: rotate(315deg);
}
.pw__hub {
position: absolute;
left: calc(50% - 4px);
top: calc(50% - 4px);
width: 8px;
height: 8px;
border-radius: 50%;
background: $subject-ink;
}
.pw__wlab {
margin: 0;
font-size: 11px;
font-weight: 700;
line-height: 1.3;
font-variant-numeric: tabular-nums;
}
.pw__btn {
display: inline-flex;
align-items: center;
gap: $sp-1;
padding: $sp-1 $sp-2;
border: 0;
border-radius: $r-control;
background: $subject-ink;
color: $subject-cream;
font-size: 10px;
font-weight: 700;
line-height: 1.3;
cursor: pointer;
&:focus-visible { outline: 2px solid $subject-blue; outline-offset: 2px; }
}
// 겹쳐 둔 두 번째 값 — 미리보기 루프에서만 보인다
.pw__swap { position: relative; display: inline-block; }
.pw__alt {
position: absolute;
inset: 0;
opacity: 0;
white-space: nowrap;
}
// ── 격자 미리보기 루프 (2초) ──
// 여섯 막대가 시차를 두고 자라 오르고(0~48%), 88% 에 접힌다 — 복귀는 90% 안.
// 바늘은 북서(315도)에서 북(0도)으로 크게 한 바퀴 감아 돌아간 뒤 제자리.
@keyframes pw-grow {
0% { transform: scaleY(0); }
40%, 86% { transform: scaleY(1); }
88%, 100% { transform: scaleY(0); }
}
@keyframes pw-va { 0%, 44% { opacity: 1; } 45%, 87% { opacity: 0; } 88%, 100% { opacity: 1; } }
@keyframes pw-vb { 0%, 44% { opacity: 0; } 45%, 87% { opacity: 1; } 88%, 100% { opacity: 0; } }
@keyframes pw-swing {
0%, 30% { transform: rotate(315deg); }
55% { transform: rotate(45deg); }
88%, 100% { transform: rotate(315deg); }
}
.pw.is-demo {
.pw__col:nth-child(1) .pw__bar { animation-name: pw-grow; animation-delay: 0s; }
.pw__col:nth-child(2) .pw__bar { animation-name: pw-grow; animation-delay: .08s; }
.pw__col:nth-child(3) .pw__bar { animation-name: pw-grow; animation-delay: .16s; }
.pw__col:nth-child(4) .pw__bar { animation-name: pw-grow; animation-delay: .24s; }
.pw__col:nth-child(5) .pw__bar { animation-name: pw-grow; animation-delay: .32s; }
.pw__col:nth-child(6) .pw__bar { animation-name: pw-grow; animation-delay: .4s; }
.pw__bar {
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.pw__needle {
animation-name: pw-swing;
animation-duration: $duration;
animation-timing-function: $ease-spring;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.pw__swap .pw__v { animation-name: pw-va; }
.pw__swap .pw__alt { animation-name: pw-vb; }
.pw__swap .pw__v, .pw__swap .pw__alt {
animation-duration: $duration;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
}
@media (max-width: 340px) {
.pw { padding: $sp-1; gap: $sp-2; }
.pw__barwrap { height: 44px; }
.pw__compass { width: 64px; height: 64px; }
.pw__needle { height: 42px; top: calc(50% - 21px); }
.pw__tick { font-size: 8px; }
}
@media (prefers-reduced-motion: reduce) {
.pw.is-demo .pw__bar,
.pw.is-demo .pw__needle,
.pw.is-demo .pw__swap .pw__v,
.pw.is-demo .pw__swap .pw__alt {
animation: none !important;
transform: none !important;
}
// 정지 상태: 막대는 다 자란 모습, 바늘은 실제 풍향각
.pw.is-demo .pw__bar { transform: scaleY(1) !important; }
.pw.is-demo .pw__needle { transform: rotate(315deg) !important; }
.pw.is-demo .pw__swap .pw__v { opacity: 1 !important; }
.pw.is-demo .pw__swap .pw__alt { opacity: 0 !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.pw {
box-sizing: border-box;
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: center;
width: min(300px, 92vw);
padding: 8px;
border-radius: 16px;
background: #fff7e6;
color: #17141a;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.pw * {
box-sizing: border-box;
}
.pw__rt {
margin: 0 0 4px;
font-size: 11px;
font-weight: 700;
line-height: 1.3;
}
.pw__cols {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 4px;
align-items: end;
}
.pw__col {
display: grid;
justify-items: center;
gap: 4px;
}
.pw__pct {
font-size: 9px;
font-weight: 700;
line-height: 1;
font-variant-numeric: tabular-nums;
color: rgba(23, 20, 26, 0.72);
}
.pw__barwrap {
position: relative;
display: block;
width: 100%;
height: 56px;
border-radius: 4px;
background: rgba(23, 20, 26, 0.07);
overflow: hidden;
}
.pw__bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: var(--h);
border-radius: 4px 4px 0 0;
transform-origin: bottom;
background: linear-gradient(180deg, #9cb7ce, #2f6df6);
}
.pw__hr {
font-size: 8px;
font-weight: 500;
line-height: 1;
color: rgba(23, 20, 26, 0.72);
}
.pw__wind {
display: grid;
justify-items: center;
gap: 4px;
}
.pw__compass {
position: relative;
width: 76px;
height: 76px;
border-radius: 50%;
background: rgba(23, 20, 26, 0.07);
box-shadow: inset 0 0 0 2px rgba(23, 20, 26, 0.12);
}
.pw__tick {
position: absolute;
font-size: 9px;
font-weight: 700;
line-height: 1;
color: rgba(23, 20, 26, 0.72);
}
.pw__tick--n {
top: 4px;
left: 50%;
transform: translateX(-50%);
}
.pw__tick--s {
bottom: 4px;
left: 50%;
transform: translateX(-50%);
}
.pw__tick--w {
left: 4px;
top: 50%;
transform: translateY(-50%);
}
.pw__tick--e {
right: 4px;
top: 50%;
transform: translateY(-50%);
}
.pw__needle {
position: absolute;
left: calc(50% - 1px);
top: calc(50% - 25px);
width: 2px;
height: 50px;
border-radius: 999px;
background: linear-gradient(180deg, #c2361a 0 50%, rgba(23, 20, 26, 0.35) 50% 100%);
transform-origin: 50% 50%;
transform: rotate(315deg);
}
.pw__hub {
position: absolute;
left: calc(50% - 4px);
top: calc(50% - 4px);
width: 8px;
height: 8px;
border-radius: 50%;
background: #17141a;
}
.pw__wlab {
margin: 0;
font-size: 11px;
font-weight: 700;
line-height: 1.3;
font-variant-numeric: tabular-nums;
}
.pw__btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
border: 0;
border-radius: 12px;
background: #17141a;
color: #fff7e6;
font-size: 10px;
font-weight: 700;
line-height: 1.3;
cursor: pointer;
}
.pw__btn:focus-visible {
outline: 2px solid #2f6df6;
outline-offset: 2px;
}
.pw__swap {
position: relative;
display: inline-block;
}
.pw__alt {
position: absolute;
inset: 0;
opacity: 0;
white-space: nowrap;
}
@keyframes pw-grow {
0% {
transform: scaleY(0);
}
40%, 86% {
transform: scaleY(1);
}
88%, 100% {
transform: scaleY(0);
}
}
@keyframes pw-va {
0%, 44% {
opacity: 1;
}
45%, 87% {
opacity: 0;
}
88%, 100% {
opacity: 1;
}
}
@keyframes pw-vb {
0%, 44% {
opacity: 0;
}
45%, 87% {
opacity: 1;
}
88%, 100% {
opacity: 0;
}
}
@keyframes pw-swing {
0%, 30% {
transform: rotate(315deg);
}
55% {
transform: rotate(45deg);
}
88%, 100% {
transform: rotate(315deg);
}
}
.pw.is-demo .pw__col:nth-child(1) .pw__bar {
animation-name: pw-grow;
animation-delay: 0s;
}
.pw.is-demo .pw__col:nth-child(2) .pw__bar {
animation-name: pw-grow;
animation-delay: 0.08s;
}
.pw.is-demo .pw__col:nth-child(3) .pw__bar {
animation-name: pw-grow;
animation-delay: 0.16s;
}
.pw.is-demo .pw__col:nth-child(4) .pw__bar {
animation-name: pw-grow;
animation-delay: 0.24s;
}
.pw.is-demo .pw__col:nth-child(5) .pw__bar {
animation-name: pw-grow;
animation-delay: 0.32s;
}
.pw.is-demo .pw__col:nth-child(6) .pw__bar {
animation-name: pw-grow;
animation-delay: 0.4s;
}
.pw.is-demo .pw__bar {
animation-duration: 2s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.pw.is-demo .pw__needle {
animation-name: pw-swing;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.pw.is-demo .pw__swap .pw__v {
animation-name: pw-va;
}
.pw.is-demo .pw__swap .pw__alt {
animation-name: pw-vb;
}
.pw.is-demo .pw__swap .pw__v, .pw.is-demo .pw__swap .pw__alt {
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
@media (max-width: 340px) {
.pw {
padding: 4px;
gap: 8px;
}
.pw__barwrap {
height: 44px;
}
.pw__compass {
width: 64px;
height: 64px;
}
.pw__needle {
height: 42px;
top: calc(50% - 21px);
}
.pw__tick {
font-size: 8px;
}
}
@media (prefers-reduced-motion: reduce) {
.pw.is-demo .pw__bar,
.pw.is-demo .pw__needle,
.pw.is-demo .pw__swap .pw__v,
.pw.is-demo .pw__swap .pw__alt {
animation: none !important;
transform: none !important;
}
.pw.is-demo .pw__bar {
transform: scaleY(1) !important;
}
.pw.is-demo .pw__needle {
transform: rotate(315deg) !important;
}
.pw.is-demo .pw__swap .pw__v {
opacity: 1 !important;
}
.pw.is-demo .pw__swap .pw__alt {
opacity: 0 !important;
}
}// 강수 막대는 scaleY 로 아래에서 자라고(높이값은 --h 로 정적으로 들어 있다),
// 나침반 바늘은 풍향 각도로 돈다 — 누르면 여덟 방향을 돌아가며 바꾼다.
var pwRoot = document.getElementById('pw');
var pwDirs = [
{ deg: 0, name: '북풍', speed: 2 },
{ deg: 45, name: '북동풍', speed: 3 },
{ deg: 90, name: '동풍', speed: 4 },
{ deg: 135, name: '남동풍', speed: 3 },
{ deg: 180, name: '남풍', speed: 2 },
{ deg: 225, name: '남서풍', speed: 4 },
{ deg: 270, name: '서풍', speed: 5 },
{ deg: 315, name: '북서풍', speed: 3 }
];
var pwIdx = 7;
var pwNeedle = pwRoot.querySelector('.pw__needle');
var pwLabel = pwRoot.querySelector('.pw__wlab');
function pwApply(i) {
var d = pwDirs[i];
pwNeedle.style.transform = 'rotate(' + d.deg + 'deg)';
pwLabel.querySelector('.pw__v').textContent = d.name + ' ' + d.speed + 'm/s';
}
document.getElementById('pw-turn').addEventListener('click', function () {
pwRoot.classList.remove('is-demo');
pwIdx = (pwIdx + 1) % pwDirs.length;
pwApply(pwIdx);
});
pwApply(pwIdx);import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./WeatherPrecipWind.module.scss";
// vanilla 판(run/281/demo/07-weather-precip-wind/index.html) 의 React 포트.
// 강수 막대는 scaleY 로 아래에서 자라고(높이값은 --h 로 정적으로 들어 있다),
// 나침반 바늘은 풍향 각도로 돈다 — 누르면 여덟 방향을 돌아가며 바꾼다.
export type PwBar = {
/** 퍼센트 숫자 */
pct: number;
/** 막대 높이 — CSS 변수 --h 값 그대로("20%") */
h: string;
/** 밑 라벨("12시") — 언어팩이 통째로 바꾼다 */
hour: string;
};
export type PwDir = {
deg: number;
name: string;
speed: number;
};
export type WeatherPrecipWindProps = {
groupLabel?: string;
rainTitle?: string;
barsAria?: string;
buttonLabel?: string;
bars?: PwBar[];
dirs?: PwDir[];
/** 겹쳐 둔 두 번째 풍문 글자 — 미리보기 루프에서만 보인다 */
altLabel?: string;
/** 처음에 골라 둔 풍향 인덱스 */
startIdx?: number;
/** 격자 미리보기 2초 자동 루프. 사람이 손대면 꺼진다 */
demo?: boolean;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
// 막대 높이 — CSS 변수로만 전달한다
type BarVars = CSSProperties & { "--h"?: string };
// 바늘 각도 — inline style 로 넣는다
type NeedleVars = CSSProperties & { transform?: string };
const DEFAULT_BARS: PwBar[] = [
{ pct: 10, h: "20%", hour: "12시" },
{ pct: 35, h: "44%", hour: "13시" },
{ pct: 60, h: "68%", hour: "14시" },
{ pct: 80, h: "88%", hour: "15시" },
{ pct: 45, h: "52%", hour: "16시" },
{ pct: 20, h: "28%", hour: "17시" },
];
const DEFAULT_DIRS: PwDir[] = [
{ deg: 0, name: "북풍", speed: 2 },
{ deg: 45, name: "북동풍", speed: 3 },
{ deg: 90, name: "동풍", speed: 4 },
{ deg: 135, name: "남동풍", speed: 3 },
{ deg: 180, name: "남풍", speed: 2 },
{ deg: 225, name: "남서풍", speed: 4 },
{ deg: 270, name: "서풍", speed: 5 },
{ deg: 315, name: "북서풍", speed: 3 },
];
function WindMarkIcon() {
return (
<svg viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true"><path d="M184,184a32,32,0,0,1-32,32c-13.7,0-26.95-8.93-31.5-21.22a8,8,0,0,1,15-5.56C137.74,195.27,145,200,152,200a16,16,0,0,0,0-32H40a8,8,0,0,1,0-16H152A32,32,0,0,1,184,184Zm-64-80a32,32,0,0,0,0-64c-13.7,0-26.95,8.93-31.5,21.22a8,8,0,0,0,15,5.56C105.74,60.73,113,56,120,56a16,16,0,0,1,0,32H24a8,8,0,0,0,0,16Zm88-32c-13.7,0-26.95,8.93-31.5,21.22a8,8,0,0,0,15,5.56C193.74,92.73,201,88,208,88a16,16,0,0,1,0,32H32a8,8,0,0,0,0,16H208a32,32,0,0,0,0-64Z" opacity="0.2"/><path d="M120,96a24,24,0,1,1,24-24A24,24,0,0,1,120,96Zm88-16a24,24,0,1,0,24,24A24,24,0,0,0,208,80Zm-56,80a24,24,0,1,0,24,24A24,24,0,0,0,152,160Z"/></svg>
);
}
export default function WeatherPrecipWind({
groupLabel = "강수 확률과 바람",
rainTitle = "강수 확률",
barsAria = "시간별 강수 확률 막대",
buttonLabel = "방향 바꾸기",
bars = DEFAULT_BARS,
dirs = DEFAULT_DIRS,
altLabel = "북서풍 5m/s",
startIdx = 7,
demo = true,
duration = 2000,
easing = "ease-out",
color = "#2f6df6",
}: WeatherPrecipWindProps) {
const [idx, setIdx] = useState(startIdx);
const [live, setLive] = useState(demo);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
const dir = dirs[idx % dirs.length];
const needleVars: NeedleVars = { transform: `rotate(${dir.deg}deg)` };
const windWord = `${dir.name} ${dir.speed}m/s`;
function turn(): void {
setLive(false);
setIdx((prev: number) => (prev + 1) % dirs.length);
}
const cls = [styles.pw, live ? styles.isDemo : ""].filter(Boolean).join(" ");
return (
<main className={styles.stage}>
<section className={cls} style={vars} aria-label={groupLabel}>
<div className={styles.rain}>
<p className={styles.rt}>{rainTitle}</p>
<div className={styles.cols} role="img" aria-label={barsAria}>
{bars.map((b: PwBar) => (
<div className={styles.col} key={b.hour}>
<span className={styles.pct}>{b.pct}</span>
<span className={styles.barwrap}><span className={styles.bar} style={({ "--h": b.h } as BarVars)} /></span>
<span className={styles.hr}>{b.hour}</span>
</div>
))}
</div>
</div>
<div className={styles.wind}>
<div className={styles.compass} aria-hidden="true">
<span className={`${styles.tick} ${styles["tick--n"]}`}>북</span>
<span className={`${styles.tick} ${styles["tick--e"]}`}>동</span>
<span className={`${styles.tick} ${styles["tick--s"]}`}>남</span>
<span className={`${styles.tick} ${styles["tick--w"]}`}>서</span>
<span className={styles.needle} style={needleVars} />
<span className={styles.hub} />
</div>
<p className={styles.wlab} aria-live="polite">
<span className={styles.swap}>
<span className={styles.v}>{windWord}</span>
<span className={styles.alt} aria-hidden="true">{altLabel}</span>
</span>
</p>
<button className={styles.btn} type="button" onClick={turn}><WindMarkIcon />{buttonLabel}</button>
</div>
</section>
</main>
);
}07 강수 확률이 자라는 막대와 풍향을 짚는 나침반 — 측정 누적면적 3.967% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 날씨 위젯 9종 — 복붙하면 되는 온도·시계·아이콘 코드
이 데모가 속한 글
날씨 위젯 9종 — 복붙하면 되는 온도·시계·아이콘 코드날씨 위젯은 사이트에 붙이는 순간 가장 먼저 보이는 작은 화면 묶음입니다. 「날씨 위젯」 을 검색하는 사람이 원하는 건 큰 그림이 아니라 온도 카드·시간별 띠·해의 길 같은 부품 하나인데, 여기 모은 아홉 벌은 복붙 이 글의 데모 전부 보기


