BLK-23Blocks
Client & Press Logo Marquee
A two-lane infinite logo strip for client and press mentions. No logo images: each fictional wordmark varies in weight, tracking, size and leading glyph, mixing Japanese and Latin type so the row reads as logos rather than a list. The lanes run in opposite directions at different speeds (42s/52s), both edges are cut with mask-image, and hovering pauses the lane. Under prefers-reduced-motion the whole marquee is dropped and the same names are shown as a static hairline grid.
- Added:
- 2026-08-13
- Dependencies:
- None
- tags
- #logos #marquee #section #block #motion #infinite-loop #hairline #brand #no-image
Preview
取引先・掲載実績
敬称略・順不同
取引先
- NORDVEIL
- 白樺工機
- LUMENCLIFF
- 三雲製作所
- STILLPORT
- 蔵前アグリ
- AOBADAI WORKS
- 陽和リビング
掲載メディア
- MEGURO DESIGN REVIEW
- 月刊ものづくり通信
- UI ARCHIVE TOKYO
- 地域ビジネス白書
- STUDIO BRIEF
- ローカル経済ジャーナル
- NOTED WORKS
- はたらく現場ラボ
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>
);
}
Add to your project via shadcn CLI
npx shadcn@latest add https://designs.first-ch.com/r/logo-marquee.json