Claude Code · claude-opus-5-5 · React (TSX)
아래 블록 '메인 둘째 줄: 공간 사진·공지사항·오시는 길 카드'와 위아래로 붙여 쓰면 카드 간격이 맞게 이어집니다.
결과 코드펼치기
import { useRef, useState, type KeyboardEvent } from "react";
type Course = { id: number; title: string; state: "모집 중" | "진행 중" | "마감" };
const TRACKS: { key: string; label: string; courses: Course[] }[] = [
{
key: "basic",
label: "입문 클래스",
courses: [
{ id: 103, title: "처음 만나는 카메라: 노출과 구도 (4주)", state: "모집 중" },
{ id: 102, title: "스마트폰으로 찍는 일상 사진 (원데이)", state: "모집 중" },
{ id: 101, title: "필름 카메라 입문과 현상 체험 (3주)", state: "진행 중" },
],
},
{
key: "advanced",
label: "심화 클래스",
courses: [
{ id: 204, title: "나만의 사진집 만들기 (8주)", state: "모집 중" },
{ id: 203, title: "라이트룸 보정 마스터 (4주)", state: "모집 중" },
{ id: 202, title: "풍경 사진 장노출 실습 (5주)", state: "진행 중" },
{ id: 201, title: "인물 사진 라이팅 (6주)", state: "진행 중" },
],
},
];
const FONT = '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif';
function StateBadge({ state }: { state: Course["state"] }) {
const off = state === "마감";
return (
<span
className={`inline-flex h-[26px] shrink-0 items-center whitespace-nowrap rounded-full px-2.5 text-xs font-bold leading-none ${
off ? "bg-[#f4f6f9] text-[#8a93a3]" : "bg-[#e9effa] text-[#3D64B1]"
}`}
>
{state}
</span>
);
}
export default function MainHeroCourses() {
const [active, setActive] = useState(0);
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
const onTabKey = (e: KeyboardEvent<HTMLButtonElement>, i: number) => {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
const next = (i + (e.key === "ArrowRight" ? 1 : TRACKS.length - 1)) % TRACKS.length;
setActive(next);
tabRefs.current[next]?.focus();
};
return (
<section className="w-full bg-[#f4f6f9]" style={{ fontFamily: FONT }}>
<div className="mx-auto grid w-full max-w-[1200px] grid-cols-1 gap-3 px-4 pb-1.5 pt-4 sm:gap-4 sm:px-6 sm:pb-2 sm:pt-6 lg:grid-cols-12">
{/* 히어로 카드 */}
<div
className="relative flex min-h-[320px] flex-col justify-between gap-8 overflow-hidden rounded-[20px] bg-[#121a2a] bg-cover bg-center p-6 text-white sm:min-h-[360px] sm:rounded-[24px] sm:p-12 lg:col-span-5 lg:min-h-[440px]"
style={{ backgroundImage: "url(https://nnuer.site/media/py3QPH39W5Be)" }}
>
<div className="absolute inset-0 bg-[linear-gradient(160deg,rgba(18,26,42,0.45)_0%,rgba(18,26,42,0.92)_100%)]" />
<div className="relative">
<h1 className="text-[28px] font-extrabold leading-[1.35] tracking-[-0.025em] lg:text-[38px]">
빛을 읽는 사진,
<br />
<span className="text-[#F5C842]">루멘</span>에서 시작합니다
</h1>
<p className="mt-4 max-w-[320px] text-[15px] leading-[1.75] tracking-[-0.01em] text-white/75" style={{ wordBreak: "keep-all" }}>
카메라를 처음 잡는 날부터 나만의 사진집을 만드는 날까지, 소규모 클래스로 함께합니다.
</p>
</div>
<div className="relative flex flex-wrap gap-2">
<a
href="/classes"
className="inline-flex h-12 flex-1 items-center justify-center whitespace-nowrap rounded-full bg-[#F5C842] px-3 text-base font-bold text-[#1B263B] transition hover:-translate-y-0.5 hover:bg-[#e5b420] sm:h-[52px] sm:flex-none sm:px-[26px]"
>
클래스 보기
</a>
<a
href="/contact"
className="inline-flex h-12 flex-1 items-center justify-center whitespace-nowrap rounded-full border-[1.5px] border-white/45 px-3 text-base font-bold text-white transition hover:-translate-y-0.5 hover:bg-white hover:text-[#1B263B] sm:h-[52px] sm:flex-none sm:px-[26px]"
>
상담 문의
</a>
</div>
</div>
{/* 클래스 카드 */}
<div className="rounded-[20px] bg-white p-[22px] sm:rounded-[24px] sm:p-7 lg:col-span-7">
<div className="mb-4 flex items-center justify-between gap-3">
<h2 className="text-xl font-extrabold tracking-[-0.02em] text-[#172031]">클래스</h2>
<a href="/classes" className="whitespace-nowrap text-sm font-bold text-[#8a93a3] hover:text-[#3D64B1]">
전체 보기
</a>
</div>
<div role="tablist" aria-label="클래스 단계" className="mb-2 grid grid-cols-2 gap-1 rounded-[14px] bg-[#f4f6f9] p-1">
{TRACKS.map((t, i) => (
<button
key={t.key}
ref={(el) => {
tabRefs.current[i] = el;
}}
type="button"
role="tab"
id={`tab-${t.key}`}
aria-controls={`panel-${t.key}`}
aria-selected={active === i}
tabIndex={active === i ? 0 : -1}
onClick={() => setActive(i)}
onKeyDown={(e) => onTabKey(e, i)}
className={`h-11 rounded-[10px] text-[15px] font-bold transition-colors ${
active === i ? "bg-white text-[#1B263B] shadow-[0_2px_8px_rgba(18,26,42,0.08)]" : "text-[#8a93a3]"
}`}
>
{t.label}
</button>
))}
</div>
{TRACKS.map((t, i) => (
<ul key={t.key} id={`panel-${t.key}`} role="tabpanel" aria-labelledby={`tab-${t.key}`} hidden={active !== i}>
{t.courses.map((c, j) => (
<li key={c.id}>
<a
href={`/classes/${c.id}`}
className={`group flex flex-col items-start gap-1.5 px-1 py-4 sm:flex-row sm:items-center sm:justify-between sm:gap-3 ${
j < t.courses.length - 1 ? "border-b border-[#e4e7ed]" : ""
}`}
>
<span className="min-w-0 text-base font-semibold leading-[1.45] text-[#172031] transition-colors group-hover:text-[#3D64B1] sm:order-1" style={{ wordBreak: "keep-all" }}>
{c.title}
</span>
<span className="sm:order-2">
<StateBadge state={c.state} />
</span>
</a>
</li>
))}
</ul>
))}
</div>
</div>
</section>
);
}
적용 사이트 0
이 블록이 실제로 쓰인 사이트입니다.
아직 등록된 사이트가 없습니다. 이 블록을 쓴 사이트가 있다면 알려 주세요.
재현 후기
v1 · 0개아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.