08軌道オーブフッター
フッター上部の区切り線に沿ってミニオーブ3個がそれぞれ違う速度で横切っていき(animation-delay)、リンクはホバーで明るくなります。4列は狭い画面で1列に折りたたまれます。
使いどころマルチカラムフッター・サイトの締め
translateXanimation-delaygrid-template-columns

<main class="stage">
<footer class="fo">
<!-- 구분선 위를 지나가는 미니 오브 셋 — 장식이라 낭독기에서 숨긴다 -->
<div class="fo__rail" aria-hidden="true">
<span class="fo__beacon fo__beacon--slow"><span class="fo__dot fo__dot--lilac"></span></span>
<span class="fo__beacon fo__beacon--fast"><span class="fo__dot fo__dot--mint"></span></span>
<span class="fo__beacon fo__beacon--drift"><span class="fo__dot fo__dot--blue"></span></span>
</div>
<div class="fo__cols">
<section class="fo__col">
<h2 class="fo__head">プロダクト</h2>
<ul class="fo__links">
<li><a href="#">機能</a></li>
<li><a href="#">料金プラン</a></li>
<li><a href="#">変更履歴</a></li>
</ul>
</section>
<section class="fo__col">
<h2 class="fo__head">ドキュメント</h2>
<ul class="fo__links">
<li><a href="#">クイックスタート</a></li>
<li><a href="#">API</a></li>
<li><a href="#">サンプル</a></li>
</ul>
</section>
<section class="fo__col">
<h2 class="fo__head">会社</h2>
<ul class="fo__links">
<li><a href="#">会社概要</a></li>
<li><a href="#">採用</a></li>
<li><a href="#">お問い合わせ</a></li>
</ul>
</section>
<section class="fo__col">
<h2 class="fo__head">コミュニティ</h2>
<ul class="fo__links">
<li><a href="#">Discord</a></li>
<li><a href="#">ブログ</a></li>
<li><a href="#">ステータス</a></li>
</ul>
</section>
</div>
<p class="fo__copy">
<span>© 2026 Orbit Labs</span>
<span class="fo__lang">
<span class="fo__ico" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M213.09,172.48a96,96,0,0,1-80.41,51.41l3.17-16.44a8,8,0,0,0-2-6.95l-19.74-20.33a8,8,0,0,1-1.44-8.69l13.7-30.74a8,8,0,0,1,8.38-4.67l22.82,3.08a8.11,8.11,0,0,1,3.12,1.11ZM116.71,95,129,88.24a7.46,7.46,0,0,0,1.5-1.07l26.91-24.33A8,8,0,0,0,159,53l-10.5-18.81A96.62,96.62,0,0,0,128,32,95.61,95.61,0,0,0,67.78,53.23L56,81.08A8,8,0,0,0,55.88,87l11.5,30.67a8,8,0,0,0,5.81,5l2.69.58L89.2,100a8,8,0,0,1,6.94-4h16.71A7.9,7.9,0,0,0,116.71,95Z" opacity="0.2"/><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm88,104a87.62,87.62,0,0,1-6.4,32.94l-44.7-27.49a15.92,15.92,0,0,0-6.24-2.23l-22.82-3.08a16.11,16.11,0,0,0-16,7.86h-8.72l-3.8-7.86a15.91,15.91,0,0,0-11-8.67l-8-1.73L96.14,104h16.71a16.06,16.06,0,0,0,7.73-2l12.25-6.76a16.62,16.62,0,0,0,3-2.14l26.91-24.34A15.93,15.93,0,0,0,166,49.1l-.36-.65A88.11,88.11,0,0,1,216,128ZM143.31,41.34,152,56.9,125.09,81.24,112.85,88H96.14a16,16,0,0,0-13.88,8l-8.73,15.23L63.38,84.19,74.32,58.32a87.87,87.87,0,0,1,69-17ZM40,128a87.53,87.53,0,0,1,8.54-37.8l11.34,30.27a16,16,0,0,0,11.62,10l21.43,4.61L96.74,143a16.09,16.09,0,0,0,14.4,9h1.48l-7.23,16.23a16,16,0,0,0,2.86,17.37l.14.14L128,205.94l-1.94,10A88.11,88.11,0,0,1,40,128Zm102.58,86.78,1.13-5.81a16.09,16.09,0,0,0-4-13.9,1.85,1.85,0,0,1-.14-.14L120,174.74,133.7,144l22.82,3.08,45.72,28.12A88.18,88.18,0,0,1,142.58,214.78Z"/></svg></span>
日本語
</span>
</p>
</footer>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 느린 오브가 구분선을 한 번 건너는 시간 (빠른 쪽은 이것의 절반)
$easing: linear; // 지나가는 빛은 가속하지 않는다
$color: $subject-lilac; // 첫 오브 색
@include stage($stage-paper);
.fo {
position: relative; overflow: hidden; // 오브가 푸터 밖으로 새지 않게
box-sizing: border-box; width: min(100%, 440px);
background: $subject-ink; border-radius: $r-card; box-shadow: $shadow-raised;
padding: $sp-4 $sp-5 $sp-4;
color: $subject-cream;
}
// ── 상단 구분선 = 오브가 지나가는 궤도
.fo__rail {
position: relative; height: 2px; border-radius: $r-pill;
background: rgba($subject-cream, .18);
}
// 캐리어. 폭이 궤도와 같아서 translateX 의 % 가 곧 "궤도의 몇 %" 가 된다
// (점 자체에 % 를 주면 20·14·24px 짜리 점 자기 폭의 % 라 궤도를 못 건넌다)
.fo__beacon {
position: absolute; left: 0; top: 50%; width: 100%; height: 0;
animation-name: beaconCross;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards; // 지연이 걸린 루프 — 없으면 첫 프레임에 기본 상태가 번쩍인다
}
.fo__beacon--slow { animation-duration: $duration; animation-delay: -400ms; }
.fo__beacon--fast { animation-duration: $dur-loop * .5; animation-delay: -500ms; }
.fo__beacon--drift { animation-duration: $duration; animation-delay: -1200ms; }
// 음수 지연으로 셋의 위상을 20% · 50% · 62% 로 벌려 둔다 — 어느 순간에도 최소 하나는 궤도 안에 있다
// (poster 가 빈 구분선이 되지 않게)
.fo__dot {
position: absolute; left: 0; top: 0; transform: translate(-50%, -50%);
width: 20px; height: 20px; border-radius: 50%;
}
.fo__dot--lilac { background: radial-gradient(circle at 50% 50%, rgba($color, .92) 0%, rgba($color, .34) 42%, rgba($color, 0) 72%); }
.fo__dot--mint { width: 14px; height: 14px;
background: radial-gradient(circle at 50% 50%, rgba($subject-mint, .92) 0%, rgba($subject-mint, .34) 42%, rgba($subject-mint, 0) 72%); }
.fo__dot--blue { width: 24px; height: 24px;
background: radial-gradient(circle at 50% 50%, rgba($subject-sky, .88) 0%, rgba($subject-blue, .34) 42%, rgba($subject-blue, 0) 72%); }
@keyframes beaconCross {
from { transform: translateX(-8%); }
to { transform: translateX(108%); }
}
// ── 네 열
.fo__cols {
display: grid; grid-template-columns: repeat(4, 1fr); gap: $sp-4 $sp-3;
margin-top: $sp-4;
}
.fo__col { min-width: 0; }
.fo__head {
margin: 0; font-weight: 800; font-size: clamp(9px, 2.6vw, 10px); line-height: 1;
letter-spacing: .03em; color: $subject-cream; // 17.11:1
}
.fo__links { list-style: none; margin: $sp-2 0 0; padding: 0; }
.fo__links li { margin-top: $sp-1; }
.fo__links li:first-child { margin-top: 0; }
.fo__links a {
font-weight: 600; font-size: clamp(8px, 2.4vw, 10px); line-height: 1.3;
color: rgba($subject-cream, .72); // 9.18:1
text-decoration: none; transition: color $dur-quick $ease-out;
}
.fo__links a:hover,
.fo__links a:focus-visible { color: $subject-cream; }
.fo__copy {
display: flex; align-items: center; justify-content: space-between; gap: $sp-2;
margin: $sp-4 0 0; padding-top: $sp-3;
border-top: 1px solid rgba($subject-cream, .14);
font-weight: 600; font-size: clamp(7px, 2.2vw, 9px); line-height: 1;
color: rgba($subject-cream, .62); // 7.12:1
}
.fo__lang { display: inline-flex; align-items: center; gap: $sp-1; }
.fo__ico { display: inline-flex; width: 12px; height: 12px; }
.fo__ico svg { width: 100%; height: 100%; }
// ── 320×200 폰 실측 대비 압축 (BD9). 네 열이 한 열로 접힌다 — 이 블록 안에서는 간격을 올리지 않는다
@media (max-width: 340px), (max-height: 220px) {
.fo { padding: $sp-3; }
.fo__cols { grid-template-columns: 1fr; gap: $sp-2; margin-top: $sp-3; }
// 한 열이 되면 제목과 링크를 한 줄에 눕혀 네 덩어리가 폰 높이 안에 들어오게 한다
.fo__col { display: flex; align-items: baseline; gap: $sp-2; }
.fo__head { font-size: 8px; flex: 0 0 auto; }
.fo__links { display: flex; flex-wrap: wrap; gap: $sp-2; margin-top: 0; }
.fo__links li { margin-top: 0; }
.fo__links a { font-size: 8px; }
.fo__dot { width: 14px; height: 14px; }
.fo__dot--mint { width: 10px; height: 10px; }
.fo__dot--blue { width: 16px; height: 16px; }
.fo__copy { margin-top: $sp-2; padding-top: $sp-2; font-size: 7px; }
.fo__ico { width: 10px; height: 10px; }
}
@media (prefers-reduced-motion: reduce) {
.fo__beacon { animation: none !important; }
.fo__beacon--slow { transform: translateX(20%) !important; }
.fo__beacon--fast { transform: translateX(50%) !important; }
.fo__beacon--drift { transform: translateX(62%) !important; }
.fo__links a { transition: none !important; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.fo {
position: relative;
overflow: hidden;
box-sizing: border-box;
width: min(100%, 440px);
background: #17141a;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
padding: 16px 20px 16px;
color: #fff7e6;
}
.fo__rail {
position: relative;
height: 2px;
border-radius: 999px;
background: rgba(255, 247, 230, 0.18);
}
.fo__beacon {
position: absolute;
left: 0;
top: 50%;
width: 100%;
height: 0;
animation-name: beaconCross;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.fo__beacon--slow {
animation-duration: 2s;
animation-delay: -400ms;
}
.fo__beacon--fast {
animation-duration: 1s;
animation-delay: -500ms;
}
.fo__beacon--drift {
animation-duration: 2s;
animation-delay: -1200ms;
}
.fo__dot {
position: absolute;
left: 0;
top: 0;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border-radius: 50%;
}
.fo__dot--lilac {
background: radial-gradient(circle at 50% 50%, rgba(185, 160, 255, 0.92) 0%, rgba(185, 160, 255, 0.34) 42%, rgba(185, 160, 255, 0) 72%);
}
.fo__dot--mint {
width: 14px;
height: 14px;
background: radial-gradient(circle at 50% 50%, rgba(76, 212, 166, 0.92) 0%, rgba(76, 212, 166, 0.34) 42%, rgba(76, 212, 166, 0) 72%);
}
.fo__dot--blue {
width: 24px;
height: 24px;
background: radial-gradient(circle at 50% 50%, rgba(110, 200, 255, 0.88) 0%, rgba(47, 109, 246, 0.34) 42%, rgba(47, 109, 246, 0) 72%);
}
@keyframes beaconCross {
from {
transform: translateX(-8%);
}
to {
transform: translateX(108%);
}
}
.fo__cols {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px 12px;
margin-top: 16px;
}
.fo__col {
min-width: 0;
}
.fo__head {
margin: 0;
font-weight: 800;
font-size: clamp(9px, 2.6vw, 10px);
line-height: 1;
letter-spacing: 0.03em;
color: #fff7e6;
}
.fo__links {
list-style: none;
margin: 8px 0 0;
padding: 0;
}
.fo__links li {
margin-top: 4px;
}
.fo__links li:first-child {
margin-top: 0;
}
.fo__links a {
font-weight: 600;
font-size: clamp(8px, 2.4vw, 10px);
line-height: 1.3;
color: rgba(255, 247, 230, 0.72);
text-decoration: none;
transition: color 220ms ease-out;
}
.fo__links a:hover,
.fo__links a:focus-visible {
color: #fff7e6;
}
.fo__copy {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin: 16px 0 0;
padding-top: 12px;
border-top: 1px solid rgba(255, 247, 230, 0.14);
font-weight: 600;
font-size: clamp(7px, 2.2vw, 9px);
line-height: 1;
color: rgba(255, 247, 230, 0.62);
}
.fo__lang {
display: inline-flex;
align-items: center;
gap: 4px;
}
.fo__ico {
display: inline-flex;
width: 12px;
height: 12px;
}
.fo__ico svg {
width: 100%;
height: 100%;
}
@media (max-width: 340px), (max-height: 220px) {
.fo {
padding: 12px;
}
.fo__cols {
grid-template-columns: 1fr;
gap: 8px;
margin-top: 12px;
}
.fo__col {
display: flex;
align-items: baseline;
gap: 8px;
}
.fo__head {
font-size: 8px;
flex: 0 0 auto;
}
.fo__links {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 0;
}
.fo__links li {
margin-top: 0;
}
.fo__links a {
font-size: 8px;
}
.fo__dot {
width: 14px;
height: 14px;
}
.fo__dot--mint {
width: 10px;
height: 10px;
}
.fo__dot--blue {
width: 16px;
height: 16px;
}
.fo__copy {
margin-top: 8px;
padding-top: 8px;
font-size: 7px;
}
.fo__ico {
width: 10px;
height: 10px;
}
}
@media (prefers-reduced-motion: reduce) {
.fo__beacon {
animation: none !important;
}
.fo__beacon--slow {
transform: translateX(20%) !important;
}
.fo__beacon--fast {
transform: translateX(50%) !important;
}
.fo__beacon--drift {
transform: translateX(62%) !important;
}
.fo__links a {
transition: none !important;
}
}import styles from "./FooterOrb.module.scss";
export type FooterOrbProps = {
/** 느린 오브가 구분선을 한 번 건너는 시간 (빠른 쪽은 이것의 절반) */
duration?: string;
/** 지나가는 빛의 이징 — 가속하지 않는다 */
easing?: string;
/** 첫 오브 색 — --beaconColor 로 내려간다 */
color?: string;
};
type Column = { head: string; links: string[] };
const COLUMNS: Column[] = [
{ head: "제품", links: ["기능", "요금제", "변경 기록"] },
{ head: "문서", links: ["빠른 시작", "API", "예제"] },
{ head: "회사", links: ["소개", "채용", "문의"] },
{ head: "커뮤니티", links: ["디스코드", "블로그", "상태"] },
];
// 음수 지연으로 셋의 위상을 20% · 50% · 62% 로 벌려 둔다 — 어느 순간에도 최소 하나는 궤도 안에 있다
const BEACONS = [
{ dot: styles.dotLilac, delay: "-400ms", half: false },
{ dot: styles.dotMint, delay: "-500ms", half: true },
{ dot: styles.dotBlue, delay: "-1200ms", half: false },
];
function halfOf(duration: string): string {
const n = parseFloat(duration);
return duration.indexOf("ms") >= 0 ? `${n / 2}ms` : `${n / 2}s`;
}
export default function FooterOrb({
duration = "2s",
easing = "linear",
color = "#b9a0ff",
}: FooterOrbProps) {
return (
<main className={styles.stage}>
<footer className={styles.fo} style={{ "--beaconColor": color }}>
{/* 구분선 위를 지나가는 미니 오브 셋 — 장식이라 낭독기에서 숨긴다 */}
<div className={styles.rail} aria-hidden="true">
{BEACONS.map((b) => (
<span
key={b.delay}
className={styles.beacon}
style={{
animationName: "beaconCross",
animationDuration: b.half ? halfOf(duration) : duration,
animationTimingFunction: easing,
animationIterationCount: "infinite",
animationFillMode: "backwards",
animationDelay: b.delay,
}}
>
<span className={`${styles.dot} ${b.dot}`} />
</span>
))}
</div>
<div className={styles.cols}>
{COLUMNS.map((c) => (
<section key={c.head} className={styles.col}>
<h2 className={styles.head}>{c.head}</h2>
<ul className={styles.links}>
{c.links.map((l) => (
<li key={l}><a href="#" onClick={(e: { preventDefault: () => void }) => { e.preventDefault(); }}>{l}</a></li>
))}
</ul>
</section>
))}
</div>
<p className={styles.copy}>
<span>© 2026 Orbit Labs</span>
<span className={styles.lang}>
<span className={styles.ico} aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M213.09,172.48a96,96,0,0,1-80.41,51.41l3.17-16.44a8,8,0,0,0-2-6.95l-19.74-20.33a8,8,0,0,1-1.44-8.69l13.7-30.74a8,8,0,0,1,8.38-4.67l22.82,3.08a8.11,8.11,0,0,1,3.12,1.11ZM116.71,95,129,88.24a7.46,7.46,0,0,0,1.5-1.07l26.91-24.33A8,8,0,0,0,159,53l-10.5-18.81A96.62,96.62,0,0,0,128,32,95.61,95.61,0,0,0,67.78,53.23L56,81.08A8,8,0,0,0,55.88,87l11.5,30.67a8,8,0,0,0,5.81,5l2.69.58L89.2,100a8,8,0,0,1,6.94-4h16.71A7.9,7.9,0,0,0,116.71,95Z" opacity="0.2"/><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm88,104a87.62,87.62,0,0,1-6.4,32.94l-44.7-27.49a15.92,15.92,0,0,0-6.24-2.23l-22.82-3.08a16.11,16.11,0,0,0-16,7.86h-8.72l-3.8-7.86a15.91,15.91,0,0,0-11-8.67l-8-1.73L96.14,104h16.71a16.06,16.06,0,0,0,7.73-2l12.25-6.76a16.62,16.62,0,0,0,3-2.14l26.91-24.34A15.93,15.93,0,0,0,166,49.1l-.36-.65A88.11,88.11,0,0,1,216,128ZM143.31,41.34,152,56.9,125.09,81.24,112.85,88H96.14a16,16,0,0,0-13.88,8l-8.73,15.23L63.38,84.19,74.32,58.32a87.87,87.87,0,0,1,69-17ZM40,128a87.53,87.53,0,0,1,8.54-37.8l11.34,30.27a16,16,0,0,0,11.62,10l21.43,4.61L96.74,143a16.09,16.09,0,0,0,14.4,9h1.48l-7.23,16.23a16,16,0,0,0,2.86,17.37l.14.14L128,205.94l-1.94,10A88.11,88.11,0,0,1,40,128Zm102.58,86.78,1.13-5.81a16.09,16.09,0,0,0-4-13.9,1.85,1.85,0,0,1-.14-.14L120,174.74,133.7,144l22.82,3.08,45.72,28.12A88.18,88.18,0,0,1,142.58,214.78Z"/></svg></span>
한국어
</span>
</p>
</footer>
</main>
);
}08 軌道オーブフッター — 計測 累積面積 6.163% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · ai ランディングページ 光るオーブ デザインキット、セクション9選
このデモが属する記事
ai ランディングページ 光るオーブ デザインキット、セクション9選ai ランディングページの光るオーブとは、台になる背景を単色のまま置いて、画面の中で光るものを丸い球ひとつに絞る作り方です。グラデーション背景なしでもAIらしく見えるこの手を、ヒーローからフッターまで貼って使える9つのセクションにまとめまし この記事のデモをすべて見る


