nnuer
강희태@gmlxo9917

메인 첫 줄: 사진 히어로 카드 + 입문/심화 탭 클래스 카드

히어로 블록

연회색 바탕 위에 카메라 사진 히어로 카드(5/12)와 클래스 목록 카드(7/12)가 나란히 놓인 메인 첫 줄입니다. 클래스 카드는 입문/심화 탭으로 목록을 바꿉니다.

#bento#card-grid#tabs#photo#navy#gold2026. 10. 16. 업데이트

블록 신고

운영자가 확인한 뒤 필요하면 블록을 숨깁니다.

사유
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개

아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.