No. 10

画廊の回遊 Gallery Kaiyu

モノクロ写真と明朝、圧倒的な余白で組む横スクロールの展示体験。UIは展示室の壁に徹し、写真の連なりが主役。scroll-snapによる横スクロール・ギャラリーとページ表示のインジケータ、額装のようにトリミングしない写真、図録様式のキャプションで構成する。現代写真ギャラリー・美術館・工芸作家のポートフォリオに。

画廊の回遊のスクリーンショット

仕様

typographymincho-modern
color-strategymonochrome
densityextreme-airy
layouthorizontal
ornamentphoto-dominant
motionrich
moodserene / luxury / wa
industryportfolio-creative / corporate / event
架空企業架空・現代写真ギャラリー「月白画廊(げっぱくがろう)」(京都・町屋改装、年6回企画展)
license商用利用可・改変可・クレジット不要

検証記録

verified_at: 2026-07-11

公式パイプライン検証(Claude Code / sonnet subagent): 別セッションのagentに DESIGN.md + tokens.css のみを渡し、別業種=陶芸家・八島汀の個人ポートフォリオで再生成(verification/ceramist-portfolio)。初回で不一致を検出→DESIGN.md改稿→r2で合格。横スクロールscroll-snapの回遊動線・縦書き見出し・図録様式キャプション・grayscale(1)フィルタの写真トーン・ページ表示インジケータまで業種替えでも一致(tier-A 🔴0/🟡0)。写真調達(PDのみ)も再現側で正しく実行された

DESIGN.md

DESIGN.mdtokens.css

https://3pt.co.jp/design-library/md/gallery-kaiyu/DESIGN.md

# DESIGN.md — 画廊の回遊 / gallery-kaiyu

> **使い方**: このファイルをプロジェクトに置き、AIに
> 「DESIGN.md に従って◯◯のサイトを作って」と指示してください。
> ライセンス: 商用利用可・改変可・クレジット不要(AIっぽくないデザイン見本帳 / サードパーティートラスト)

## 1. コンセプト

画廊の中を歩くように、横へ横へと進む体験。モノクロに統一した写真と明朝、
圧倒的な余白で、UIは展示室の壁に徹する。主役は常に作品(写真・図版)の連なりで、
サイトの構造は「順路」として設計する。縦スクロールは導入と実務情報だけに使い、
核となる展示は横スクロールのギャラリー動線に置く。

**キーワード**: 回遊 / モノクロ / 順路 / 図録 / 余白
**この見た目が向く場面**: ギャラリー・写真スタジオ・美術館・工芸作家のポートフォリオ・
建築設計事務所の作品集

## 2. タイポグラフィ

- 和文見出し: Zen Old Mincho / ウェイト 500〜600(大見出しは 500、節見出し・強調語は 600)
- 和文本文: Zen Old Mincho / ウェイト 400(このフォントに 300 は存在しないため、
  軽さは字間・行間・余白で作る。ウェイトを 300 相当に見せかけて別フォントを混ぜない)
- 欧文・数字: Cormorant Garamond(展覧会英題・作品の英題・年代・キャプション末尾の
  出典表記に使う。**必ずイタリックで、英題やキャプションの装飾として使う**。
  地の文の代替書体としては使わない。**Inter に置換しない**)
- 見出しスケール: h1(展覧会名・縦書き) clamp(38px, 6vw, 60px) / h2(節見出し)
  clamp(26px, 3.6vw, 34px) / h3(作品名) 19px / 本文 16px / キャプション 12.5〜13px
- 特記: 級数差だけでなく**字間と余白**で階層を作る。見出しは字間 0.3em 前後まで開く。
  英字ラベル(節番号・作品英題)は字間 0.16〜0.3em。空虚な機械翻訳のeyebrowにしない
  (§8参照)。**縦書き(`writing-mode: vertical-rl`)を展覧会名・章タイトルに使う。**
  一覧性が要る本文(作家紹介・利用案内・キャプション本文)は横書きのまま

## 3. カラー

**monochrome。彩度のある色を1色も足さない。**

