03きらめき応答カードグリッド
濃い色のカード4枚にそれぞれ小さなオーブが乗り、ホバーでオーブが自分の中心から大きくなり(scale)、カードが2px浮かび上がって影が深くなります。カーソルを追うスポットライト方式は使いません。
使いどころ機能紹介グリッド・モデル比較カード
scaletranslateYbox-shadowgrid-template-columns

<main class="stage">
<div class="cg is-demo" id="cg">
<a class="cg__card" href="#">
<span class="cg__orb" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M194.82,151.43l-55.09,20.3-20.3,55.09a7.92,7.92,0,0,1-14.86,0l-20.3-55.09-55.09-20.3a7.92,7.92,0,0,1,0-14.86l55.09-20.3,20.3-55.09a7.92,7.92,0,0,1,14.86,0l20.3,55.09,55.09,20.3A7.92,7.92,0,0,1,194.82,151.43Z" opacity="0.2"/><path d="M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z"/></svg></span>
<span class="cg__name">文書の要約</span>
<span class="cg__line">長い議事録を3行に</span>
</a>
<a class="cg__card" href="#">
<span class="cg__orb" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M96,240l16-80L48,136,160,16,144,96l64,24Z" opacity="0.2"/><path d="M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7l-112,120a8,8,0,0,0,3,13l57.63,21.61L88.16,238.43a8,8,0,0,0,13.69,7l112-120A8,8,0,0,0,215.79,118.17ZM109.37,214l10.47-52.38a8,8,0,0,0-5-9.06L62,132.71l84.62-90.66L136.16,94.43a8,8,0,0,0,5,9.06l52.8,19.8Z"/></svg></span>
<span class="cg__name">下書き生成</span>
<span class="cg__line">メールと企画書の骨組み</span>
</a>
<a class="cg__card" href="#">
<span class="cg__orb" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,124a48,48,0,0,1-32,45.27h0V176a40,40,0,0,1-80,0,40,40,0,0,1-80,0v-6.73h0a48,48,0,0,1,0-90.54V72a40,40,0,0,1,80,0,40,40,0,0,1,80,0v6.73A48,48,0,0,1,240,124Z" opacity="0.2"/><path d="M248,124a56.11,56.11,0,0,0-32-50.61V72a48,48,0,0,0-88-26.49A48,48,0,0,0,40,72v1.39a56,56,0,0,0,0,101.2V176a48,48,0,0,0,88,26.49A48,48,0,0,0,216,176v-1.41A56.09,56.09,0,0,0,248,124ZM88,208a32,32,0,0,1-31.81-28.56A55.87,55.87,0,0,0,64,180h8a8,8,0,0,0,0-16H64A40,40,0,0,1,50.67,86.27,8,8,0,0,0,56,78.73V72a32,32,0,0,1,64,0v68.26A47.8,47.8,0,0,0,88,128a8,8,0,0,0,0,16,32,32,0,0,1,0,64Zm104-44h-8a8,8,0,0,0,0,16h8a55.87,55.87,0,0,0,7.81-.56A32,32,0,1,1,168,144a8,8,0,0,0,0-16,47.8,47.8,0,0,0-32,12.26V72a32,32,0,0,1,64,0v6.73a8,8,0,0,0,5.33,7.54A40,40,0,0,1,192,164Zm16-52a8,8,0,0,1-8,8h-4a36,36,0,0,1-36-36V80a8,8,0,0,1,16,0v4a20,20,0,0,0,20,20h4A8,8,0,0,1,208,112ZM60,120H56a8,8,0,0,1,0-16h4A20,20,0,0,0,80,84V80a8,8,0,0,1,16,0v4A36,36,0,0,1,60,120Z"/></svg></span>
<span class="cg__name">文脈を記憶</span>
<span class="cg__line">前回の会話の続きから</span>
</a>
<a class="cg__card" href="#">
<span class="cg__orb" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128A96,96,0,0,1,79.93,211.11h0L42.54,223.58a8,8,0,0,1-10.12-10.12l12.47-37.39h0A96,96,0,1,1,224,128Z" opacity="0.2"/><path d="M128,24A104,104,0,0,0,36.18,176.88L24.83,210.93a16,16,0,0,0,20.24,20.24l34.05-11.35A104,104,0,1,0,128,24Zm0,192a87.87,87.87,0,0,1-44.06-11.81,8,8,0,0,0-4-1.08,7.85,7.85,0,0,0-2.53.42L40,216,52.47,178.6a8,8,0,0,0-.66-6.54A88,88,0,1,1,128,216Zm12-88a12,12,0,1,1-12-12A12,12,0,0,1,140,128Zm-44,0a12,12,0,1,1-12-12A12,12,0,0,1,96,128Zm88,0a12,12,0,1,1-12-12A12,12,0,0,1,184,128Z"/></svg></span>
<span class="cg__name">チームチャット</span>
<span class="cg__line">Slackからそのまま質問</span>
</a>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 변수 3개 — 변형은 이 셋만 바꾼다
$duration: $dur-quick; // 실제 호버에서 카드가 뜨는 시간
$easing: $ease-spring;
$color: $subject-sky; // 카드 오브의 빛 색
// 이 편 아홉 항목은 전부 잉크 무대다 — 빛나는 오브는 어두운 바탕에서만 빛으로 읽힌다
@include stage($stage-ink);
.cg {
display: grid; grid-template-columns: repeat(2, 1fr); gap: $sp-3;
width: min(100%, 432px);
}
// 대비 실측(WCAG 상대휘도 계산): 카드 바탕(잉크+하늘색 6%)의 상대휘도 0.0137
.cg__card {
box-sizing: border-box; display: grid; gap: $sp-2; padding: $sp-3;
border: 1px solid rgba(255, 247, 230, .1); border-radius: $r-card;
background: rgba(110, 200, 255, .06);
box-shadow: 0 2px 6px rgba(23, 20, 26, .28);
text-decoration: none;
transition: transform $duration $easing, box-shadow $duration $easing;
}
.cg__name {
font-weight: 700; font-size: 13px; line-height: 1.2; color: $subject-cream; // $subject-cream 기준 15.46:1
}
.cg__line {
font-weight: 500; font-size: 10px; line-height: 1.3;
color: rgba(255, 247, 230, .72); // 카드 바탕 위에서 #beb7ad 로 합성 = 8.32:1
}
// 오브 — 겹으로 쌓은 radial-gradient. 커질 땐 자기 중심에서 (blog-demo 규칙 8)
.cg__orb {
display: inline-flex; align-items: center; justify-content: center;
width: 28px; height: 28px; border-radius: 50%; transform-origin: 50% 50%;
color: $stage-ink;
background:
radial-gradient(circle at 34% 28%, rgba(255, 255, 255, .95), rgba(255, 255, 255, 0) 46%),
radial-gradient(circle at 66% 74%, rgba(185, 160, 255, .85), rgba(185, 160, 255, 0) 60%),
radial-gradient(circle at 50% 50%, rgba(110, 200, 255, 1) 20%, rgba(47, 109, 246, .95) 96%);
box-shadow: 0 0 10px rgba(110, 200, 255, .35);
transition: transform $duration $easing;
}
.cg__orb svg { width: 60%; height: 60%; }
// 사람이 실제로 올렸을 때 — 카드는 2px 뜨고 그림자가 깊어지고 오브가 커진다
.cg__card:hover, .cg__card:focus-visible {
transform: translateY(-2px); box-shadow: $shadow-raised;
}
.cg__card:hover .cg__orb, .cg__card:focus-visible .cg__orb { transform: scale(1.35); }
// ── 미리보기 루프: 네 장이 차례로 같은 호버 상태를 지난다
// animation 단축은 delay 를 0 으로 리셋한다 → 낱개 속성으로. 지연 구간의 번쩍임은 backwards 로 막는다
.cg.is-demo .cg__card {
animation-name: cgLift; animation-duration: $dur-loop; animation-timing-function: $easing;
animation-iteration-count: infinite; animation-fill-mode: backwards;
}
.cg.is-demo .cg__orb {
animation-name: cgOrb; animation-duration: $dur-loop; animation-timing-function: $easing;
animation-iteration-count: infinite; animation-fill-mode: backwards;
}
.cg.is-demo .cg__card:nth-child(2), .cg.is-demo .cg__card:nth-child(2) .cg__orb { animation-delay: 120ms; }
.cg.is-demo .cg__card:nth-child(3), .cg.is-demo .cg__card:nth-child(3) .cg__orb { animation-delay: 240ms; }
.cg.is-demo .cg__card:nth-child(4), .cg.is-demo .cg__card:nth-child(4) .cg__orb { animation-delay: 360ms; }
@keyframes cgLift {
0%, 8% { transform: translateY(0); box-shadow: 0 2px 6px rgba(23, 20, 26, .28); }
22%, 46% { transform: translateY(-2px); box-shadow: $shadow-raised; }
62%, 100% { transform: translateY(0); box-shadow: 0 2px 6px rgba(23, 20, 26, .28); }
}
@keyframes cgOrb {
0%, 8% { transform: scale(1); }
22%, 46% { transform: scale(1.35); }
62%, 100% { transform: scale(1); }
}
// ── 320×200 폰 실측 대비 압축 (BD9). 좁은 화면에서는 간격을 올리지 않는다
@media (max-width: 340px), (max-height: 220px) {
.cg { gap: $sp-2; }
.cg__card { gap: $sp-1; padding: $sp-2; }
.cg__orb { width: 22px; height: 22px; }
.cg__name { font-size: 11px; }
.cg__line { font-size: 9px; }
}
@media (prefers-reduced-motion: reduce) {
.cg__card, .cg__orb { transition: none !important; }
.cg.is-demo .cg__card, .cg.is-demo .cg__orb { 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: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cg {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
width: min(100%, 432px);
}
.cg__card {
box-sizing: border-box;
display: grid;
gap: 8px;
padding: 12px;
border: 1px solid rgba(255, 247, 230, 0.1);
border-radius: 16px;
background: rgba(110, 200, 255, 0.06);
box-shadow: 0 2px 6px rgba(23, 20, 26, 0.28);
text-decoration: none;
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cg__name {
font-weight: 700;
font-size: 13px;
line-height: 1.2;
color: #fff7e6;
}
.cg__line {
font-weight: 500;
font-size: 10px;
line-height: 1.3;
color: rgba(255, 247, 230, 0.72);
}
.cg__orb {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
transform-origin: 50% 50%;
color: #17141a;
background: radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0) 46%), radial-gradient(circle at 66% 74%, rgba(185, 160, 255, 0.85), rgba(185, 160, 255, 0) 60%), radial-gradient(circle at 50% 50%, rgb(110, 200, 255) 20%, rgba(47, 109, 246, 0.95) 96%);
box-shadow: 0 0 10px rgba(110, 200, 255, 0.35);
transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cg__orb svg {
width: 60%;
height: 60%;
}
.cg__card:hover, .cg__card:focus-visible {
transform: translateY(-2px);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.cg__card:hover .cg__orb, .cg__card:focus-visible .cg__orb {
transform: scale(1.35);
}
.cg.is-demo .cg__card {
animation-name: cgLift;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.cg.is-demo .cg__orb {
animation-name: cgOrb;
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.cg.is-demo .cg__card:nth-child(2), .cg.is-demo .cg__card:nth-child(2) .cg__orb {
animation-delay: 120ms;
}
.cg.is-demo .cg__card:nth-child(3), .cg.is-demo .cg__card:nth-child(3) .cg__orb {
animation-delay: 240ms;
}
.cg.is-demo .cg__card:nth-child(4), .cg.is-demo .cg__card:nth-child(4) .cg__orb {
animation-delay: 360ms;
}
@keyframes cgLift {
0%, 8% {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(23, 20, 26, 0.28);
}
22%, 46% {
transform: translateY(-2px);
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
62%, 100% {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(23, 20, 26, 0.28);
}
}
@keyframes cgOrb {
0%, 8% {
transform: scale(1);
}
22%, 46% {
transform: scale(1.35);
}
62%, 100% {
transform: scale(1);
}
}
@media (max-width: 340px), (max-height: 220px) {
.cg {
gap: 8px;
}
.cg__card {
gap: 4px;
padding: 8px;
}
.cg__orb {
width: 22px;
height: 22px;
}
.cg__name {
font-size: 11px;
}
.cg__line {
font-size: 9px;
}
}
@media (prefers-reduced-motion: reduce) {
.cg__card, .cg__orb {
transition: none !important;
}
.cg.is-demo .cg__card, .cg.is-demo .cg__orb {
animation: none !important;
}
}var grid = document.getElementById('cg');
function stopLoop() { grid.classList.remove('is-demo'); }
grid.addEventListener('pointerenter', stopLoop);
grid.addEventListener('focusin', stopLoop);
var cards = document.querySelectorAll('.cg__card');
for (var i = 0; i < cards.length; i++) {
cards[i].addEventListener('click', function (e) { e.preventDefault(); stopLoop(); });
}import { useState } from "react";
import styles from "./CardGridOrb.module.scss";
export type CardGridOrbProps = { duration?: string; easing?: string; color?: string };
const SPARKLE = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M194.82,151.43l-55.09,20.3-20.3,55.09a7.92,7.92,0,0,1-14.86,0l-20.3-55.09-55.09-20.3a7.92,7.92,0,0,1,0-14.86l55.09-20.3,20.3-55.09a7.92,7.92,0,0,1,14.86,0l20.3,55.09,55.09,20.3A7.92,7.92,0,0,1,194.82,151.43Z" opacity="0.2"/><path d="M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z"/></svg>
);
const LIGHTNING = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M96,240l16-80L48,136,160,16,144,96l64,24Z" opacity="0.2"/><path d="M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7l-112,120a8,8,0,0,0,3,13l57.63,21.61L88.16,238.43a8,8,0,0,0,13.69,7l112-120A8,8,0,0,0,215.79,118.17ZM109.37,214l10.47-52.38a8,8,0,0,0-5-9.06L62,132.71l84.62-90.66L136.16,94.43a8,8,0,0,0,5,9.06l52.8,19.8Z"/></svg>
);
const BRAIN = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,124a48,48,0,0,1-32,45.27h0V176a40,40,0,0,1-80,0,40,40,0,0,1-80,0v-6.73h0a48,48,0,0,1,0-90.54V72a40,40,0,0,1,80,0,40,40,0,0,1,80,0v6.73A48,48,0,0,1,240,124Z" opacity="0.2"/><path d="M248,124a56.11,56.11,0,0,0-32-50.61V72a48,48,0,0,0-88-26.49A48,48,0,0,0,40,72v1.39a56,56,0,0,0,0,101.2V176a48,48,0,0,0,88,26.49A48,48,0,0,0,216,176v-1.41A56.09,56.09,0,0,0,248,124ZM88,208a32,32,0,0,1-31.81-28.56A55.87,55.87,0,0,0,64,180h8a8,8,0,0,0,0-16H64A40,40,0,0,1,50.67,86.27,8,8,0,0,0,56,78.73V72a32,32,0,0,1,64,0v68.26A47.8,47.8,0,0,0,88,128a8,8,0,0,0,0,16,32,32,0,0,1,0,64Zm104-44h-8a8,8,0,0,0,0,16h8a55.87,55.87,0,0,0,7.81-.56A32,32,0,1,1,168,144a8,8,0,0,0,0-16,47.8,47.8,0,0,0-32,12.26V72a32,32,0,0,1,64,0v6.73a8,8,0,0,0,5.33,7.54A40,40,0,0,1,192,164Zm16-52a8,8,0,0,1-8,8h-4a36,36,0,0,1-36-36V80a8,8,0,0,1,16,0v4a20,20,0,0,0,20,20h4A8,8,0,0,1,208,112ZM60,120H56a8,8,0,0,1,0-16h4A20,20,0,0,0,80,84V80a8,8,0,0,1,16,0v4A36,36,0,0,1,60,120Z"/></svg>
);
const CHAT = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M224,128A96,96,0,0,1,79.93,211.11h0L42.54,223.58a8,8,0,0,1-10.12-10.12l12.47-37.39h0A96,96,0,1,1,224,128Z" opacity="0.2"/><path d="M128,24A104,104,0,0,0,36.18,176.88L24.83,210.93a16,16,0,0,0,20.24,20.24l34.05-11.35A104,104,0,1,0,128,24Zm0,192a87.87,87.87,0,0,1-44.06-11.81,8,8,0,0,0-4-1.08,7.85,7.85,0,0,0-2.53.42L40,216,52.47,178.6a8,8,0,0,0-.66-6.54A88,88,0,1,1,128,216Zm12-88a12,12,0,1,1-12-12A12,12,0,0,1,140,128Zm-44,0a12,12,0,1,1-12-12A12,12,0,0,1,96,128Zm88,0a12,12,0,1,1-12-12A12,12,0,0,1,184,128Z"/></svg>
);
const CARDS = [
{ icon: SPARKLE, name: "문서 요약", line: "긴 회의록을 세 줄로" },
{ icon: LIGHTNING, name: "초안 생성", line: "메일과 기획서의 뼈대" },
{ icon: BRAIN, name: "맥락 기억", line: "지난 대화를 이어서" },
{ icon: CHAT, name: "팀 채팅", line: "슬랙에서 바로 질문" },
];
export default function CardGridOrb({ duration = "220ms", easing = "cubic-bezier(.2,.8,.2,1)", color = "#6ec8ff" }: CardGridOrbProps) {
// 변수 3개는 props 로 — 카드가 뜨는 시간 · 그 이징 · 오브의 빛 색
const [demo, setDemo] = useState(true);
const stopLoop = () => setDemo(false);
return (
<main className={styles.stage} style={{ "--orb": color }}>
<div className={styles.cg} onPointerEnter={stopLoop} onFocus={stopLoop}>
{CARDS.map((card, i) => (
<a
key={card.name}
className={`${styles.card} ${demo ? styles.isDemo : ""}`}
href="#"
style={{ transitionDuration: duration, transitionTimingFunction: easing, animationDelay: `${i * 120}ms` }}
onClick={(e: { preventDefault: () => void }) => { e.preventDefault(); stopLoop(); }}
>
<span className={styles.orb} aria-hidden="true" style={{ transitionDuration: duration, transitionTimingFunction: easing, animationDelay: `${i * 120}ms` }}>
{card.icon}
</span>
<span className={styles.name}>{card.name}</span>
<span className={styles.line}>{card.line}</span>
</a>
))}
</div>
</main>
);
}03 きらめき応答カードグリッド — 計測 累積面積 10.769% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · ai ランディングページ 光るオーブ デザインキット、セクション9選
このデモが属する記事
ai ランディングページ 光るオーブ デザインキット、セクション9選ai ランディングページの光るオーブとは、台になる背景を単色のまま置いて、画面の中で光るものを丸い球ひとつに絞る作り方です。グラデーション背景なしでもAIらしく見えるこの手を、ヒーローからフッターまで貼って使える9つのセクションにまとめまし この記事のデモをすべて見る


