First CH Designs
PGE-03Pages

Full-bleed Visual Hero

A full-bleed hero whose key visual is split into a 4x3 tile grid that lands back into one seamless photograph. A white scrim keeps Japanese copy legible, and a hairline rule groups the summary items instead of cards. The reveal is pure CSS @keyframes with animation-fill-mode: backwards, so it needs no JS and falls back to the finished state on SSR and under reduced motion. Swap the text-free background KV via the imageSrc prop.

Added:
2026-08-01
Dependencies:
None
tags
#hero #page #fullbleed #image #grid-reveal #css-only-motion #premium

Preview

Web Production

その一枚が、
会社の顔になる。

コーポレートサイトは、会社の信用が最初に伝わる場所。写真・言葉・余白の設計から、長く効く一枚に仕上げます。

制作領域
コーポレート・採用・LP
標準納期
6〜10週間
公開後
改善まで伴走
/**
 * ヒーロー③全面ビジュアル(写真グリッド → 全景)
 * 参考: 他社サイトに見られる、写真をタイル分割して見せてから全景へ寄せる重厚な導入を自社ブランドで再構築。
 *
 * 汎用技法メモ(web-design-playbook 還流用):
 * 1) タイル分解: 4x3 の grid の各セルを overflow-hidden にし、中に「ステージ全体と同寸
 *    (w-[400%] h-[300%])の bg-cover bg-center」を置いて left/top を -col*100% / -row*100%
 *    ずらす。background-size の % 指定だと画像が引き伸ばされるが、この入れ子なら
 *    背景レイヤーと完全に同じ cover 結果になり、タイルを scale(1) に戻した瞬間に継ぎ目なく
 *    1枚の全景に戻る。
 * 2) JS を使わない出現: transform/opacity の @keyframes に animation-fill-mode: backwards(both)
 *    を効かせ、要素の「素の状態=完成形」にする。JS 無効・SSR の初期HTML・
 *    prefers-reduced-motion: reduce のいずれでも完成状態で表示され、useState/useEffect が要らない。
 *    reduced-motion ガードは同梱の <style> 末尾の @media で animation: none にする
 *    (animation を inline style で書くと Tailwind の motion-reduce: クラスが効かないため、
 *    クラス側に animation を持たせ inline は animation-delay だけにする)。
 */

const IMAGE_SRC = "/designs/hero-fullbleed-visual-kv.webp";

// 4 列 x 3 行。ステージを覆うタイル群(左上から対角に着地させる)
const COLS = 4;
const ROWS = 3;
const TILES = Array.from({ length: COLS * ROWS }, (_, i) => ({
  col: i % COLS,
  row: Math.floor(i / COLS),
}));

const META = [
  { term: "制作領域", desc: "コーポレート・採用・LP" },
  { term: "標準納期", desc: "6〜10週間" },
  { term: "公開後", desc: "改善まで伴走" },
];

