First CH Designs
BLK-04Blocks

Service Zigzag (Card-less)

A card-less editorial take on a services / features section: instead of a 3-up card grid, entries stack vertically as full-width bands that alternate left/right (zigzag). Each row swaps a dotted 'plate' panel (with an oversized outlined index numeral) and un-boxed text via CSS grid order, grouped by hairline rules and generous whitespace. Zero image assets; amber rules and Montserrat display numerals give it a specimen-like editorial feel.

Added:
2026-07-27
Dependencies:
None
tags
#service #features #block #editorial #zigzag #cardless #hairline #no-image

Preview

BLK-04 — Services

私たちにできること

サービスをカードで並べず、ひとつずつを「面」で見せる。 設計から運用まで、成果に必要な工程を左右交互のリズムで。

  1. S-01 / CORPORATE
    01

    コーポレートサイト制作

    会社の「標準」になるサイトを、情報設計からデザイン・実装まで一貫して。伝わる構造と、育てられる土台を同時に。

    • 情報設計
    • デザイン
    • 実装
  2. S-02 / LANDING
    02

    ランディングページ / LP

    成果から逆算した構成とコピーで、見た人を次の一歩へ。速度・可読性・導線まで、数字で語れる面をつくります。

    • 構成設計
    • コピー
    • 計測導線
  3. S-03 / OPERATION
    03

    保守・改善運用

    つくって終わりにしない。公開後もデータを見ながら、更新・改善・AI活用で軽く回り続ける運用へ。

    • 保守
    • 改善提案
    • AI活用
"use client";

import { useEffect, useState } from "react";
import { Montserrat } from "next/font/google";

const montserrat = Montserrat({
  weight: ["600", "700", "800"],
  subsets: ["latin"],
  display: "swap",
});

/**
 * サービス・特徴(脱カードのエディトリアル変種)
 * 参考: 他社サイトに見られる「互い違いブロック」構図をブランド再構築(コピーではない)。
 *
 * 汎用技法メモ(web-design-playbook 還流用):
 * - 脱カード=サービスを 3-up のカード羅列にせず、左右交互(zigzag)の全幅バンドで縦に積む。
 *   各バンドは sm:grid-cols-12 上で「面(ドット方眼の余白プレート)」と「テキスト」を組み、
 *   偶数行/奇数行で order を入れ替えるだけで互い違いのリズムが出る(画像素材ゼロで成立)。
 * - グルーピングは箱の border ではなく hairline 罫線(border-t/border-b border-gray-200)+
 *   広い縦余白(py)で行う。囲まないことで"面のエディトリアル"に見える。
 * - リビールは mount 後 rAF で inView を立て、行ごとに transition-delay をずらすだけ。
 *   transform/opacity のみ・motion-reduce: で即時表示にフォールバック。
 */

const services = [
  {
    idx: "01",
    code: "S-01 / CORPORATE",
    title: "コーポレートサイト制作",
    body: "会社の「標準」になるサイトを、情報設計からデザイン・実装まで一貫して。伝わる構造と、育てられる土台を同時に。",
    points: ["情報設計", "デザイン", "実装"],
  },
  {
    idx: "02",
    code: "S-02 / LANDING",
    title: "ランディングページ / LP",
    body: "成果から逆算した構成とコピーで、見た人を次の一歩へ。速度・可読性・導線まで、数字で語れる面をつくります。",
    points: ["構成設計", "コピー", "計測導線"],
  },
  {
    idx: "03",
    code: "S-03 / OPERATION",
    title: "保守・改善運用",
    body: "つくって終わりにしない。公開後もデータを見ながら、更新・改善・AI活用で軽く回り続ける運用へ。",
    points: ["保守", "改善提案", "AI活用"],
  },
];