| 役割 | 値 | 使用比率の目安 |
|---|---|---|
| 地(月白・ベース) | #f7f7f5 | 65% |
| 地(節替え) | #efeeea | — |
| 墨(見出し・強い要素) | #1f1e1c | 20% |
| 薄墨(本文弱・キャプション・英字ラベル) | #686765 | 10% |
| 罫線・境界 | #e3e2de | — |

- 配色ルール: 純黒 #000000・純白 #ffffff を使わない。彩度を持つ色(青・朱・金など)を
  一切追加しない。強調は「墨の濃さ」と「文字の大きさ・字間」でつける
- **薄墨 `#686765` は `#f7f7f5` 地で 5.3:1、`#efeeea` 地で 4.9:1 のコントラストがあり、
  13px 程度の小さなキャプション・ラベルでも AA (4.5:1) を満たす。** これより明るい
  灰色(例: `#8e8c87` 系、3:1前後)をキャプションや英字ラベルの既定色にしない
- 写真の上に文字を載せる場合は**不透明の帯**を敷く(§5・§9参照)。半透明の
  グラデーションスクリムやガラス風の`backdrop-filter`は使わない

## 4. レイアウト・余白

- コンテンツ最大幅: 1120px(本文カラムは 640px まで絞る)
- **レイアウトの核は横スクロール・ギャラリー(`scroll-snap-type: x mandatory`)。**
  1作品=1スナップ点。作品と作品の間は 88px 以上あける。縦スクロールは
  「入口(ヒーロー)」「出品作家」「アーカイブ」「利用案内」の4節だけに使う
- グリッドの性格: 縦スクロール部は中央寄せの1本道。横スクロール部は左右の
  余白(10vw程度)を大きく取り、最初と最後の作品も画面中央でスナップさせる
- セクション間余白: 上下 160〜170px 基準。**余白を埋めるための飾り線・ドット・
  アイコンを足さない**
- **ヒーローの構成("展示室の入口")**: 画面を左1/3・右2/3に分割し、右2/3に
  現在の企画展のメイン写真を裁ち落としで置く。左1/3には縦書きで
  「展覧会名(大)→ 英題イタリック(中)→ 会期(小)→ 回数ラベル(小)」を
  右から左へ並べる(`flex-direction: row-reverse` で縦書きの列を自然な順序にする)。
  **ボタンを置かない。** 誘導はスクロールという行為そのものに委ねる。
  下部に小さな一筆線のスクロールキュー(自作、装飾のみ)を置いてよい。
  「中央寄せ見出し+サブコピー+ボタン2つ」の定型を絶対に作らない

## 5. コンポーネントの質感

- ヘッダー・ナビゲーション: 初期は透明地・スクロールで地色に切り替わる
  (`backdrop-filter` は使わず不透明な `--color-bg` に切り替える)。ナビリンクは
  既定で薄墨、ホバーで墨色に変わり**字間が広がる**(下線は使わない。
  下線を使うテイストは別にあるため、ここでは字間の変化で差別化する)
- 角丸: 0px(すべて直角)/ 影: 使わない(box-shadow 禁止)/ 境界線: 1px solid
  `--color-line`
- ボタン: **原則使わない。** ヒーローに限らずサイト全体で主ボタン/副ボタンの
  対を作らない。誘導はテキストリンクとスクロールに委ねる
- 横スクロール・ギャラリーの1点(`.artwork`):
  - 写真は**トリミングしない**(`object-fit: contain`)。額装のように、作品ごとの
    アスペクト比のまま高さをそろえて幅は可変にする(横長は横に大きく、縦長は
    幅が狭く並ぶ=実際の展示壁の掛け方に近い)
  - 写真の下に**常時表示のキャプション**(figcaption)を置く。図録の様式で
    「作品番号 → 作品名(和文) → 英題(イタリック) → 作者・年代・技法・出典」の順
  - 写真にマウスオーバー(またはフォーカス)すると、写真下端に**不透明の墨帯**が
    せり上がり、英題だけが浮かぶ(§9コード例参照)。半透明のグラデーションで
    にじませない
  - 進行インジケータ(「01 / 06」)はギャラリー節の間だけ画面に固定表示する
- カード的要素: 「アイコン+見出し+説明」の角丸カードを**一切使わない**。
  出品作家・アーカイブの一覧は罫線区切りの横並びリスト(グリッド)で組む
