03픽셀 커서 세 종류
화살표·모래시계·손가락을 작은 격자 그림으로 두고 확대할 때 흐려지지 않도록 픽셀 경계를 그대로 지킵니다. 무대 위에서 세 커서가 차례로 바뀌며 모래시계는 제자리에서 뒤집힙니다.
쓰임커서까지 갈아 끼우는 테마 페이지
image-renderingconic-gradientsteps(1, end)
<main class="stage">
<div class="pxc is-demo" id="pxc">
<div class="pxc__slot" id="slot" data-cur="arrow" aria-label="커서 미리보기">
<span class="px pxc__cur pxc__cur--arrow" aria-hidden="true"></span>
<span class="px pxc__cur pxc__cur--hour" aria-hidden="true"></span>
<span class="px pxc__cur pxc__cur--hand" aria-hidden="true"></span>
<span class="pxc__grid" aria-hidden="true"></span>
</div>
<div class="pxc__row" role="group" aria-label="커서 고르기">
<button class="pxc__pick is-on" type="button" data-set="arrow" aria-pressed="true">
<span class="px pxc__mini pxc__mini--arrow" aria-hidden="true"></span>
<span class="pxc__lbl">화살표</span>
</button>
<button class="pxc__pick" type="button" data-set="hour" aria-pressed="false">
<span class="px pxc__mini pxc__mini--hour" aria-hidden="true"></span>
<span class="pxc__lbl">모래시계</span>
</button>
<button class="pxc__pick" type="button" data-set="hand" aria-pressed="false">
<span class="px pxc__mini pxc__mini--hand" aria-hidden="true"></span>
<span class="pxc__lbl">손가락</span>
</button>
</div>
</div>
</main>@use "sass:list";
@use "../../../../site/static/demo-tokens" as *;
// 픽셀 커서 세 종류 — 화살표·모래시계·손가락을 1px 칸의 격자 그림(box-shadow 픽셀)으로 그리고
// scale 로 정수배만 확대해 픽셀 경계를 그대로 지킨다. 칸의 격자 무늬는 conic-gradient 체커보드다.
// 미리보기(.is-demo)에서는 세 커서가 steps(1, end) 컷으로 차례로 바뀌고 모래시계는 제자리에서 뒤집힌다.
$duration: $dur-loop;
$easing: $ease-out;
$color: $stage-yellow;
// ── 픽셀 그림 — 가로 줄 runs (x y 폭). K 는 검은 외곽선, W 는 흰 채움, S 는 모래
@function pxRuns($runs, $c) {
$s: ();
@each $r in $runs {
@for $i from 0 through list.nth($r, 3) - 1 {
$s: list.append($s, ((list.nth($r, 1) + $i) * 1px) (list.nth($r, 2) * 1px) 0 0 $c, comma);
}
}
@return $s;
}
// 화살표 11x16
$arrow-k: (0 0 1), (0 1 2), (0 2 1), (2 2 1), (0 3 1), (3 3 1), (0 4 1), (4 4 1), (0 5 1), (5 5 1),
(0 6 1), (6 6 1), (0 7 1), (7 7 1), (0 8 1), (8 8 1), (0 9 1), (9 9 1),
(0 10 1), (6 10 5), (0 11 1), (3 11 1), (6 11 1), (0 12 1), (2 12 1), (4 12 1), (7 12 1),
(0 13 2), (4 13 1), (7 13 1), (0 14 1), (5 14 1), (8 14 1), (5 15 2);
$arrow-w: (1 2 1), (1 3 2), (1 4 3), (1 5 4), (1 6 5), (1 7 6), (1 8 7), (1 9 8), (1 10 5),
(1 11 2), (4 11 2), (1 12 1), (5 12 2), (5 13 2), (6 14 2);
// 모래시계 9x11
$hour-k: (0 0 9), (0 1 1), (8 1 1), (0 2 1), (8 2 1), (0 3 1), (8 3 1), (0 4 1), (8 4 1),
(0 5 1), (8 5 1), (0 6 1), (8 6 1), (0 7 1), (8 7 1), (0 8 1), (8 8 1), (0 9 1), (8 9 1), (0 10 9);
$hour-s: (1 1 7), (2 2 5), (3 3 3), (4 4 1), (4 5 1), (3 6 1), (5 6 1),
(2 7 1), (4 7 1), (6 7 1), (2 8 5), (1 9 7);
// 손가락 8x13
$hand-k: (3 0 2), (2 1 1), (5 1 1), (2 2 1), (5 2 1), (2 3 1), (5 3 1), (2 4 1), (5 4 1),
(1 5 2), (5 5 2), (0 6 1), (7 6 1), (0 7 1), (7 7 1), (0 8 1), (7 8 1), (0 9 1), (7 9 1),
(0 10 1), (7 10 1), (1 11 1), (6 11 1), (1 12 6);
$hand-w: (3 1 2), (3 2 2), (3 3 2), (3 4 2), (3 5 2), (1 6 6), (1 7 6), (1 8 6), (1 9 6),
(1 10 6), (2 11 4);
$arrow-art: list.join(pxRuns($arrow-k, $subject-ink), pxRuns($arrow-w, $stage-paper));
$hour-art: list.join(pxRuns($hour-k, $subject-ink), pxRuns($hour-s, $color));
$hand-art: list.join(pxRuns($hand-k, $subject-ink), pxRuns($hand-w, $stage-paper));
@include stage($stage-orange);
.pxc {
display: flex; flex-direction: column; gap: $sp-2;
width: min(100%, 200px);
}
// ── 픽셀 그림 바탕 — 1px 칸 하나. 픽셀 그림 요소에는 image-rendering: pixelated 를 기본으로 심는다
// (그림을 배경 이미지·캔버스로 바꿔 확대해도 보간으로 흐려지지 않는다)
.px {
display: block; width: 1px; height: 1px;
image-rendering: pixelated;
}
// ── 큰 미리보기 칸 — conic-gradient 체커보드 격자 위에 세 커서를 한 칸에 겹쳐 둔다
.pxc__slot {
position: relative;
display: grid; place-items: center;
height: 124px;
background: $subject-cream; border-radius: $r-sm;
box-shadow: inset 0 0 0 1px rgba($subject-ink, .18);
}
.pxc__slot > .px { grid-area: 1 / 1; }
.pxc__grid {
position: absolute; inset: 0; pointer-events: none;
background-image: conic-gradient(
rgba($subject-ink, .08) 25%,
transparent 0 50%,
rgba($subject-ink, .08) 0 75%,
transparent 0);
background-size: 12px 12px; // 격자 한 칸 12px = 커서 픽셀(6px) 두 칸
}
.pxc__cur { scale: 6; opacity: 0; }
.pxc__cur--arrow { box-shadow: $arrow-art; }
.pxc__cur--hour { box-shadow: $hour-art; }
.pxc__cur--hand { box-shadow: $hand-art; }
// 정적 경로(사람이 고른 커서) — data-cur 값이 곧 보이는 커서다
.pxc__slot[data-cur="arrow"] .pxc__cur--arrow { opacity: 1; }
.pxc__slot[data-cur="hour"] .pxc__cur--hour { opacity: 1; }
.pxc__slot[data-cur="hand"] .pxc__cur--hand { opacity: 1; }
// ── 고르기 단추 — 실제 크기(2배) 커서와 이름
.pxc__row { display: grid; grid-template-columns: repeat(3, 1fr); gap: $sp-2; }
.pxc__pick {
display: grid; justify-items: center; align-content: center; gap: $sp-1;
box-sizing: border-box; height: 64px; padding: $sp-1; border: 0;
background: $subject-cream; color: $subject-ink; border-radius: $r-xs;
font-family: inherit; font-weight: 700; font-size: 10px; line-height: 1.4;
cursor: pointer;
}
.pxc__pick.is-on { box-shadow: inset 0 0 0 2px $subject-ink; }
.pxc__mini { scale: 2; }
.pxc__mini--arrow { box-shadow: $arrow-art; }
.pxc__mini--hour { box-shadow: $hour-art; }
.pxc__mini--hand { box-shadow: $hand-art; }
// ── 격자 미리보기 루프 (2초)
// 0-29% 화살표(마우스를 옮기듯 작게 배회), 30-62% 모래시계(제자리에서 180 도 뒤집힘),
// 63-88% 손가락(살짝 흔들림). 교체는 전부 steps(1, end) 컷이고 89% 안에 화살표로 돌아온다.
.pxc.is-demo .pxc__cur {
animation-duration: $duration;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.pxc.is-demo .pxc__cur--arrow {
animation-name: pxcArrowO, pxcArrowM;
animation-timing-function: steps(1, end), $easing;
}
.pxc.is-demo .pxc__cur--hour {
animation-name: pxcHourO, pxcHourR;
animation-timing-function: steps(1, end), linear;
}
.pxc.is-demo .pxc__cur--hand {
animation-name: pxcHandO, pxcHandM;
animation-timing-function: steps(1, end), $easing;
}
@keyframes pxcArrowO {
0%, 29% { opacity: 1; }
30%, 88% { opacity: 0; }
89%, 100% { opacity: 1; }
}
@keyframes pxcArrowM {
0% { translate: 0 0; }
10% { translate: 3px 2px; }
20% { translate: -2px 4px; }
29%, 100% { translate: 0 0; }
}
@keyframes pxcHourO {
0%, 29% { opacity: 0; }
30%, 62% { opacity: 1; }
63%, 100% { opacity: 0; }
}
@keyframes pxcHourR {
0%, 30% { rotate: 0deg; }
62%, 100% { rotate: 180deg; }
}
@keyframes pxcHandO {
0%, 62% { opacity: 0; }
63%, 88% { opacity: 1; }
89%, 100% { opacity: 0; }
}
@keyframes pxcHandM {
0%, 63% { translate: 0 0; }
72% { translate: 2px -2px; }
80% { translate: -2px 0; }
88%, 100% { translate: 0 0; }
}
// ── 좁은 화면: 큰 칸과 그림 배율을 줄인다(정수 배율만 — 픽셀 경계 유지)
@media (max-width: 340px) {
.pxc { gap: $sp-1; }
.pxc__slot { height: 88px; }
.pxc__cur { scale: 4; }
.pxc__grid { background-size: 8px 8px; }
.pxc__pick { height: 52px; }
.pxc__mini { scale: 1; }
}
@media (prefers-reduced-motion: reduce) {
.pxc.is-demo .pxc__cur { animation: none !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);
}
.pxc {
display: flex;
flex-direction: column;
gap: 8px;
width: min(100%, 200px);
}
.px {
display: block;
width: 1px;
height: 1px;
image-rendering: pixelated;
}
.pxc__slot {
position: relative;
display: grid;
place-items: center;
height: 124px;
background: #fff7e6;
border-radius: 8px;
box-shadow: inset 0 0 0 1px rgba(23, 20, 26, 0.18);
}
.pxc__slot > .px {
grid-area: 1/1;
}
.pxc__grid {
position: absolute;
inset: 0;
pointer-events: none;
background-image: conic-gradient(rgba(23, 20, 26, 0.08) 25%, transparent 0 50%, rgba(23, 20, 26, 0.08) 0 75%, transparent 0);
background-size: 12px 12px;
}
.pxc__cur {
scale: 6;
opacity: 0;
}
.pxc__cur--arrow {
box-shadow: 0px 0px 0 0 #17141a, 0px 1px 0 0 #17141a, 1px 1px 0 0 #17141a, 0px 2px 0 0 #17141a, 2px 2px 0 0 #17141a, 0px 3px 0 0 #17141a, 3px 3px 0 0 #17141a, 0px 4px 0 0 #17141a, 4px 4px 0 0 #17141a, 0px 5px 0 0 #17141a, 5px 5px 0 0 #17141a, 0px 6px 0 0 #17141a, 6px 6px 0 0 #17141a, 0px 7px 0 0 #17141a, 7px 7px 0 0 #17141a, 0px 8px 0 0 #17141a, 8px 8px 0 0 #17141a, 0px 9px 0 0 #17141a, 9px 9px 0 0 #17141a, 0px 10px 0 0 #17141a, 6px 10px 0 0 #17141a, 7px 10px 0 0 #17141a, 8px 10px 0 0 #17141a, 9px 10px 0 0 #17141a, 10px 10px 0 0 #17141a, 0px 11px 0 0 #17141a, 3px 11px 0 0 #17141a, 6px 11px 0 0 #17141a, 0px 12px 0 0 #17141a, 2px 12px 0 0 #17141a, 4px 12px 0 0 #17141a, 7px 12px 0 0 #17141a, 0px 13px 0 0 #17141a, 1px 13px 0 0 #17141a, 4px 13px 0 0 #17141a, 7px 13px 0 0 #17141a, 0px 14px 0 0 #17141a, 5px 14px 0 0 #17141a, 8px 14px 0 0 #17141a, 5px 15px 0 0 #17141a, 6px 15px 0 0 #17141a, 1px 2px 0 0 #ffffff, 1px 3px 0 0 #ffffff, 2px 3px 0 0 #ffffff, 1px 4px 0 0 #ffffff, 2px 4px 0 0 #ffffff, 3px 4px 0 0 #ffffff, 1px 5px 0 0 #ffffff, 2px 5px 0 0 #ffffff, 3px 5px 0 0 #ffffff, 4px 5px 0 0 #ffffff, 1px 6px 0 0 #ffffff, 2px 6px 0 0 #ffffff, 3px 6px 0 0 #ffffff, 4px 6px 0 0 #ffffff, 5px 6px 0 0 #ffffff, 1px 7px 0 0 #ffffff, 2px 7px 0 0 #ffffff, 3px 7px 0 0 #ffffff, 4px 7px 0 0 #ffffff, 5px 7px 0 0 #ffffff, 6px 7px 0 0 #ffffff, 1px 8px 0 0 #ffffff, 2px 8px 0 0 #ffffff, 3px 8px 0 0 #ffffff, 4px 8px 0 0 #ffffff, 5px 8px 0 0 #ffffff, 6px 8px 0 0 #ffffff, 7px 8px 0 0 #ffffff, 1px 9px 0 0 #ffffff, 2px 9px 0 0 #ffffff, 3px 9px 0 0 #ffffff, 4px 9px 0 0 #ffffff, 5px 9px 0 0 #ffffff, 6px 9px 0 0 #ffffff, 7px 9px 0 0 #ffffff, 8px 9px 0 0 #ffffff, 1px 10px 0 0 #ffffff, 2px 10px 0 0 #ffffff, 3px 10px 0 0 #ffffff, 4px 10px 0 0 #ffffff, 5px 10px 0 0 #ffffff, 1px 11px 0 0 #ffffff, 2px 11px 0 0 #ffffff, 4px 11px 0 0 #ffffff, 5px 11px 0 0 #ffffff, 1px 12px 0 0 #ffffff, 5px 12px 0 0 #ffffff, 6px 12px 0 0 #ffffff, 5px 13px 0 0 #ffffff, 6px 13px 0 0 #ffffff, 6px 14px 0 0 #ffffff, 7px 14px 0 0 #ffffff;
}
.pxc__cur--hour {
box-shadow: 0px 0px 0 0 #17141a, 1px 0px 0 0 #17141a, 2px 0px 0 0 #17141a, 3px 0px 0 0 #17141a, 4px 0px 0 0 #17141a, 5px 0px 0 0 #17141a, 6px 0px 0 0 #17141a, 7px 0px 0 0 #17141a, 8px 0px 0 0 #17141a, 0px 1px 0 0 #17141a, 8px 1px 0 0 #17141a, 0px 2px 0 0 #17141a, 8px 2px 0 0 #17141a, 0px 3px 0 0 #17141a, 8px 3px 0 0 #17141a, 0px 4px 0 0 #17141a, 8px 4px 0 0 #17141a, 0px 5px 0 0 #17141a, 8px 5px 0 0 #17141a, 0px 6px 0 0 #17141a, 8px 6px 0 0 #17141a, 0px 7px 0 0 #17141a, 8px 7px 0 0 #17141a, 0px 8px 0 0 #17141a, 8px 8px 0 0 #17141a, 0px 9px 0 0 #17141a, 8px 9px 0 0 #17141a, 0px 10px 0 0 #17141a, 1px 10px 0 0 #17141a, 2px 10px 0 0 #17141a, 3px 10px 0 0 #17141a, 4px 10px 0 0 #17141a, 5px 10px 0 0 #17141a, 6px 10px 0 0 #17141a, 7px 10px 0 0 #17141a, 8px 10px 0 0 #17141a, 1px 1px 0 0 #ffd23f, 2px 1px 0 0 #ffd23f, 3px 1px 0 0 #ffd23f, 4px 1px 0 0 #ffd23f, 5px 1px 0 0 #ffd23f, 6px 1px 0 0 #ffd23f, 7px 1px 0 0 #ffd23f, 2px 2px 0 0 #ffd23f, 3px 2px 0 0 #ffd23f, 4px 2px 0 0 #ffd23f, 5px 2px 0 0 #ffd23f, 6px 2px 0 0 #ffd23f, 3px 3px 0 0 #ffd23f, 4px 3px 0 0 #ffd23f, 5px 3px 0 0 #ffd23f, 4px 4px 0 0 #ffd23f, 4px 5px 0 0 #ffd23f, 3px 6px 0 0 #ffd23f, 5px 6px 0 0 #ffd23f, 2px 7px 0 0 #ffd23f, 4px 7px 0 0 #ffd23f, 6px 7px 0 0 #ffd23f, 2px 8px 0 0 #ffd23f, 3px 8px 0 0 #ffd23f, 4px 8px 0 0 #ffd23f, 5px 8px 0 0 #ffd23f, 6px 8px 0 0 #ffd23f, 1px 9px 0 0 #ffd23f, 2px 9px 0 0 #ffd23f, 3px 9px 0 0 #ffd23f, 4px 9px 0 0 #ffd23f, 5px 9px 0 0 #ffd23f, 6px 9px 0 0 #ffd23f, 7px 9px 0 0 #ffd23f;
}
.pxc__cur--hand {
box-shadow: 3px 0px 0 0 #17141a, 4px 0px 0 0 #17141a, 2px 1px 0 0 #17141a, 5px 1px 0 0 #17141a, 2px 2px 0 0 #17141a, 5px 2px 0 0 #17141a, 2px 3px 0 0 #17141a, 5px 3px 0 0 #17141a, 2px 4px 0 0 #17141a, 5px 4px 0 0 #17141a, 1px 5px 0 0 #17141a, 2px 5px 0 0 #17141a, 5px 5px 0 0 #17141a, 6px 5px 0 0 #17141a, 0px 6px 0 0 #17141a, 7px 6px 0 0 #17141a, 0px 7px 0 0 #17141a, 7px 7px 0 0 #17141a, 0px 8px 0 0 #17141a, 7px 8px 0 0 #17141a, 0px 9px 0 0 #17141a, 7px 9px 0 0 #17141a, 0px 10px 0 0 #17141a, 7px 10px 0 0 #17141a, 1px 11px 0 0 #17141a, 6px 11px 0 0 #17141a, 1px 12px 0 0 #17141a, 2px 12px 0 0 #17141a, 3px 12px 0 0 #17141a, 4px 12px 0 0 #17141a, 5px 12px 0 0 #17141a, 6px 12px 0 0 #17141a, 3px 1px 0 0 #ffffff, 4px 1px 0 0 #ffffff, 3px 2px 0 0 #ffffff, 4px 2px 0 0 #ffffff, 3px 3px 0 0 #ffffff, 4px 3px 0 0 #ffffff, 3px 4px 0 0 #ffffff, 4px 4px 0 0 #ffffff, 3px 5px 0 0 #ffffff, 4px 5px 0 0 #ffffff, 1px 6px 0 0 #ffffff, 2px 6px 0 0 #ffffff, 3px 6px 0 0 #ffffff, 4px 6px 0 0 #ffffff, 5px 6px 0 0 #ffffff, 6px 6px 0 0 #ffffff, 1px 7px 0 0 #ffffff, 2px 7px 0 0 #ffffff, 3px 7px 0 0 #ffffff, 4px 7px 0 0 #ffffff, 5px 7px 0 0 #ffffff, 6px 7px 0 0 #ffffff, 1px 8px 0 0 #ffffff, 2px 8px 0 0 #ffffff, 3px 8px 0 0 #ffffff, 4px 8px 0 0 #ffffff, 5px 8px 0 0 #ffffff, 6px 8px 0 0 #ffffff, 1px 9px 0 0 #ffffff, 2px 9px 0 0 #ffffff, 3px 9px 0 0 #ffffff, 4px 9px 0 0 #ffffff, 5px 9px 0 0 #ffffff, 6px 9px 0 0 #ffffff, 1px 10px 0 0 #ffffff, 2px 10px 0 0 #ffffff, 3px 10px 0 0 #ffffff, 4px 10px 0 0 #ffffff, 5px 10px 0 0 #ffffff, 6px 10px 0 0 #ffffff, 2px 11px 0 0 #ffffff, 3px 11px 0 0 #ffffff, 4px 11px 0 0 #ffffff, 5px 11px 0 0 #ffffff;
}
.pxc__slot[data-cur=arrow] .pxc__cur--arrow {
opacity: 1;
}
.pxc__slot[data-cur=hour] .pxc__cur--hour {
opacity: 1;
}
.pxc__slot[data-cur=hand] .pxc__cur--hand {
opacity: 1;
}
.pxc__row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.pxc__pick {
display: grid;
justify-items: center;
align-content: center;
gap: 4px;
box-sizing: border-box;
height: 64px;
padding: 4px;
border: 0;
background: #fff7e6;
color: #17141a;
border-radius: 4px;
font-family: inherit;
font-weight: 700;
font-size: 10px;
line-height: 1.4;
cursor: pointer;
}
.pxc__pick.is-on {
box-shadow: inset 0 0 0 2px #17141a;
}
.pxc__mini {
scale: 2;
}
.pxc__mini--arrow {
box-shadow: 0px 0px 0 0 #17141a, 0px 1px 0 0 #17141a, 1px 1px 0 0 #17141a, 0px 2px 0 0 #17141a, 2px 2px 0 0 #17141a, 0px 3px 0 0 #17141a, 3px 3px 0 0 #17141a, 0px 4px 0 0 #17141a, 4px 4px 0 0 #17141a, 0px 5px 0 0 #17141a, 5px 5px 0 0 #17141a, 0px 6px 0 0 #17141a, 6px 6px 0 0 #17141a, 0px 7px 0 0 #17141a, 7px 7px 0 0 #17141a, 0px 8px 0 0 #17141a, 8px 8px 0 0 #17141a, 0px 9px 0 0 #17141a, 9px 9px 0 0 #17141a, 0px 10px 0 0 #17141a, 6px 10px 0 0 #17141a, 7px 10px 0 0 #17141a, 8px 10px 0 0 #17141a, 9px 10px 0 0 #17141a, 10px 10px 0 0 #17141a, 0px 11px 0 0 #17141a, 3px 11px 0 0 #17141a, 6px 11px 0 0 #17141a, 0px 12px 0 0 #17141a, 2px 12px 0 0 #17141a, 4px 12px 0 0 #17141a, 7px 12px 0 0 #17141a, 0px 13px 0 0 #17141a, 1px 13px 0 0 #17141a, 4px 13px 0 0 #17141a, 7px 13px 0 0 #17141a, 0px 14px 0 0 #17141a, 5px 14px 0 0 #17141a, 8px 14px 0 0 #17141a, 5px 15px 0 0 #17141a, 6px 15px 0 0 #17141a, 1px 2px 0 0 #ffffff, 1px 3px 0 0 #ffffff, 2px 3px 0 0 #ffffff, 1px 4px 0 0 #ffffff, 2px 4px 0 0 #ffffff, 3px 4px 0 0 #ffffff, 1px 5px 0 0 #ffffff, 2px 5px 0 0 #ffffff, 3px 5px 0 0 #ffffff, 4px 5px 0 0 #ffffff, 1px 6px 0 0 #ffffff, 2px 6px 0 0 #ffffff, 3px 6px 0 0 #ffffff, 4px 6px 0 0 #ffffff, 5px 6px 0 0 #ffffff, 1px 7px 0 0 #ffffff, 2px 7px 0 0 #ffffff, 3px 7px 0 0 #ffffff, 4px 7px 0 0 #ffffff, 5px 7px 0 0 #ffffff, 6px 7px 0 0 #ffffff, 1px 8px 0 0 #ffffff, 2px 8px 0 0 #ffffff, 3px 8px 0 0 #ffffff, 4px 8px 0 0 #ffffff, 5px 8px 0 0 #ffffff, 6px 8px 0 0 #ffffff, 7px 8px 0 0 #ffffff, 1px 9px 0 0 #ffffff, 2px 9px 0 0 #ffffff, 3px 9px 0 0 #ffffff, 4px 9px 0 0 #ffffff, 5px 9px 0 0 #ffffff, 6px 9px 0 0 #ffffff, 7px 9px 0 0 #ffffff, 8px 9px 0 0 #ffffff, 1px 10px 0 0 #ffffff, 2px 10px 0 0 #ffffff, 3px 10px 0 0 #ffffff, 4px 10px 0 0 #ffffff, 5px 10px 0 0 #ffffff, 1px 11px 0 0 #ffffff, 2px 11px 0 0 #ffffff, 4px 11px 0 0 #ffffff, 5px 11px 0 0 #ffffff, 1px 12px 0 0 #ffffff, 5px 12px 0 0 #ffffff, 6px 12px 0 0 #ffffff, 5px 13px 0 0 #ffffff, 6px 13px 0 0 #ffffff, 6px 14px 0 0 #ffffff, 7px 14px 0 0 #ffffff;
}
.pxc__mini--hour {
box-shadow: 0px 0px 0 0 #17141a, 1px 0px 0 0 #17141a, 2px 0px 0 0 #17141a, 3px 0px 0 0 #17141a, 4px 0px 0 0 #17141a, 5px 0px 0 0 #17141a, 6px 0px 0 0 #17141a, 7px 0px 0 0 #17141a, 8px 0px 0 0 #17141a, 0px 1px 0 0 #17141a, 8px 1px 0 0 #17141a, 0px 2px 0 0 #17141a, 8px 2px 0 0 #17141a, 0px 3px 0 0 #17141a, 8px 3px 0 0 #17141a, 0px 4px 0 0 #17141a, 8px 4px 0 0 #17141a, 0px 5px 0 0 #17141a, 8px 5px 0 0 #17141a, 0px 6px 0 0 #17141a, 8px 6px 0 0 #17141a, 0px 7px 0 0 #17141a, 8px 7px 0 0 #17141a, 0px 8px 0 0 #17141a, 8px 8px 0 0 #17141a, 0px 9px 0 0 #17141a, 8px 9px 0 0 #17141a, 0px 10px 0 0 #17141a, 1px 10px 0 0 #17141a, 2px 10px 0 0 #17141a, 3px 10px 0 0 #17141a, 4px 10px 0 0 #17141a, 5px 10px 0 0 #17141a, 6px 10px 0 0 #17141a, 7px 10px 0 0 #17141a, 8px 10px 0 0 #17141a, 1px 1px 0 0 #ffd23f, 2px 1px 0 0 #ffd23f, 3px 1px 0 0 #ffd23f, 4px 1px 0 0 #ffd23f, 5px 1px 0 0 #ffd23f, 6px 1px 0 0 #ffd23f, 7px 1px 0 0 #ffd23f, 2px 2px 0 0 #ffd23f, 3px 2px 0 0 #ffd23f, 4px 2px 0 0 #ffd23f, 5px 2px 0 0 #ffd23f, 6px 2px 0 0 #ffd23f, 3px 3px 0 0 #ffd23f, 4px 3px 0 0 #ffd23f, 5px 3px 0 0 #ffd23f, 4px 4px 0 0 #ffd23f, 4px 5px 0 0 #ffd23f, 3px 6px 0 0 #ffd23f, 5px 6px 0 0 #ffd23f, 2px 7px 0 0 #ffd23f, 4px 7px 0 0 #ffd23f, 6px 7px 0 0 #ffd23f, 2px 8px 0 0 #ffd23f, 3px 8px 0 0 #ffd23f, 4px 8px 0 0 #ffd23f, 5px 8px 0 0 #ffd23f, 6px 8px 0 0 #ffd23f, 1px 9px 0 0 #ffd23f, 2px 9px 0 0 #ffd23f, 3px 9px 0 0 #ffd23f, 4px 9px 0 0 #ffd23f, 5px 9px 0 0 #ffd23f, 6px 9px 0 0 #ffd23f, 7px 9px 0 0 #ffd23f;
}
.pxc__mini--hand {
box-shadow: 3px 0px 0 0 #17141a, 4px 0px 0 0 #17141a, 2px 1px 0 0 #17141a, 5px 1px 0 0 #17141a, 2px 2px 0 0 #17141a, 5px 2px 0 0 #17141a, 2px 3px 0 0 #17141a, 5px 3px 0 0 #17141a, 2px 4px 0 0 #17141a, 5px 4px 0 0 #17141a, 1px 5px 0 0 #17141a, 2px 5px 0 0 #17141a, 5px 5px 0 0 #17141a, 6px 5px 0 0 #17141a, 0px 6px 0 0 #17141a, 7px 6px 0 0 #17141a, 0px 7px 0 0 #17141a, 7px 7px 0 0 #17141a, 0px 8px 0 0 #17141a, 7px 8px 0 0 #17141a, 0px 9px 0 0 #17141a, 7px 9px 0 0 #17141a, 0px 10px 0 0 #17141a, 7px 10px 0 0 #17141a, 1px 11px 0 0 #17141a, 6px 11px 0 0 #17141a, 1px 12px 0 0 #17141a, 2px 12px 0 0 #17141a, 3px 12px 0 0 #17141a, 4px 12px 0 0 #17141a, 5px 12px 0 0 #17141a, 6px 12px 0 0 #17141a, 3px 1px 0 0 #ffffff, 4px 1px 0 0 #ffffff, 3px 2px 0 0 #ffffff, 4px 2px 0 0 #ffffff, 3px 3px 0 0 #ffffff, 4px 3px 0 0 #ffffff, 3px 4px 0 0 #ffffff, 4px 4px 0 0 #ffffff, 3px 5px 0 0 #ffffff, 4px 5px 0 0 #ffffff, 1px 6px 0 0 #ffffff, 2px 6px 0 0 #ffffff, 3px 6px 0 0 #ffffff, 4px 6px 0 0 #ffffff, 5px 6px 0 0 #ffffff, 6px 6px 0 0 #ffffff, 1px 7px 0 0 #ffffff, 2px 7px 0 0 #ffffff, 3px 7px 0 0 #ffffff, 4px 7px 0 0 #ffffff, 5px 7px 0 0 #ffffff, 6px 7px 0 0 #ffffff, 1px 8px 0 0 #ffffff, 2px 8px 0 0 #ffffff, 3px 8px 0 0 #ffffff, 4px 8px 0 0 #ffffff, 5px 8px 0 0 #ffffff, 6px 8px 0 0 #ffffff, 1px 9px 0 0 #ffffff, 2px 9px 0 0 #ffffff, 3px 9px 0 0 #ffffff, 4px 9px 0 0 #ffffff, 5px 9px 0 0 #ffffff, 6px 9px 0 0 #ffffff, 1px 10px 0 0 #ffffff, 2px 10px 0 0 #ffffff, 3px 10px 0 0 #ffffff, 4px 10px 0 0 #ffffff, 5px 10px 0 0 #ffffff, 6px 10px 0 0 #ffffff, 2px 11px 0 0 #ffffff, 3px 11px 0 0 #ffffff, 4px 11px 0 0 #ffffff, 5px 11px 0 0 #ffffff;
}
.pxc.is-demo .pxc__cur {
animation-duration: 2s;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.pxc.is-demo .pxc__cur--arrow {
animation-name: pxcArrowO, pxcArrowM;
animation-timing-function: steps(1, end), ease-out;
}
.pxc.is-demo .pxc__cur--hour {
animation-name: pxcHourO, pxcHourR;
animation-timing-function: steps(1, end), linear;
}
.pxc.is-demo .pxc__cur--hand {
animation-name: pxcHandO, pxcHandM;
animation-timing-function: steps(1, end), ease-out;
}
@keyframes pxcArrowO {
0%, 29% {
opacity: 1;
}
30%, 88% {
opacity: 0;
}
89%, 100% {
opacity: 1;
}
}
@keyframes pxcArrowM {
0% {
translate: 0 0;
}
10% {
translate: 3px 2px;
}
20% {
translate: -2px 4px;
}
29%, 100% {
translate: 0 0;
}
}
@keyframes pxcHourO {
0%, 29% {
opacity: 0;
}
30%, 62% {
opacity: 1;
}
63%, 100% {
opacity: 0;
}
}
@keyframes pxcHourR {
0%, 30% {
rotate: 0deg;
}
62%, 100% {
rotate: 180deg;
}
}
@keyframes pxcHandO {
0%, 62% {
opacity: 0;
}
63%, 88% {
opacity: 1;
}
89%, 100% {
opacity: 0;
}
}
@keyframes pxcHandM {
0%, 63% {
translate: 0 0;
}
72% {
translate: 2px -2px;
}
80% {
translate: -2px 0;
}
88%, 100% {
translate: 0 0;
}
}
@media (max-width: 340px) {
.pxc {
gap: 4px;
}
.pxc__slot {
height: 88px;
}
.pxc__cur {
scale: 4;
}
.pxc__grid {
background-size: 8px 8px;
}
.pxc__pick {
height: 52px;
}
.pxc__mini {
scale: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.pxc.is-demo .pxc__cur {
animation: none !important;
}
}var pxc = document.getElementById('pxc');
var slot = document.getElementById('slot');
var picks = [].slice.call(document.querySelectorAll('.pxc__pick'));
picks.forEach(function (btn) {
btn.addEventListener('click', function () {
pxc.classList.remove('is-demo');
slot.dataset.cur = btn.dataset.set;
picks.forEach(function (other) {
var on = other === btn;
other.classList.toggle('is-on', on);
other.setAttribute('aria-pressed', on ? 'true' : 'false');
});
});
});// PixelCursors — vanilla 판(run/023/demo/03-os-pixel-cursor-set/index.html) 의 React 포트.
// 화살표·모래시계·손가락을 1px 격자 그림으로 그려 scale 로 정수배 확대한다. 고르기 단추가
// data-cur 값을 바꿔 큰 칸의 커서를 갈아 낀다. 미리보기 루프는 CSS keyframes 그대로다.
import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./PixelCursors.module.scss";
export type PixelCursorsProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const PICKS: { set: string; label: string }[] = [
{ set: "arrow", label: "화살표" },
{ set: "hour", label: "모래시계" },
{ set: "hand", label: "손가락" }
];
const ARIA_SLOT = "커서 미리보기";
const ARIA_GROUP = "커서 고르기";
export default function PixelCursors(props: PixelCursorsProps) {
const vars: Vars = {
"--duration": (props.duration ?? 2000) + "ms",
"--easing": props.easing ?? "ease-out",
"--color": props.color ?? "#ffd23f"
};
const [preview, setPreview] = useState<boolean>(true);
const [cur, setCur] = useState<string>("arrow");
return (
<main className={styles.stage}>
<div
className={styles.pxc + (preview ? " " + styles.isDemo : "")}
style={vars}
onPointerDown={() => setPreview(false)}
>
<div className={styles.slot} data-cur={cur} aria-label={ARIA_SLOT}>
<span className={styles.px + " " + styles.curArrow} aria-hidden="true" />
<span className={styles.px + " " + styles.curHour} aria-hidden="true" />
<span className={styles.px + " " + styles.curHand} aria-hidden="true" />
<span className={styles.grid} aria-hidden="true" />
</div>
<div className={styles.row} role="group" aria-label={ARIA_GROUP}>
{PICKS.map((p) => (
<button
key={p.set}
type="button"
className={styles.pick + (cur === p.set ? " " + styles.pickOn : "")}
aria-pressed={cur === p.set}
onClick={() => {
setPreview(false);
setCur(p.set);
}}
>
<span
className={
styles.px + " " + styles.mini + " " + styles["mini" + p.set.charAt(0).toUpperCase() + p.set.slice(1)]
}
aria-hidden="true"
/>
<span className={styles.lbl}>{p.label}</span>
</button>
))}
</div>
</div>
</main>
);
}03 픽셀 커서 세 종류 — 측정 누적면적 9.179% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 윈도우 98 디자인 9종 — crt 모니터 효과까지 복붙
이 데모가 속한 글
윈도우 98 디자인 9종 — crt 모니터 효과까지 복붙윈도우 98 디자인은 지금 웹에서도 가장 알아보기 쉬운 레트로 문법입니다. 여기 아홉 개는 그 시절 책상 위 화면을 통째로 옮긴 부품인데, 창을 끌고 최소화까지 진짜로 됩니다. 스캔라인이 흐르는 브라운관, 픽셀 커서 이 글의 데모 전부 보기


