First CH Designs
BLK-23Blocks

取引先・掲載実績ロゴ帯(マーキー)

取引先と掲載メディアを2レーンの無限ループ帯で流すロゴストリップ。ロゴ画像を1枚も使わず、weight・tracking・大小・先頭マークを1件ずつ変えた和欧混在のワードマーク(社名はすべて架空のダミー)で「ロゴらしさ」を作る。上下レーンは逆走+速度違い(42s/52s)、両端は mask-image で断ち切り、hoverで停止。prefers-reduced-motion では帯ごと落として同じ内容をhairlineの静止グリッドで見せる(イベント系サイトに見られるマーキー帯の構造を白基調コーポレート文脈へブランド再構築・2026-08-13)。

追加日:
2026-08-13
依存:
なし
tags
#logos #marquee #section #block #motion #infinite-loop #hairline #brand #no-image

プレビュー

取引先・掲載実績

敬称略・順不同

取引先
  • NORDVEIL
  • 白樺工機
  • LUMENCLIFF
  • 三雲製作所
  • STILLPORT
  • 蔵前アグリ
  • AOBADAI WORKS
  • 陽和リビング
掲載メディア
  • MEGURO DESIGN REVIEW
  • 月刊ものづくり通信
  • UI ARCHIVE TOKYO
  • 地域ビジネス白書
  • STUDIO BRIEF
  • ローカル経済ジャーナル
  • NOTED WORKS
  • はたらく現場ラボ

掲載社名はすべて架空のサンプルです。実在の企業・媒体とは関係ありません。

制作実績を見る
import { Montserrat } from "next/font/google";

// ラテン系フォントは unicode-range の分割が少ないので preload 既定のままでよい
// (和文フォントの preload 禁止は scripts/generate-registry.mjs のビルド関門を参照)。
const montserrat = Montserrat({
  weight: ["600", "700", "800"],
  subsets: ["latin"],
  display: "swap",
});

/**
 * 取引先・掲載実績ロゴ帯(マーキー)
 * 参考: イベント/フェス系サイトの無限ループ帯(PT-MOTION-009)を、白基調のコーポレート文脈へ再構築。
 * 参考元のテキスト・画像・ロゴは一切使わず、社名はすべて架空のダミーワードマーク。
 *
 * 汎用技法メモ(web-design-playbook 還流用):
 * - 継ぎ目のないマーキーは「gap を使わない」のが肝。トラックを w-max の flex にして同じ配列を2周ぶん並べ、
 *   translateX(0)→translateX(-50%) を linear infinite で回す。-50% が「1周ぶんの幅」と一致する必要があるため、
 *   要素間の余白は ul の gap-x ではなく各 li の mr-* で持たせる(gap は要素間にしか入らず、
 *   2周ぶんの合計幅が 2W にならないので -50% が半 gap ぶんズレて継ぎ目が跳ねる)。
 * - 逆走レーンは keyframes を増やさず [animation-direction:reverse] の1クラスで作る。速度を素数的にずらす
 *   (42s / 52s)と2レーンの位相が揃わず、機械的な反復に見えない。
 * - 両端の断ち切りは mask-image の linear-gradient で行う。白いグラデ画像を被せる方式と違い、
 *   台紙の色(ここではドット方眼の specimen-stage)が透けるので背景を選ばない。
 * - ロゴ素材ゼロで「ロゴらしさ」を出す: 字面の weight / tracking / 大小 / 先頭マーク(□ / ○ / ―)を
 *   1件ずつ変え、和文と欧文を交互に混ぜる。均一なテキストの羅列に見せないことがすべて。
 * - reduced-motion は animate-none で止めるだけでは足りない(止まった位置で見切れる)。
 *   マーキー全体を motion-reduce:hidden で落とし、同じ内容を hairline のグリッドで静止表示する。
 */

type Wordmark = {
  label: string;
  /** 先頭に添える小さな図形マーク(装飾・aria-hidden) */
  mark?: "square" | "ring" | "bar";
  /** 字面のバリエーション。ロゴらしさは weight と tracking の差で作る */
  type: string;
};

const mono = montserrat.className;