- フォーム要素: 本テイストは問い合わせフォームを持たない
  (電話・メールのテキスト表記のみ)。フォームが必要な場合は下線1pxのみの
  枠なしinputとし、角丸は使わない
- 画像の扱い: 全写真に `grayscale(1) contrast(1.04)` を一律適用してトーンを統一する
  (§9)。角丸・影・枠線を使わない

## 6. モーション

- 量: rich。ただし「動き=リッチ」を安易な均一フェードで済ませない
- 使う動き:
  - ヒーロー写真・縦書きテキストは初回のみフェード+わずかな上昇(1.1〜1.4秒)。
    **このトリガーは `window.addEventListener('load', …)` または単発の `setTimeout`
    のみで発火させ、`requestAnimationFrame` は使わない**(rAFは横スクロール・
    ギャラリーのキャプション視差専用ですらなく、本テイストのどの用途にも使わない。
    tools/slop-scan.py の「カウントアップ疑い」検知は rAF + `.textContent`書き換え
    + 数値操作 の組み合わせをまとめて拾うため、進行インジケータ「01 / 06」用の
    正当な `.textContent` 書き換えがある本テイストでは、無関係な箇所で rAF を
    使うだけでも誤検知の引き金になる)
  - 縦スクロールの各節は IntersectionObserver で登場。**見出しは長く遅く
    (1.1秒)、リード文はさらに遅れて(1s, delay 0.2s)、リスト項目は
    0.7〜0.8秒**というように**役割ごとに速さを変える**。全要素を同じ0.6秒で
    フェードさせない
  - 横スクロール・ギャラリーは `scroll-snap` で進む。作品のキャプションは、
    スクロール位置に応じてわずかに横方向へずれ、CSSの `transition` で
    追いつく(§9)。**`requestAnimationFrame` は本テイストのどの用途にも
    一切使わない**(連続ループはもちろん、ヒーロー初回フェードや進行
    インジケータ更新のような単発トリガーとしても使わない)。初期表示・
    遅延発火はすべて `setTimeout` / `load`イベント / `IntersectionObserver`
    の組み合わせだけで実装する(実装をシンプルに保ち、スクロールイベント
    ごとの位置計算とCSS遷移だけで「少し遅れてついてくる」感触を作る)
  - 作品へのホバー/フォーカスで英題が不透明の帯とともにせり上がる(0.5秒)
  - ヘッダーはスクロールで透明→不透明に切り替わる(0.4秒)
- 使わない動き: 自動カルーセル、数字のカウントアップ、マーキー、パララックスの
  背景ブロブ、バウンス、拡大縮小のズーム演出、0.6秒一律の均一フェード
- `prefers-reduced-motion: reduce` 時はすべての transition/animation を止め、
  要素は最終状態で即表示する。横スクロールの `scroll-snap` 自体は動きの演出では
  なく構造なので維持してよい

## 7. 写真・イラスト・図版

- 写真がサイトの主役(`ornament: photo-dominant`)。5〜6点、CC0 /
  パブリックドメインのみを使用し、被写体は「静物・建築・風景・古典写真」など
  単体で展示作品として成立するものに限る。**人物のクローズアップ・ポートレートは
  避ける**(肖像の配慮。遠景に小さく人影が写り込む程度は可)
- 全写真に同一のグレースケール・フィルタを適用し、トーンを統一する(§9)。
  撮影年代・原版の色味が違っても、フィルタを通すことで「1つの展示」として
  一貫させる
- 写真はトリミングしない(`object-fit: contain`)。角丸・影・枠線を使わない。
  Pinterest風のグリッドに敷き詰めない(§8参照)
- キャプションは図録の様式(作品名・英題・作者/出典・年代・技法)で統一し、
  SNS風の短い一言キャプションにしない

## 8. 禁止事項(must not)

- Inter / Roboto / Arial / Noto Sans / Helvetica を使わない(明朝=Zen Old Mincho
  で組む。欧文は Cormorant Garamond のイタリックのみ)
- グラデーションを一切使わない(背景・文字・ボタンすべて)。box-shadow を使わない。
  border-radius は常に 0