export default function ServiceZigzag() {
  const [inView, setInView] = useState(false);

  // マウント後にリビール開始(reduced-motion 時は motion-reduce: クラスで即時表示)
  useEffect(() => {
    const id = requestAnimationFrame(() => setInView(true));
    return () => cancelAnimationFrame(id);
  }, []);

  // リビール用クラス(遅延は各要素の inline transitionDelay で行ごとにずらす)
  const reveal =
    `transition-[opacity,transform] duration-700 ease-out motion-reduce:transition-none ${
      inView
        ? "translate-y-0 opacity-100"
        : "translate-y-6 opacity-0 motion-reduce:translate-y-0 motion-reduce:opacity-100"
    }`;

  // 面プレートのドット方眼(標本モチーフ・装飾なので aria-hidden)
  const dotGrid = {
    backgroundImage:
      "radial-gradient(circle, rgba(34,30,25,0.10) 1px, transparent 1px)",
    backgroundSize: "14px 14px",
  };

  return (
    <section
      aria-labelledby="svc-zigzag-heading"
      className="w-full max-w-5xl bg-white px-1"
    >
      {/* ヘッダー */}
      <header className="max-w-2xl">
        <p
          className={`${montserrat.className} text-xs font-bold tracking-[0.25em] text-amber-600 uppercase ${reveal}`}
          style={{ transitionDelay: "0ms" }}
        >
          BLK-04 — Services
        </p>
        <h2
          id="svc-zigzag-heading"
          className={`mt-4 text-[clamp(1.8rem,4vw,2.6rem)] leading-[1.25] font-bold tracking-tight text-gray-900 ${reveal}`}
          style={{ transitionDelay: "80ms" }}
        >
          私たちに
          <span className="relative whitespace-nowrap">
            できること
            <span
              aria-hidden="true"
              className="absolute -bottom-1 left-0 h-[3px] w-full bg-amber-500/70"
            />
          </span>
        </h2>
        <p
          className={`mt-5 text-[15px] leading-[1.9] font-medium text-gray-600 ${reveal}`}
          style={{ transitionDelay: "160ms" }}
        >
          サービスをカードで並べず、ひとつずつを「面」で見せる。
          設計から運用まで、成果に必要な工程を左右交互のリズムで。
        </p>
      </header>

      {/* ジグザグ・リスト(hairline罫線+余白でグルーピング) */}
      <ol className="mt-12 border-t border-gray-200 sm:mt-16">
        {services.map((s, i) => {
          const visualLeft = i % 2 === 0;
          return (
            <li
              key={s.idx}
              className={`grid grid-cols-1 gap-6 border-b border-gray-200 py-10 sm:grid-cols-12 sm:items-center sm:gap-10 sm:py-16 ${reveal}`}
              style={{ transitionDelay: `${240 + i * 120}ms` }}
            >
              {/* 面プレート(画像枠の代わり・ドット方眼+大インデックス数字) */}
              <div
                className={`relative overflow-hidden rounded-xl bg-[#faf7f2] sm:col-span-5 ${
                  visualLeft ? "sm:order-1" : "sm:order-2"
                }`}
              >
                <div
                  aria-hidden="true"
                  className="absolute inset-0"
                  style={dotGrid}
                />
                {/* 上辺のアンバー罫線 */}
                <span
                  aria-hidden="true"
                  className="absolute top-0 left-0 h-[3px] w-16 bg-amber-500"
                />
                <div className="relative flex aspect-[16/9] flex-col justify-between p-5 sm:aspect-[4/3] sm:p-6">
                  <span
                    className={`${montserrat.className} text-[11px] font-semibold tracking-[0.22em] text-amber-700 uppercase`}
                  >
                    {s.code}
                  </span>
                  <span
                    className={`${montserrat.className} self-end text-[clamp(3.5rem,9vw,6rem)] leading-none font-extrabold`}
                    style={{
                      WebkitTextStroke: "1.5px rgba(34,30,25,0.22)",
                      color: "transparent",
                    }}
                    aria-hidden="true"
                  >
                    {s.idx}
                  </span>
                </div>
              </div>

              {/* テキスト(箱で囲まない=脱カード) */}
              <div
                className={`sm:col-span-7 ${
                  visualLeft ? "sm:order-2" : "sm:order-1"
                }`}
              >
                <div className="flex items-baseline gap-3">
                  <span
                    className={`${montserrat.className} text-sm font-bold text-amber-600`}
                  >
                    {s.idx}
                  </span>
                  <span
                    aria-hidden="true"
                    className="h-px flex-1 bg-gray-200"
                  />
                </div>
                <h3 className="mt-3 text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">
                  {s.title}
                </h3>
                <p className="mt-3 max-w-xl text-[15px] leading-[1.9] text-gray-600">
                  {s.body}
                </p>
                <ul className="mt-5 flex flex-wrap gap-x-5 gap-y-2">
                  {s.points.map((p) => (
                    <li
                      key={p}
                      className="border-l border-amber-400 pl-2 text-[13px] font-semibold tracking-wide text-gray-700"
                    >
                      {p}
                    </li>
                  ))}
                </ul>
                <div className="mt-6">
                  <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 className="absolute -bottom-1 left-0 h-px w-full origin-left scale-x-100 bg-gray-900 transition-transform duration-300 ease-out group-hover:scale-x-0 motion-reduce:transition-none" />
                      <span className="absolute -bottom-1 left-0 h-px w-full origin-right scale-x-0 bg-amber-600 transition-transform delay-150 duration-300 ease-out group-hover:origin-left group-hover:scale-x-100 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>
              </div>
            </li>
          );
        })}
      </ol>
    </section>
  );
}

Add to your project via shadcn CLI

npx shadcn@latest add https://designs.first-ch.com/r/service-zigzag.json