/** 取引先(架空) */
const CLIENTS: Wordmark[] = [
  { label: "NORDVEIL", mark: "square", type: `${mono} text-[17px] font-extrabold tracking-[-0.02em]` },
  { label: "白樺工機", type: "text-[16px] font-bold tracking-[0.18em]" },
  { label: "LUMENCLIFF", mark: "ring", type: `${mono} text-[15px] font-semibold tracking-[0.14em]` },
  { label: "三雲製作所", type: "text-[15px] font-medium tracking-[0.3em]" },
  { label: "STILLPORT", type: `${mono} text-[13px] font-bold tracking-[0.22em] uppercase` },
  { label: "蔵前アグリ", mark: "bar", type: "text-[16px] font-bold tracking-[0.06em]" },
  { label: "AOBADAI WORKS", type: `${mono} text-[13px] font-bold tracking-[0.2em]` },
  { label: "陽和リビング", type: "text-[15px] font-semibold tracking-[0.12em]" },
];

/** 掲載メディア(架空) */
const MEDIA: Wordmark[] = [
  { label: "MEGURO DESIGN REVIEW", type: `${mono} text-[12px] font-bold tracking-[0.24em]` },
  { label: "月刊ものづくり通信", type: "text-[15px] font-bold tracking-[0.08em]" },
  { label: "UI ARCHIVE TOKYO", mark: "ring", type: `${mono} text-[13px] font-semibold tracking-[0.18em]` },
  { label: "地域ビジネス白書", type: "text-[15px] font-medium tracking-[0.22em]" },
  { label: "STUDIO BRIEF", mark: "square", type: `${mono} text-[16px] font-extrabold tracking-[-0.01em]` },
  { label: "ローカル経済ジャーナル", type: "text-[14px] font-semibold tracking-[0.1em]" },
  { label: "NOTED WORKS", mark: "bar", type: `${mono} text-[15px] font-bold tracking-[0.08em]` },
  { label: "はたらく現場ラボ", type: "text-[15px] font-bold tracking-[0.06em]" },
];

function Mark({ shape }: { shape: NonNullable<Wordmark["mark"]> }) {
  const cls =
    shape === "square"
      ? "size-1.5 bg-amber-600"
      : shape === "ring"
        ? "size-2 rounded-full border border-amber-600"
        : "h-px w-3 bg-amber-600";
  return <span aria-hidden="true" className={`inline-block shrink-0 ${cls}`} />;
}

/**
 * ワードマーク1件。マーキー上では nowrap(流れる帯で折り返させない)、
 * 静止グリッドでは nowrap を外す(セル幅より長い社名がはみ出して切れるため)。
 */
function Logo({ item, nowrap = true }: { item: Wordmark; nowrap?: boolean }) {
  return (
    <span
      className={`flex items-center gap-2 text-gray-500 transition-colors duration-200 hover:text-gray-900 ${
        nowrap ? "whitespace-nowrap" : "text-center"
      }`}
    >
      {item.mark && <Mark shape={item.mark} />}
      <span className={item.type}>{item.label}</span>
    </span>
  );
}

/**
 * マーキー1レーン。同じ配列を2周ぶん並べて -50% まで流すと継ぎ目が消える。
 * animation クラスは呼び出し側からリテラルで渡す(Tailwind はソース上の文字列しか拾わないため、
 * duration を変数で組み立てるとクラスが生成されない)。
 */
function Lane({ items, animation }: { items: Wordmark[]; animation: string }) {
  return (
    <div className="group/lane relative flex-1 overflow-hidden [mask-image:linear-gradient(to_right,transparent_0,#000_7%,#000_93%,transparent_100%)]">
      <ul
        className={`flex w-max py-4 group-hover/lane:[animation-play-state:paused] sm:py-5 ${animation}`}
      >
        {[0, 1].map((copy) =>
          items.map((item) => (
            <li
              key={`${copy}-${item.label}`}
              className="mr-12 flex items-center sm:mr-14"
              // 2周目は見た目の継ぎ目のための複製なので読み上げ対象から外す
              aria-hidden={copy === 1 ? "true" : undefined}
            >
              <Logo item={item} />
            </li>
          )),
        )}
      </ul>
    </div>
  );
}