- 純黒 #000000・純白 #ffffff を使わない。彩度のある色(青・朱・金・緑など)を
  1色も追加しない
- 中央寄せの定型ヒーロー(見出し+サブコピー+ボタン2つ)を作らない。
  ヒーローにボタンを置かない
- **「ギャラリー風=グリッドに写真を敷き詰める」に逃げない。** Pinterest型の
  マソンリーグリッド・正方形サムネイルの均一グリッドを作らない。
  横スクロールの1本の順路(`scroll-snap-type: x`)以外のギャラリー表現を
  メイン展示に使わない
- 写真に角丸をつけない。影をつけない。半透明グラデーションのスクリムで
  文字を載せない(載せる場合は不透明の帯のみ、§5・§9)
- 自動で流れるカルーセル・マーキー・タイプライター演出・数字のカウントアップを
  使わない
- 「アイコン+見出し+説明」の角丸カードを使わない(出品作家・アーカイブは
  罫線区切りのリストで組む)
- 絵文字をアイコンとして使わない(装飾に線画が必要な場合は自作インラインSVGのみ。
  本テイストでは基本的にアイコンを使わず、文字と罫線と写真だけで構成する)
- 全要素を同じ0.6秒の均一フェードで登場させない(§6の速度差を守る)
- 人物のクローズアップ写真を展示作品として使わない
- **`requestAnimationFrame` を一切使わない。** 本テイストの初期表示・遅延発火に
  許可される手段は次の3つのみ(排他リスト。これ以外は使わない):
  `window.addEventListener('load', …)` / 単発の `setTimeout` /
  `IntersectionObserver`。ヒーロー初回フェード・進行インジケータ更新・
  キャプション視差のいずれも、この3手段の組み合わせだけで実装する
  (§6・§9のコード例が唯一の正解実装。rAFを使った代替実装を作らない)

## 9. 実装ノート

```css
/* tokens.css — この変数セットをそのまま使うこと */
:root {
  --color-bg: #f7f7f5;
  --color-bg-alt: #efeeea;
  --color-ink: #1f1e1c;
  --color-text: #1f1e1c;
  --color-text-weak: #686765;  /* #f7f7f5地で5.3:1・#efeeea地で4.9:1。AA余裕あり */
  --color-line: #e3e2de;

  /* 変数には総称ファミリ(serif)を含めないこと。フォールバック連結が壊れる */
  --font-heading: "Zen Old Mincho";
  --font-body: "Zen Old Mincho";
  --font-latin: "Cormorant Garamond";

  --max-width: 1120px;
  --measure: 640px;
  --radius: 0;

  --photo-filter: grayscale(1) contrast(1.04); /* 全写真に一律適用するトーン統一フィルタ */
}
```

- フォント読み込み:
  `https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,400;1,500&family=Zen+Old+Mincho:wght@400;500;600;700&display=swap`
- 本文の font-family は `var(--font-latin), var(--font-body), serif` の順に指定する
  (英数字が自動で Cormorant になる)

**写真のトーン統一(全写真に必ず適用):**
```css
.artwork-frame img, .hero-photo-wrap img {
  filter: var(--photo-filter);
  object-fit: contain;   /* トリミングしない。cover でクロップしない */
}
```

**横スクロール・ギャラリーの骨格(1作品=1スナップ点。マソンリーグリッドにしない):**
```html
<div class="gallery-track" id="galleryTrack" role="list">
  <figure class="artwork" role="listitem">
    <div class="artwork-frame">
      <img src="..." alt="…具体的な被写体の説明…" loading="lazy">
      <div class="artwork-scrim" aria-hidden="true">
        <span class="artwork-scrim-title">English Title, Italic</span>
      </div>
    </div>
    <figcaption class="artwork-caption">
      <p class="ac-index">01</p>
      <h3 class="ac-title">作品名(和文)</h3>
      <p class="ac-en">English Title</p>
      <p class="ac-meta">作者 / 年代 / 技法・出典</p>
    </figcaption>
  </figure>
  <!-- ×5〜6 -->
</div>
```
```css
.gallery-track {
  display: flex;
  align-items: flex-end;
  gap: 88px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 24px 10vw 40px;
}
.artwork { flex: 0 0 auto; scroll-snap-align: center; display: flex; flex-direction: column; gap: 24px; }
.artwork-frame { position: relative; display: inline-flex; height: min(60vh, 560px); overflow: hidden; }
.artwork-frame img { height: 100%; width: auto; max-width: 74vw; }
```

