03Three pixel cursors
Arrow, hourglass and pointing hand live as small grid drawings, and the pixel edges stay hard instead of blurring when scaled up. On the stage the three swap in turn while the hourglass flips in place.
Best forTheme page that swaps the cursor too
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="Cursor preview">
<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="Pick a cursor">
<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">Arrow</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">Hourglass</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">Hand</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 Three pixel cursors — Measured area 9.179% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · Windows 98 CSS: 9 Retro OS Parts, No Library
The post this demo lives in
Windows 98 CSS: 9 Retro OS Parts, No LibraryWindows 98 CSS rebuilds a 1998 desktop out of one stylesheet and a little script — nine parts where the windows really d See all demos in this post


