05ソーシャルタイルフッター
ソーシャルアカウント4つが大きなタイルとして並び、順番に浮いては沈み、浮いたタイルのアイコンがアカウントの色に変わり、開く印が隅に現れます。
使いどころクリエイター・コミュニティサイトのフォロー誘導
translateYbox-shadow:focus-visible

<div class="stage">
<footer class="sc">
<p class="sc__head">フォロー</p>
<nav class="sc__grid" aria-label="ソーシャルアカウントのリンク">
<a class="sc__tile" href="#">
<span class="sc__ic">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path d="M176 32H80a48 48 0 0 0-48 48v96a48 48 0 0 0 48 48h96a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48m-48 136a40 40 0 1 1 40-40a40 40 0 0 1-40 40" opacity=".2"/><path d="M176 24H80a56.06 56.06 0 0 0-56 56v96a56.06 56.06 0 0 0 56 56h96a56.06 56.06 0 0 0 56-56V80a56.06 56.06 0 0 0-56-56m40 152a40 40 0 0 1-40 40H80a40 40 0 0 1-40-40V80a40 40 0 0 1 40-40h96a40 40 0 0 1 40 40Zm-88-96a48 48 0 1 0 48 48a48.05 48.05 0 0 0-48-48m0 80a32 32 0 1 1 32-32a32 32 0 0 1-32 32m64-84a12 12 0 1 1-12-12a12 12 0 0 1 12 12"/></g></svg>
</span>
<span class="sc__name">Instagram</span>
<span class="sc__go" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256"><g fill="currentColor"><path d="M192 64v104L88 64Z" opacity=".2"/><path d="M192 56H88a8 8 0 0 0-5.66 13.66L128.69 116l-70.35 70.34a8 8 0 0 0 11.32 11.32L140 127.31l46.34 46.35A8 8 0 0 0 200 168V64a8 8 0 0 0-8-8m-8 92.69l-38.34-38.34L107.31 72H184Z"/></g></svg>
</span>
</a>
<a class="sc__tile" href="#">
<span class="sc__ic">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path d="M208 216h-48L48 40h48Z" opacity=".2"/><path d="m214.75 211.71l-62.6-98.38l61.77-67.95a8 8 0 0 0-11.84-10.76l-58.84 64.72l-40.49-63.63A8 8 0 0 0 96 32H48a8 8 0 0 0-6.75 12.3l62.6 98.37l-61.77 68a8 8 0 1 0 11.84 10.76l58.84-64.72l40.49 63.63A8 8 0 0 0 160 224h48a8 8 0 0 0 6.75-12.29M164.39 208L62.57 48h29l101.86 160Z"/></g></svg>
</span>
<span class="sc__name">X</span>
<span class="sc__go" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256"><g fill="currentColor"><path d="M192 64v104L88 64Z" opacity=".2"/><path d="M192 56H88a8 8 0 0 0-5.66 13.66L128.69 116l-70.35 70.34a8 8 0 0 0 11.32 11.32L140 127.31l46.34 46.35A8 8 0 0 0 200 168V64a8 8 0 0 0-8-8m-8 92.69l-38.34-38.34L107.31 72H184Z"/></g></svg>
</span>
</a>
<a class="sc__tile" href="#">
<span class="sc__ic">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path d="M226.59 71.53a16 16 0 0 0-9.63-11C183.48 47.65 128 48 128 48s-55.48-.35-89 12.58a16 16 0 0 0-9.63 11C27.07 80.54 24 98.09 24 128s3.07 47.46 5.41 56.47A16 16 0 0 0 39 195.42C72.52 208.35 128 208 128 208s55.48.35 89-12.58a16 16 0 0 0 9.63-10.95c2.34-9 5.41-26.56 5.41-56.47s-3.11-47.46-5.45-56.47M112 160V96l48 32Z" opacity=".2"/><path d="m164.44 121.34l-48-32A8 8 0 0 0 104 96v64a8 8 0 0 0 12.44 6.66l48-32a8 8 0 0 0 0-13.32M120 145.05V111l25.58 17Zm114.33-75.53a24 24 0 0 0-14.49-16.4C185.56 39.88 131 40 128 40s-57.56-.12-91.84 13.12a24 24 0 0 0-14.49 16.4C19.08 79.5 16 97.74 16 128s3.08 48.5 5.67 58.48a24 24 0 0 0 14.49 16.41C69 215.56 120.4 216 127.34 216h1.32c6.94 0 58.37-.44 91.18-13.11a24 24 0 0 0 14.49-16.41c2.59-10 5.67-28.22 5.67-58.48s-3.08-48.5-5.67-58.48m-15.49 113a8 8 0 0 1-4.77 5.49c-31.65 12.22-85.48 12-86.12 12s-54.37.18-86-12a8 8 0 0 1-4.77-5.49C34.8 173.39 32 156.57 32 128s2.8-45.39 5.16-54.47A8 8 0 0 1 41.93 68c31.65-12.18 85.47-12 86.12-12s54.37-.18 86 12a8 8 0 0 1 4.77 5.49C221.2 82.61 224 99.43 224 128s-2.8 45.39-5.16 54.47Z"/></g></svg>
</span>
<span class="sc__name">YouTube</span>
<span class="sc__go" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256"><g fill="currentColor"><path d="M192 64v104L88 64Z" opacity=".2"/><path d="M192 56H88a8 8 0 0 0-5.66 13.66L128.69 116l-70.35 70.34a8 8 0 0 0 11.32 11.32L140 127.31l46.34 46.35A8 8 0 0 0 200 168V64a8 8 0 0 0-8-8m-8 92.69l-38.34-38.34L107.31 72H184Z"/></g></svg>
</span>
</a>
<a class="sc__tile" href="#">
<span class="sc__ic">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path d="M208 104v8a48 48 0 0 1-48 48h-24a32 32 0 0 1 32 32v40h-64v-40a32 32 0 0 1 32-32h-24a48 48 0 0 1-48-48v-8a49.3 49.3 0 0 1 8.51-27.3A51.92 51.92 0 0 1 76 32a52 52 0 0 1 43.83 24h32.34A52 52 0 0 1 196 32a51.92 51.92 0 0 1 3.49 44.7A49.3 49.3 0 0 1 208 104" opacity=".2"/><path d="M208.3 75.68A59.74 59.74 0 0 0 202.93 28a8 8 0 0 0-6.93-4a59.75 59.75 0 0 0-48 24h-24a59.75 59.75 0 0 0-48-24a8 8 0 0 0-6.93 4a59.78 59.78 0 0 0-5.38 47.68A58.14 58.14 0 0 0 56 104v8a56.06 56.06 0 0 0 48.44 55.47A39.8 39.8 0 0 0 96 192v8H72a24 24 0 0 1-24-24a40 40 0 0 0-40-40a8 8 0 0 0 0 16a24 24 0 0 1 24 24a40 40 0 0 0 40 40h24v16a8 8 0 0 0 16 0v-40a24 24 0 0 1 48 0v40a8 8 0 0 0 16 0v-40a39.8 39.8 0 0 0-8.44-24.53A56.06 56.06 0 0 0 216 112v-8a58 58 0 0 0-7.7-28.32M200 112a40 40 0 0 1-40 40h-48a40 40 0 0 1-40-40v-8a41.74 41.74 0 0 1 6.9-22.48a8 8 0 0 0 1.1-7.69a43.8 43.8 0 0 1 .79-33.58a43.88 43.88 0 0 1 32.32 20.06a8 8 0 0 0 6.71 3.69h32.35a8 8 0 0 0 6.74-3.69a43.87 43.87 0 0 1 32.32-20.06a43.8 43.8 0 0 1 .77 33.58a8.09 8.09 0 0 0 1 7.65a41.76 41.76 0 0 1 7 22.52Z"/></g></svg>
</span>
<span class="sc__name">GitHub</span>
<span class="sc__go" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 256 256"><g fill="currentColor"><path d="M192 64v104L88 64Z" opacity=".2"/><path d="M192 56H88a8 8 0 0 0-5.66 13.66L128.69 116l-70.35 70.34a8 8 0 0 0 11.32 11.32L140 127.31l46.34 46.35A8 8 0 0 0 200 168V64a8 8 0 0 0-8-8m-8 92.69l-38.34-38.34L107.31 72H184Z"/></g></svg>
</span>
</a>
</nav>
<p class="sc__note">アカウントごとに違うお知らせが届きます</p>
</footer>
</div>@use "../../../../site/static/demo-tokens" as *;
// 편 272 푸터 팔레트 — 04·05·06 이 같은 값을 쓴다.
// 대비 실측(WCAG 2.1): ink on flame 5.21:1 · ink on paper 17.28:1 · stone on paper 7.61:1 · teal on paper 5.79:1
// ⛔ 흰 글자 on flame 은 3.32:1 이라 이 무대에서는 흰 글자를 주황 위에 얹지 않는다
$ft-ink: #1b1a20;
$ft-ink2: #2c2a33;
$ft-paper: #ffffff;
$ft-cream: #fbf6ee;
$ft-stone: #575162;
$ft-mist: #b9b3c4;
$ft-amber: $stage-yellow;
$ft-flame: $stage-orange;
$ft-teal: #479089;
$ft-aqua: #b3dad5;
// 계정 색 — 아이콘(비텍스트)에만 쓴다. 흰 타일 위 대비 실측:
// 인스타 #c3869b 4.34:1 · X #0f0f0f 19.3:1 · 유튜브 #bd7a7a 4.00:1 · 깃허브 #181717 17.0:1 (기준 3:1)
$br-instagram: #c3869b;
$br-x: #0f0f0f;
$br-youtube: #bd7a7a;
$br-github: #181717;
$duration: $dur-loop; // 타일 넷이 차례로 한 번씩 들리는 주기
$easing: $ease-pop; // 들릴 때만 살짝 튕긴다
$color: $ft-paper; // 타일 면 색
@include stage($ft-flame);
.sc {
box-sizing: border-box;
width: clamp(268px, 88vw, 408px);
display: flex;
flex-direction: column;
gap: $sp-2;
}
.sc__head {
margin: 0;
color: $ft-ink; // 주황 위 잉크 5.21:1
font-weight: 800; font-size: 14px; line-height: 1.2;
letter-spacing: .04em;
font-family: inherit;
}
.sc__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $sp-3;
}
.sc__tile {
--brand: #{$br-instagram};
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: $sp-2;
min-height: 88px;
padding: $sp-4;
border-radius: $r-card;
background: $color;
color: $ft-ink;
text-decoration: none;
box-shadow: $shadow-press;
transform: translateY(0);
transition: transform $dur-quick $easing, box-shadow $dur-quick $ease-out;
}
.sc__tile:nth-child(1) { --brand: #{$br-instagram}; }
.sc__tile:nth-child(2) { --brand: #{$br-x}; }
.sc__tile:nth-child(3) { --brand: #{$br-youtube}; }
.sc__tile:nth-child(4) { --brand: #{$br-github}; }
// 정적 경로를 먼저 쓴다 — .is-demo 를 떼도 호버·포커스로 같은 동작이 그대로 남는다
.sc__tile:hover,
.sc__tile:focus-visible {
transform: translateY(-6px);
box-shadow: $shadow-raised;
}
.sc__tile:hover .sc__ic,
.sc__tile:focus-visible .sc__ic { color: var(--brand); }
.sc__tile:hover .sc__go,
.sc__tile:focus-visible .sc__go { transform: scale(1); }
.sc__tile:focus-visible {
outline: 3px solid $ft-ink;
outline-offset: 2px;
}
.sc__ic {
display: block;
color: $ft-stone; // 흰 타일 위 7.61:1
transition: color $dur-quick $ease-out;
}
.sc__ic svg { display: block; }
.sc__name {
font-weight: 700; font-size: 14px; line-height: 1;
font-family: inherit;
}
// 모서리의 "열림" 표시 — scale(0) 이라 쉴 때는 픽셀이 아예 없다.
// opacity 로 숨기면 중간 알파 프레임이 poster 에 유령으로 찍힌다
.sc__go {
position: absolute;
top: 8px;
right: 8px;
display: block;
color: $ft-teal; // 흰 타일 위 5.79:1
transform: scale(0);
transform-origin: top right; // 규칙8: 모서리 표시 꼭짓점서 펼침
transition: transform $dur-quick $easing;
}
.sc__go svg { display: block; }
.sc__note {
margin: 0;
color: $ft-ink;
font-weight: 500; font-size: 12px; line-height: 1.3;
font-family: inherit;
}
// ── 자동 재생 ───────────────────────────────────────────────
// 네 타일이 500ms 씩 차례로 들렸다 앉는다. 시차는 음수 delay 로 준다 —
// animation 단축을 쓰면 delay 가 0 으로 리셋된다
.is-demo .sc__tile {
animation-name: tileLift;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.is-demo .sc__tile .sc__ic {
animation-name: tileIcon;
animation-duration: $duration;
animation-timing-function: steps(1, end); // 색 교체는 한 프레임 컷
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.is-demo .sc__tile .sc__go {
animation-name: tileGo;
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.is-demo .sc__tile:nth-child(2),
.is-demo .sc__tile:nth-child(2) .sc__ic,
.is-demo .sc__tile:nth-child(2) .sc__go { animation-delay: -1.5s; }
.is-demo .sc__tile:nth-child(3),
.is-demo .sc__tile:nth-child(3) .sc__ic,
.is-demo .sc__tile:nth-child(3) .sc__go { animation-delay: -1s; }
.is-demo .sc__tile:nth-child(4),
.is-demo .sc__tile:nth-child(4) .sc__ic,
.is-demo .sc__tile:nth-child(4) .sc__go { animation-delay: -.5s; }
// 들림은 빠르게(0~5%), 앉음은 느리게(16~25%) 둔다.
// poster 는 "앞 장과 가장 크게 달라진 장" 이라 앉는 쪽이 더 빠르면 아무것도 안 들린 장이 대표가 된다
@keyframes tileLift {
0% { transform: translateY(0); box-shadow: $shadow-press; }
5%, 16% { transform: translateY(-6px); box-shadow: $shadow-raised; }
25%, 100% { transform: translateY(0); box-shadow: $shadow-press; }
}
@keyframes tileIcon {
0%, 19.9% { color: var(--brand); }
20%, 100% { color: $ft-stone; }
}
@keyframes tileGo {
0% { transform: scale(0); }
5%, 16% { transform: scale(1); }
25%, 100% { transform: scale(0); }
}
@media (max-width: 340px), (max-height: 220px) {
.sc { width: 268px; gap: $sp-1; }
.sc__head { font-size: 12px; }
.sc__grid { gap: $sp-2; }
.sc__tile { min-height: 56px; gap: $sp-1; padding: $sp-2; }
.sc__ic svg { width: 20px; height: 20px; }
.sc__name { font-size: 11px; }
.sc__go { top: 4px; right: 4px; }
.sc__go svg { width: 12px; height: 12px; }
.sc__note { font-size: 10px; }
}
@media (prefers-reduced-motion: reduce) {
// .is-demo 쪽 선택자가 (0,3,0) 이라 !important 없이는 진다 (260 실측)
.is-demo .sc__tile,
.is-demo .sc__tile .sc__ic,
.is-demo .sc__tile .sc__go { animation: none !important; }
.sc__tile, .sc__ic, .sc__go { transition: none !important; }
// 움직임을 끊어도 어느 타일이 열리는 링크인지는 남는다
.sc__tile:hover .sc__go,
.sc__tile:focus-visible .sc__go { transform: scale(1); }
}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);
}
.sc {
box-sizing: border-box;
width: clamp(268px, 88vw, 408px);
display: flex;
flex-direction: column;
gap: 8px;
}
.sc__head {
margin: 0;
color: #1b1a20;
font-weight: 800;
font-size: 14px;
line-height: 1.2;
letter-spacing: 0.04em;
font-family: inherit;
}
.sc__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.sc__tile {
--brand: #c3869b;
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 8px;
min-height: 88px;
padding: 16px;
border-radius: 16px;
background: #ffffff;
color: #1b1a20;
text-decoration: none;
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
transform: translateY(0);
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1), box-shadow 220ms ease-out;
}
.sc__tile:nth-child(1) {
--brand: #c3869b;
}
.sc__tile:nth-child(2) {
--brand: #0f0f0f;
}
.sc__tile:nth-child(3) {
--brand: #bd7a7a;
}
.sc__tile:nth-child(4) {
--brand: #181717;
}
.sc__tile:hover,
.sc__tile:focus-visible {
transform: translateY(-6px);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.sc__tile:hover .sc__ic,
.sc__tile:focus-visible .sc__ic {
color: var(--brand);
}
.sc__tile:hover .sc__go,
.sc__tile:focus-visible .sc__go {
transform: scale(1);
}
.sc__tile:focus-visible {
outline: 3px solid #1b1a20;
outline-offset: 2px;
}
.sc__ic {
display: block;
color: #575162;
transition: color 220ms ease-out;
}
.sc__ic svg {
display: block;
}
.sc__name {
font-weight: 700;
font-size: 14px;
line-height: 1;
font-family: inherit;
}
.sc__go {
position: absolute;
top: 8px;
right: 8px;
display: block;
color: #479089;
transform: scale(0);
transform-origin: top right;
transition: transform 220ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
.sc__go svg {
display: block;
}
.sc__note {
margin: 0;
color: #1b1a20;
font-weight: 500;
font-size: 12px;
line-height: 1.3;
font-family: inherit;
}
.is-demo .sc__tile {
animation-name: tileLift;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.is-demo .sc__tile .sc__ic {
animation-name: tileIcon;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.is-demo .sc__tile .sc__go {
animation-name: tileGo;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 1.4, 0.4, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.is-demo .sc__tile:nth-child(2),
.is-demo .sc__tile:nth-child(2) .sc__ic,
.is-demo .sc__tile:nth-child(2) .sc__go {
animation-delay: -1.5s;
}
.is-demo .sc__tile:nth-child(3),
.is-demo .sc__tile:nth-child(3) .sc__ic,
.is-demo .sc__tile:nth-child(3) .sc__go {
animation-delay: -1s;
}
.is-demo .sc__tile:nth-child(4),
.is-demo .sc__tile:nth-child(4) .sc__ic,
.is-demo .sc__tile:nth-child(4) .sc__go {
animation-delay: -0.5s;
}
@keyframes tileLift {
0% {
transform: translateY(0);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
5%, 16% {
transform: translateY(-6px);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
25%, 100% {
transform: translateY(0);
box-shadow: 0 6px 12px rgba(23, 20, 26, 0.32);
}
}
@keyframes tileIcon {
0%, 19.9% {
color: var(--brand);
}
20%, 100% {
color: #575162;
}
}
@keyframes tileGo {
0% {
transform: scale(0);
}
5%, 16% {
transform: scale(1);
}
25%, 100% {
transform: scale(0);
}
}
@media (max-width: 340px), (max-height: 220px) {
.sc {
width: 268px;
gap: 4px;
}
.sc__head {
font-size: 12px;
}
.sc__grid {
gap: 8px;
}
.sc__tile {
min-height: 56px;
gap: 4px;
padding: 8px;
}
.sc__ic svg {
width: 20px;
height: 20px;
}
.sc__name {
font-size: 11px;
}
.sc__go {
top: 4px;
right: 4px;
}
.sc__go svg {
width: 12px;
height: 12px;
}
.sc__note {
font-size: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.is-demo .sc__tile,
.is-demo .sc__tile .sc__ic,
.is-demo .sc__tile .sc__go {
animation: none !important;
}
.sc__tile, .sc__ic, .sc__go {
transition: none !important;
}
.sc__tile:hover .sc__go,
.sc__tile:focus-visible .sc__go {
transform: scale(1);
}
}(function () {
var body = document.body;
var grid = document.querySelector('.sc__grid');
var tiles = Array.prototype.slice.call(grid.querySelectorAll('.sc__tile'));
function hand() { body.classList.remove('is-demo'); } // 손이 닿으면 자동 재생을 끈다
// 2×2 격자라 좌우는 ±1, 상하는 ±2 칸 — 화살표 네 방향이 전부 산다
grid.addEventListener('keydown', function (e) {
var step = { ArrowRight: 1, ArrowLeft: -1, ArrowDown: 2, ArrowUp: -2 }[e.key];
if (!step) return;
e.preventDefault();
hand();
var i = tiles.indexOf(document.activeElement);
var next = (i < 0 ? 0 : i + step + tiles.length) % tiles.length;
tiles[next].focus({ preventScroll: true });
});
grid.addEventListener('pointerdown', hand);
grid.addEventListener('pointerover', hand);
grid.addEventListener('focusin', hand);
})();import { useRef, useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import styles from "./FooterSocialTiles.module.scss";
/* ambient 에 React 의 KeyboardEvent 가 없어 필요한 것만 로컬로 선언한다 */
type KeyEvt = { key: string; preventDefault(): void };
export type FooterSocialTilesProps = {
/** 타일 넷이 차례로 한 번씩 들리는 주기(ms) */
duration?: number;
easing?: string;
/** 타일 면 색 */
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
type TileVars = CSSProperties & { "--brand"?: string };
/* Phosphor Duotone (MIT). 계정 색은 아이콘(비텍스트)에만 쓴다 —
흰 타일 위 대비 실측: #e1306c 4.34:1 · #0f0f0f 19.3:1 · #ff0000 4.00:1 · #181717 17.0:1 (기준 3:1) */
const TILES: { name: string; brand: string; icon: ReactNode }[] = [
{
name: "Instagram", brand: "#e1306c",
icon: <svg width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path opacity=".2" d="M176 32H80a48 48 0 0 0-48 48v96a48 48 0 0 0 48 48h96a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48m-48 136a40 40 0 1 1 40-40a40 40 0 0 1-40 40" /><path d="M176 24H80a56.06 56.06 0 0 0-56 56v96a56.06 56.06 0 0 0 56 56h96a56.06 56.06 0 0 0 56-56V80a56.06 56.06 0 0 0-56-56m40 152a40 40 0 0 1-40 40H80a40 40 0 0 1-40-40V80a40 40 0 0 1 40-40h96a40 40 0 0 1 40 40Zm-88-96a48 48 0 1 0 48 48a48.05 48.05 0 0 0-48-48m0 80a32 32 0 1 1 32-32a32 32 0 0 1-32 32m64-84a12 12 0 1 1-12-12a12 12 0 0 1 12 12" /></g></svg>,
},
{
name: "X", brand: "#0f0f0f",
icon: <svg width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path opacity=".2" d="M208 216h-48L48 40h48Z" /><path d="m214.75 211.71l-62.6-98.38l61.77-67.95a8 8 0 0 0-11.84-10.76l-58.84 64.72l-40.49-63.63A8 8 0 0 0 96 32H48a8 8 0 0 0-6.75 12.3l62.6 98.37l-61.77 68a8 8 0 1 0 11.84 10.76l58.84-64.72l40.49 63.63A8 8 0 0 0 160 224h48a8 8 0 0 0 6.75-12.29M164.39 208L62.57 48h29l101.86 160Z" /></g></svg>,
},
{
name: "YouTube", brand: "#ff0000",
icon: <svg width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path opacity=".2" d="M226.59 71.53a16 16 0 0 0-9.63-11C183.48 47.65 128 48 128 48s-55.48-.35-89 12.58a16 16 0 0 0-9.63 11C27.07 80.54 24 98.09 24 128s3.07 47.46 5.41 56.47A16 16 0 0 0 39 195.42C72.52 208.35 128 208 128 208s55.48.35 89-12.58a16 16 0 0 0 9.63-10.95c2.34-9 5.41-26.56 5.41-56.47s-3.11-47.46-5.45-56.47M112 160V96l48 32Z" /><path d="m164.44 121.34l-48-32A8 8 0 0 0 104 96v64a8 8 0 0 0 12.44 6.66l48-32a8 8 0 0 0 0-13.32M120 145.05V111l25.58 17Zm114.33-75.53a24 24 0 0 0-14.49-16.4C185.56 39.88 131 40 128 40s-57.56-.12-91.84 13.12a24 24 0 0 0-14.49 16.4C19.08 79.5 16 97.74 16 128s3.08 48.5 5.67 58.48a24 24 0 0 0 14.49 16.41C69 215.56 120.4 216 127.34 216h1.32c6.94 0 58.37-.44 91.18-13.11a24 24 0 0 0 14.49-16.41c2.59-10 5.67-28.22 5.67-58.48s-3.08-48.5-5.67-58.48m-15.49 113a8 8 0 0 1-4.77 5.49c-31.65 12.22-85.48 12-86.12 12s-54.37.18-86-12a8 8 0 0 1-4.77-5.49C34.8 173.39 32 156.57 32 128s2.8-45.39 5.16-54.47A8 8 0 0 1 41.93 68c31.65-12.18 85.47-12 86.12-12s54.37-.18 86 12a8 8 0 0 1 4.77 5.49C221.2 82.61 224 99.43 224 128s-2.8 45.39-5.16 54.47Z" /></g></svg>,
},
{
name: "GitHub", brand: "#181717",
icon: <svg width="28" height="28" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path opacity=".2" d="M208 104v8a48 48 0 0 1-48 48h-24a32 32 0 0 1 32 32v40h-64v-40a32 32 0 0 1 32-32h-24a48 48 0 0 1-48-48v-8a49.3 49.3 0 0 1 8.51-27.3A51.92 51.92 0 0 1 76 32a52 52 0 0 1 43.83 24h32.34A52 52 0 0 1 196 32a51.92 51.92 0 0 1 3.49 44.7A49.3 49.3 0 0 1 208 104" /><path d="M208.3 75.68A59.74 59.74 0 0 0 202.93 28a8 8 0 0 0-6.93-4a59.75 59.75 0 0 0-48 24h-24a59.75 59.75 0 0 0-48-24a8 8 0 0 0-6.93 4a59.78 59.78 0 0 0-5.38 47.68A58.14 58.14 0 0 0 56 104v8a56.06 56.06 0 0 0 48.44 55.47A39.8 39.8 0 0 0 96 192v8H72a24 24 0 0 1-24-24a40 40 0 0 0-40-40a8 8 0 0 0 0 16a24 24 0 0 1 24 24a40 40 0 0 0 40 40h24v16a8 8 0 0 0 16 0v-40a24 24 0 0 1 48 0v40a8 8 0 0 0 16 0v-40a39.8 39.8 0 0 0-8.44-24.53A56.06 56.06 0 0 0 216 112v-8a58 58 0 0 0-7.7-28.32M200 112a40 40 0 0 1-40 40h-48a40 40 0 0 1-40-40v-8a41.74 41.74 0 0 1 6.9-22.48a8 8 0 0 0 1.1-7.69a43.8 43.8 0 0 1 .79-33.58a43.88 43.88 0 0 1 32.32 20.06a8 8 0 0 0 6.71 3.69h32.35a8 8 0 0 0 6.74-3.69a43.87 43.87 0 0 1 32.32-20.06a43.8 43.8 0 0 1 .77 33.58a8.09 8.09 0 0 0 1 7.65a41.76 41.76 0 0 1 7 22.52Z" /></g></svg>,
},
];
const GO: ReactNode = (
<svg width="14" height="14" viewBox="0 0 256 256" aria-hidden="true"><g fill="currentColor"><path opacity=".2" d="M192 64v104L88 64Z" /><path d="M192 56H88a8 8 0 0 0-5.66 13.66L128.69 116l-70.35 70.34a8 8 0 0 0 11.32 11.32L140 127.31l46.34 46.35A8 8 0 0 0 200 168V64a8 8 0 0 0-8-8m-8 92.69l-38.34-38.34L107.31 72H184Z" /></g></svg>
);
export default function FooterSocialTiles({
duration = 2000,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#ffffff",
}: FooterSocialTilesProps): ReactNode {
const [demo, setDemo] = useState(true); // 손이 닿으면 자동 재생을 끈다
const gridRef = useRef<HTMLElement>(null);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
/* 2×2 격자라 좌우는 ±1, 상하는 ±2 칸 — 화살표 네 방향이 전부 산다 */
const onKey = (e: KeyEvt) => {
const step = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1
: e.key === "ArrowDown" ? 2 : e.key === "ArrowUp" ? -2 : 0;
if (!step) return;
e.preventDefault();
setDemo(false);
const grid = gridRef.current;
if (!grid) return;
const list = grid.querySelectorAll("a");
const now = Array.prototype.indexOf.call(list, document.activeElement);
const next = ((now < 0 ? 0 : now + step) + list.length) % list.length;
const el = list[next] as HTMLElement | undefined;
if (el) el.focus({ preventScroll: true });
};
return (
<footer className={`${styles.sc} ${demo ? styles.isDemo : ""}`} style={vars}>
<p className={styles.head}>팔로우</p>
<nav
ref={gridRef}
className={styles.grid}
aria-label="소셜 계정 링크"
onKeyDown={onKey}
onPointerDown={() => setDemo(false)}
onPointerOver={() => setDemo(false)}
>
{TILES.map((t) => {
const tv: TileVars = { "--brand": t.brand };
return (
<a key={t.name} className={styles.tile} href="#" style={tv}>
<span className={styles.ic}>{t.icon}</span>
<span className={styles.name}>{t.name}</span>
<span className={styles.go} aria-hidden="true">{GO}</span>
</a>
);
})}
</nav>
<p className={styles.note}>계정마다 다른 소식이 올라옵니다</p>
</footer>
);
}05 ソーシャルタイルフッター — 計測 累積面積 13.894% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · フッター デザイン コピペで終わるパターン9選
このデモが属する記事
フッター デザイン コピペで終わるパターン9選フッター デザイン コピペで探しているのは、ページの一番下でサイト全体を締めくくる区画のひな形です。ここではリンクの一覧で終わらせない九つのフッターを、そのまま貼れるHTMLとSCSSで載せました。サイトマップから波の境目まで、すべて画面の この記事のデモをすべて見る