**写真上に文字を載せる時は不透明の帯(半透明グラデーションのスクリムにしない):**
```css
.artwork-scrim {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--color-ink);   /* 不透明。rgba()の半透明にしない */
  padding: 16px 20px;
  opacity: 0; transform: translateY(10px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
.artwork:hover .artwork-scrim, .artwork:focus-within .artwork-scrim {
  opacity: 1; transform: translateY(0);
}
.artwork-scrim-title { color: var(--color-bg); font-family: var(--font-latin), serif; font-style: italic; }
/* var(--color-bg) on var(--color-ink) 地 = 15.5:1。写真の上でも常に高コントラストを確保する */
```

**キャプションの視差(少し遅れてついてくる。連続rAFループではなくCSS transitionで実現):**
```css
.artwork-caption { transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1); }
```
```js
// スクロールのたびに「作品中心 と 表示領域中心 の距離」からズレ量を算出し、
// transform を書き換えるだけ。あとはCSSのtransitionが遅れて追いつく動きを作る。
function updateCaptionDrift() {
  const trackRect = track.getBoundingClientRect();
  const centerX = trackRect.left + trackRect.width / 2;
  artworks.forEach((art, i) => {
    const r = art.getBoundingClientRect();
    const drift = Math.max(-18, Math.min(18, (centerX - (r.left + r.width / 2)) * 0.05));
    captions[i].style.transform = `translateX(${drift.toFixed(1)}px)`;
  });
}
track.addEventListener('scroll', updateCaptionDrift, { passive: true });
```

**ヒーロー初回フェードの起動(`setTimeout`のみ。rAFは使わない):**
```js
// 唯一の正当な実装。requestAnimationFrame を含む他の起動方法(rAF単発呼び出し、
// rAFループでの監視、rAFによるクラス付与のタイミング調整など)は一切使わない。
window.addEventListener('load', function () {
  setTimeout(function () {
    heroTextInner.classList.add('is-visible');
    heroPhotoWrap.classList.add('is-visible');
  }, 60);
});
```
```css
.hero-text-inner, .hero-photo-wrap {
  opacity: 0; transform: translateY(16px);
  transition: opacity 1.3s ease, transform 1.3s ease;
}
.hero-text-inner.is-visible, .hero-photo-wrap.is-visible {
  opacity: 1; transform: translateY(0);
}
```

**縦書きヒーロー(右から左へ列が並ぶ自然な順序にする):**
```css
.hero { display: flex; min-height: 100vh; }
.hero-text { width: 33.333%; border-right: 1px solid var(--color-line); }
.hero-text-inner { display: flex; flex-direction: row-reverse; align-items: flex-start; gap: 28px; }
.hero-kanji, .hero-en, .hero-dates, .hero-no { writing-mode: vertical-rl; text-orientation: mixed; }
.hero-photo-wrap { width: 66.666%; }
.hero-photo-wrap img { width: 100%; height: 100%; object-fit: cover; filter: var(--photo-filter); }
```

**節の登場(役割ごとに速さを変える。全要素同じ0.6秒にしない):**
```css
.eyebrow { transition: opacity 0.8s ease, transform 0.8s ease; }
.section-head h2 { transition: opacity 1.1s ease 0.1s, transform 1.1s ease 0.1s; }
.section-lead { transition: opacity 1s ease 0.2s, transform 1s ease 0.2s; }
.archive-item, .artist-item { transition: opacity 0.7s ease, transform 0.7s ease; } /* リストは短め */
```

- モバイル(768px以下を目安): ヒーローは縦積みに変える(写真を上、縦書きテキストを下)。
  横スクロール・ギャラリーはネイティブのタッチスワイプに任せ(`overflow-x: auto` の
  ままでよい)、進行インジケータ「01 / 06」を画面下中央に固定表示する
- `prefers-reduced-motion: reduce` では全 transition/animation を `none` にし、
  最終状態で表示する