/**
 * レーン見出し。狭い幅では左カラムに置かず帯の上へ回す
 * (w-20 の縦罫カラムに「掲載メディア」を入れると和文が途中で折れて「掲載メデ/ィア」になる。
 *   左カラムに戻す sm: 以降も w-28 では内寸71pxに対し「掲載メディア」が73.5pxで溢れるため w-32 を取る)。
 * アンバー片はどちらの向きでも「罫線の起点」を指す位置に置き替える。
 */
function LaneLabel({ children }: { children: string }) {
  return (
    <div className="relative flex shrink-0 items-center border-b border-gray-200 px-5 py-2 text-[10px] font-bold tracking-[0.12em] whitespace-nowrap text-gray-500 sm:w-32 sm:border-r sm:border-b-0 sm:py-0 sm:text-[11px]">
      <span
        aria-hidden="true"
        className="absolute -bottom-px left-5 h-px w-6 bg-amber-600 sm:top-0 sm:-right-px sm:bottom-auto sm:left-auto sm:h-6 sm:w-px"
      />
      {children}
    </div>
  );
}

export default function LogoMarquee() {
  return (
    <section
      aria-labelledby="logo-marquee-title"
      className="w-full max-w-4xl overflow-hidden rounded-2xl border border-gray-200 bg-white"
    >
      {/* 見出し行 */}
      <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1 px-5 py-4 sm:px-7 sm:py-5">
        <h2
          id="logo-marquee-title"
          className="flex items-center gap-2 text-[11px] font-bold tracking-[0.14em] text-gray-900"
        >
          <span aria-hidden="true" className="inline-block size-1 shrink-0 bg-amber-600" />
          取引先・掲載実績
        </h2>
        <p className="text-[11px] text-gray-500">敬称略・順不同</p>
      </div>

      {/* マーキー(通常時) */}
      <div className="motion-reduce:hidden">
        <div className="flex flex-col border-t border-gray-200 sm:flex-row sm:items-stretch">
          <LaneLabel>取引先</LaneLabel>
          <Lane items={CLIENTS} animation="animate-[logo-marquee-run_42s_linear_infinite]" />
        </div>
        <div className="flex flex-col border-t border-gray-200 sm:flex-row sm:items-stretch">
          <LaneLabel>掲載メディア</LaneLabel>
          {/* 逆走は keyframes を増やさず direction:reverse で作る。速度もずらして位相を揃えない */}
          <Lane
            items={MEDIA}
            animation="animate-[logo-marquee-run_52s_linear_infinite] [animation-direction:reverse]"
          />
        </div>
      </div>

      {/* 静止グリッド(prefers-reduced-motion: reduce のときだけ表示) */}
      {/* 列数を欲張らない: 4列だと最長のワードマークがセル幅を超えて切れる(実測 736px 幅で 4列=160px、
          MEGURO DESIGN REVIEW は約200px)。3列までに抑え、さらに nowrap を外して折り返しを許す。 */}
      <div className="hidden motion-reduce:block">
        <div className="grid grid-cols-1 gap-px border-t border-gray-200 bg-gray-200 sm:grid-cols-2 lg:grid-cols-3">
          {[...CLIENTS, ...MEDIA].map((item) => (
            <div
              key={item.label}
              className="flex min-h-[62px] items-center justify-center bg-white px-4 py-4"
            >
              <Logo item={item} nowrap={false} />
            </div>
          ))}
        </div>
      </div>

      {/* 注記とテキストCTA */}
      <div className="flex flex-wrap items-center justify-between gap-x-6 gap-y-3 border-t border-gray-200 px-5 py-4 sm:px-7">
        <p className="max-w-md text-[11px] leading-[1.8] text-gray-500">
          掲載社名はすべて架空のサンプルです。実在の企業・媒体とは関係ありません。
        </p>
        <a
          href="#"
          className="group inline-flex items-center gap-2 text-[13px] 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>

      {/* keyframes はこのブロック内で完結させる */}
      <style>{`
        @keyframes logo-marquee-run {
          from { transform: translateX(0); }
          to { transform: translateX(-50%); }
        }
      `}</style>
    </section>
  );
}

shadcn CLI でプロジェクトに追加

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