export default function HeroFullbleedVisual({
  imageSrc = IMAGE_SRC,
  headingTag: Heading = "h1",
}: {
  imageSrc?: string;
  /**
   * 見出しのタグ。実案件ではそのまま(h1)。ギャラリーのプレビューは1ページに複数の標本が
   * 並ぶため "p" を渡してh1の重複を避ける(見た目はclassNameで決まるので変わらない)。
   */
  headingTag?: "h1" | "p";
}) {
  return (
    <div className="relative w-full max-w-5xl overflow-hidden rounded-2xl border border-gray-200 bg-white">
      <div className="relative min-h-[460px] sm:min-h-[560px]">
        {/* 全面ビジュアル: タイルが着地して1枚の全景になる */}
        <div
          role="img"
          aria-label="朝霧のなかで見上げた高層ビルの全景"
          className="absolute inset-0"
        >
          {/* 継ぎ目を埋める土台(タイルが揃いきってから遅れて出す) */}
          <span
            aria-hidden="true"
            className="hfv-base absolute inset-0 bg-cover bg-center"
            style={{ backgroundImage: `url(${imageSrc})` }}
          />
          <div className="absolute inset-0 grid grid-cols-4 grid-rows-3">
            {TILES.map(({ col, row }, i) => (
              <span
                key={i}
                aria-hidden="true"
                className="hfv-tile relative block overflow-hidden shadow-[inset_0_0_0_1px_rgba(255,255,255,0.18)]"
                style={{ animationDelay: `${80 + (col + row) * 85}ms` }}
              >
                <span
                  className="absolute h-[300%] w-[400%] bg-cover bg-center"
                  style={{
                    backgroundImage: `url(${imageSrc})`,
                    left: `${-col * 100}%`,
                    top: `${-row * 100}%`,
                  }}
                />
              </span>
            ))}
          </div>
        </div>

        {/* 文字の可読性を作る白スクリム(左→右/下→上の2枚重ね) */}
        <span
          aria-hidden="true"
          className="absolute inset-0 bg-gradient-to-r from-white via-white/85 to-white/35 sm:via-white/70 sm:to-white/0"
        />
        <span
          aria-hidden="true"
          className="absolute inset-x-0 bottom-0 h-40 bg-gradient-to-t from-white via-white/70 to-transparent"
        />

        {/* コンテンツ */}
        <div className="relative flex min-h-[460px] flex-col justify-end p-7 sm:min-h-[560px] sm:p-12">
          <p
            className="hfv-rise flex items-center gap-3 text-xs font-bold tracking-[0.25em] text-amber-600 uppercase"
            style={{ animationDelay: "700ms" }}
          >
            <span aria-hidden="true" className="h-px w-8 bg-amber-600" />
            Web Production
          </p>

          <Heading
            className="hfv-rise mt-5 max-w-xl text-[clamp(1.55rem,4.4vw,3rem)] leading-[1.4] font-bold tracking-tight text-gray-900"
            style={{ animationDelay: "820ms" }}
          >
            その一枚が、
            <br />
            会社の顔になる。
          </Heading>

          <p
            className="hfv-rise mt-5 max-w-md text-[15px] leading-[2] text-gray-700"
            style={{ animationDelay: "980ms" }}
          >
            コーポレートサイトは、会社の信用が最初に伝わる場所。写真・言葉・余白の設計から、長く効く一枚に仕上げます。
          </p>

          <div
            className="hfv-rise mt-8 flex flex-wrap items-center gap-4"
            style={{ animationDelay: "1120ms" }}
          >
            <a
              href="#"
              className="rounded-lg bg-amber-600 px-6 py-3 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-amber-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600"
            >
              制作のご相談
            </a>
            <a
              href="#"
              className="group inline-flex items-center gap-2 text-sm font-semibold text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-amber-600"
            >
              <span className="relative">
                制作実績を見る
                <span
                  aria-hidden="true"
                  className="absolute -bottom-1 left-0 h-px w-full origin-right scale-x-100 bg-gray-900/40 transition-transform duration-300 ease-out group-hover:origin-left group-hover:scale-x-0 motion-reduce:transition-none"
                />
              </span>
              <span
                aria-hidden="true"
                className="inline-block transition-transform duration-300 ease-out group-hover:translate-x-1 motion-reduce:transition-none"
              >
                →
              </span>
            </a>
          </div>

          {/* hairline罫線でグルーピング(カードにしない) */}
          <dl
            className="hfv-rise mt-9 flex flex-col gap-3 border-t border-gray-900/10 pt-5 sm:flex-row sm:gap-0"
            style={{ animationDelay: "1260ms" }}
          >
            {META.map((m, i) => (
              <div
                key={m.term}
                className={`flex items-baseline gap-3 sm:block sm:flex-1 ${
                  i > 0 ? "sm:border-l sm:border-gray-900/10 sm:pl-6" : "sm:pr-6"
                }`}
              >
                <dt className="w-[5.5rem] shrink-0 text-[11px] font-bold tracking-[0.18em] text-gray-500 uppercase sm:w-auto">
                  {m.term}
                </dt>
                <dd className="text-sm font-semibold text-gray-900 sm:mt-1.5">{m.desc}</dd>
              </div>
            ))}
          </dl>
        </div>
      </div>

      {/* このブロック内で完結する keyframes と reduced-motion ガード */}
      <style>{`
        .hfv-tile {
          animation: hfv-tile-in 950ms cubic-bezier(0.16, 1, 0.3, 1) both;
        }
        .hfv-base {
          animation: hfv-fade-in 600ms ease-out 1150ms both;
        }
        .hfv-rise {
          animation: hfv-rise-in 800ms cubic-bezier(0.16, 1, 0.3, 1) both;
        }
        @keyframes hfv-tile-in {
          from { opacity: 0; transform: scale(0.82); }
          60%  { opacity: 1; }
          to   { opacity: 1; transform: scale(1); }
        }
        @keyframes hfv-fade-in {
          from { opacity: 0; }
          to   { opacity: 1; }
        }
        @keyframes hfv-rise-in {
          from { opacity: 0; transform: translateY(14px); }
          to   { opacity: 1; transform: translateY(0); }
        }
        @media (prefers-reduced-motion: reduce) {
          .hfv-tile, .hfv-base, .hfv-rise { animation: none; }
        }
      `}</style>
    </div>
  );
}

Add to your project via shadcn CLI

npx shadcn@latest add https://designs.first-ch.com/r/hero-fullbleed-visual.json