## 概要

zudo-docはデフォルトで、ページ間の遷移をブラウザのフルリロードなしで行います。新しいドキュメントを取得してウィンドウ全体を再描画する代わりに、ページのbodyだけをその場で差し替え、周囲のchrome（ヘッダー、サイドバー、フッター）はそのまま保ち、クロスフェードで変化をアニメーションさせます。その結果、シングルページアプリ（SPA）のような体験になります。高速でなめらかで、ハードナビゲーションで生じる白い瞬きがありません。

この挙動は**ダイナミックページトランジション**と呼ばれ、**デフォルトで有効**です。

[Dynamic content unavailable in text export]

ダイナミックページトランジションは純粋に表示上の拡張です。サイトは依然として完全な静的エクスポートであり、すべてのページは独立したHTMLとして存在し、JavaScriptなしでも動作します。トランジションレイヤーはJavaScriptが利用可能なときにアプリ内リンクのクリックを横取りするだけで、それ以外の場合は通常のナビゲーションにフォールバックします。



## 何をするのか

ダイナミックナビゲーションは3つの要素を組み合わせています。

1. **同一ドキュメント内のbody差し替え。** 内部リンクをクリックすると、ルーターはバックグラウンドで遷移先ページを取得し、ドキュメント全体を破棄して再構築することなく、現在のページのbodyの内容を新しいページの内容に置き換えます。ヘッダー、サイドバー、フッターのDOMノードはナビゲーションをまたいで保持されるため、スクロール位置や状態が維持されます。
2. **履歴の処理。** ルーターは`pushState`でブラウザ履歴を更新するため、URLが変化し、戻る／進むボタンは通常のナビゲーションとまったく同じように機能します。戻るボタンを押すと`popstate`イベントが発火し、ルーターはこれを処理して前のページへ差し替えます。やはりフルリロードはありません。
3. **View Transitions APIによるクロスフェード。** この差し替えはブラウザの[View Transitions API](https://developer.mozilla.org/ja/docs/Web/API/View_Transitions_API)でラップされており、新旧の状態のスナップショットをキャプチャしてその間をアニメーションさせます。chrome以外のコンテンツはクロスフェードし、ヘッダー、サイドバー、フッターはそれぞれ独自のトランジションレイヤーに抽出されて静止したまま保たれるため、ちらつきません。

[Dynamic content unavailable in text export]

chrome要素（`<header>`、デスクトップサイドバー、`<footer>`、サイドバートグルボタン）には安定した`view-transition-name`が付与されており、これによってクロスフェードが記事領域だけをアニメーションさせられます。あるページにchrome要素が存在し、次のページには存在しない場合（たとえばサイドバーのあるドキュメントページから、サイドバーのないトップページへ移動する場合）、その要素はスナップせずにコンテンツと同期してフェードイン／フェードアウトします。



## ナビゲーション中のアイランド状態

クロームの祖先を保持しても、内側のすべてのアイランドが固定されるわけではありません。実効的なidentityとシリアライズされたpropsが変わらなければ、ネイティブランタイムはDOM、handle、スコープ内の状態を保持します。identityやpropsの変更時は描画スコープを再作成し、構造やスケジューリングの変更時は対象の永続化境界を無効にします。セッションが所有するホストpropsには`data-zd-props-preserve`を使えます。ページ由来のpropsは更新を維持します。[Host Chrome Bindings](https://zudo-doc.takazudomodular.com/ja/docs/reference/host-chrome-bindings)を参照してください。

## ローディングオーバーレイ

遷移先ページの取得中、zudo-docは中央にスピナーを配置したフルスクリーンの**ローディングオーバーレイ**を表示します。ナビゲーションが始まると表示され、新しいコンテンツが差し替えられると同時に消えるため、クリックが受け付けられたことが即座にフィードバックされます。取得に少し時間がかかる低速な接続では特に有用です。

クリックしたリンクやボタンも、そのナビゲーションが進行中の間は短時間ハイライトされるため、どのアクションが実行中なのかが分かります。

[Dynamic content unavailable in text export]

オーバーレイはクリックから差し替えまでの短い間だけ表示されます。ローカルでの高速なナビゲーションではほぼ一瞬で消えることがありますが、これは想定どおりの動作です。その役割は、低速な取得で生じる体感上の遅延を覆い隠すことであり、人為的な間を加えることではありません。



## モーション低減

zudo-docはユーザーの`prefers-reduced-motion`設定を尊重します。訪問者がOSレベルでモーション低減を有効にしている場合、

- クロスフェードアニメーションは**即時の差し替え**に縮退します。新しいページがフェードやスライドなしで即座に表示されます。
- スピナーは回転を止め、アニメーションする代わりに**静止**した暗めのリングとして表示されます。

ナビゲーションそのもの（同一ドキュメント内の差し替え、履歴の処理）は同じように動作し、取り除かれるのはモーションだけです。これにより、アニメーションに敏感なユーザーにとって快適な体験を保ちつつ、ソフトナビゲーションのパフォーマンス上の利点も維持されます。

## 機能の切り替え

ダイナミックページトランジションは`dynamicPageTransition`設定で制御され、**デフォルトで有効**です。

### zfb.config.tsで

```ts title="zfb.config.ts"
export default defineConfig(
  zudoDoc({
    // ...
    dynamicPageTransition: true,
  }),
);
```

`false`に設定すると、ダイナミックトランジションが完全に無効になります。

### プリセットジェネレーターで

`create-zudo-doc`で新しいプロジェクトを生成する際、このオプションは**「Dynamic page transition」**というラベルで表示され、デフォルトで有効です。CLIフラグで非対話的に制御できます。

```sh
# Explicitly enable (this is the default)
create-zudo-doc my-docs --dynamic-page-transition

# Disable — generate a project with plain full-page navigation
create-zudo-doc my-docs --no-dynamic-page-transition
```

## 「オフ」が意味すること

ダイナミックページトランジションを無効にすると、ナビゲーションは**通常のフルページ読み込み**にフォールバックします。

- すべての内部リンクが通常のブラウザナビゲーションを発生させ、ドキュメントが取得されてページ全体が再描画されます。
- **SPAのbody差し替えはありません**。ヘッダー、サイドバー、フッターは保持されるのではなく、ナビゲーションごとに再構築されます。
- **ローディングオーバーレイ**も**クロスフェード**もありません。ブラウザのデフォルトのナビゲーション挙動になります。

これはトランジション用のJavaScriptを一切持たない、よりシンプルなモデルです。クラシックなマルチページ体験を好む場合、ソフトスワップルーターとカスタムのクライアントサイドスクリプトとの相互作用を排除したい場合、あるいは単にアニメーションが不要な場合に選んでください。

[Dynamic content unavailable in text export]

機能をオフにしても、コンテンツやルートは一切変わりません。変わるのはブラウザがページ間を移動する方法だけです。どちらの場合でも、すべてのページは独立した静的エクスポートのHTMLドキュメントのままです。



## 独自レイアウトでオーバーレイを配線する

`DocLayoutWithDefaults`でレイアウトを組んでいる場合、ローディングオーバーレイはすでに配線済みです。何もする必要はないので、このセクションは読み飛ばして構いません。ここで説明するのは、`DocLayoutWithDefaults`を使わずに独自のレイアウトを組み立てており、同じオーバーレイの挙動を取り入れたい場合だけです。

オーバーレイは`@takazudo/zudo-doc`から、自分で配線する2つの部品として提供されます。`PageLoadingOverlay`コンポーネントと、そのスタイルシート`@takazudo/zudo-doc/page-loading.css`です。

### コンポーネントをマウントする

`PageLoadingOverlay`をインポートし、bodyの末尾のスロット（他のbody-endプロバイダーをマウントしているのと同じ場所）に**レイアウトごとに一度だけ**レンダリングします。これはサーバーレンダリングされ、**ハイドレーションは不要**です。表示・非表示をナビゲーションのライフサイクル（`zfb:before-preparation` / `zfb:after-swap`）に自動で結びつける小さなインラインスクリプトを出力するため、登録すべきislandも、追加すべき`client:*`ディレクティブもありません。

```tsx
import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";

// ...inside your layout, at the end of <body>:
<PageLoadingOverlay />;
```

コンポーネントが受け取るプロパティは、オプションの`id`が1つだけです。これはオーバーレイ要素のDOM `id`を上書きします（テストなど、1ページ上に複数のオーバーレイが共存しうる場合にのみ役立ちます）。通常は指定せず、安定したデフォルト値のままにしてください。

[Dynamic content unavailable in text export]

マウントはレイアウトごとに**一度だけ**にしてください。2つ目のインスタンスを置くと、オーバーレイ要素とブートストラップスクリプトが二重に出力されてしまいます。



### スタイルシートをインポートする

コンポーネントはオーバーレイのマークアップを出力しますが、CSSは出力しません。提供されているスタイルシートを、他のパッケージCSSのインポートと並べて、`global.css`の**先頭**でインポートしてください。

```css
@import "@takazudo/zudo-doc/page-loading.css";
```

[Dynamic content unavailable in text export]

このインポートは、`global.css`内のあらゆるルール文よりも**前**に置かなければなりません。CSSではすべての`@import`ルールが他のルールに先行する必要があります。セレクターや`:root`ブロックの後ろに置くと、インポートは黙って破棄され、オーバーレイはスタイルが当たらないまま表示されてしまいます。



### スタイルシートが参照するホストトークン

このスタイルシートはいくつかのホストのデザイントークンを読み取ります。**いずれもオプション**で、それぞれに妥当なフォールバックが組み込まれているため、これらを一切定義していない素の状態のプロジェクトでもオーバーレイは動作します。

| トークン                       | 用途                                       | フォールバック                                                     |
| ------------------------------ | ------------------------------------------ | ------------------------------------------------------------------ |
| `--color-page-loading-overlay` | スクリム（オーバーレイの背景）             | `color-mix(in oklch, var(--color-overlay) 60%, transparent)`       |
| `--color-overlay`              | 上記スクリムのフォールバックの下地となる色 | `#000`（上記フォールバック内でのみ使用）                           |
| `--color-page-loading-spinner` | スピナーのボーダー色                       | `#fff`                                                            |
| `--color-accent`               | 遷移待ち中のリンク／ボタンのハイライト     | _（フォールバックなし — 未設定ならハイライトは単に省略される）_    |
| `--z-index-modal`              | オーバーレイの重ね順                       | `100`                                                            |

プロジェクトがzudo-docの標準的なセマンティックトークンをすでに定義していれば、オーバーレイは自動的にそれらを拾い、追加の作業なしでテーマに馴染みます。

### スクリムを独立して調整する

スクリムは**専用**のトークン`--color-page-loading-overlay`を読むようになり、ライトボックス（画像拡大）の背景（`--color-overlay`）とは**切り離されています**。これを設定すれば、他のオーバーレイに一切触れることなく、ローディングのスクリムだけを調整できます。

```css
:root {
  /* A light, frosted brand scrim for the loading overlay only —
     the image lightbox keeps using --color-overlay. */
  --color-page-loading-overlay: color-mix(
    in oklch,
    var(--color-bg) 70%,
    transparent
  );
}
```

`--color-page-loading-overlay`が未設定の場合、スタイルシートは`--color-overlay`を不透明度60%で混ぜた色にフォールバックします。そのため既存のプロジェクトは変更なしでこれまでどおりの見た目を保ちます。

### スピナーに専用トークンがある理由

スクリムは**テーマに依存しません**。ライトでもダークでも、常に暗いウォッシュがかかります。そのためスピナーのリングもページの前景色に追従させず、専用の`--color-page-loading-spinner`トークンで明るい色に固定しています。ライトのカラースキームでリングが暗く反転すると、常に暗いスクリムに対して3.02:1しか出ません。固定した白いリングはDefault Lightで6.18:1、Default Darkで20.13:1となり、WCAG 1.4.11の非テキストの下限である3:1をどちらでもクリアします。

同じ理由で、モーション軽減時のリング（回転する弧ではなく、静止した完全な円）には不透明度の低減を**かけていません**。50%まで落とすと、ライトのカラースキームでは2.85:1に合成され、下限を下回ります。

スクリムを明るい色に変更する場合は、リングも合わせて上書きしてください。

```css
:root {
  --color-page-loading-overlay: color-mix(in oklch, var(--color-bg) 70%, transparent);
  /* A scheme-aware scrim wants a scheme-aware ring. */
  --color-page-loading-spinner: var(--color-fg);
}
```

### 手でコピーしたオーバーレイのルールを削除する

このスタイルシートが提供される前は、独自レイアウトの利用者はオーバーレイ・スピナー・遷移待ちリンクのルールを自分の`global.css`に**手でコピー**する必要がありました。それをしていた場合は、`@import`を採用する際に**コピーしたルールを削除**してください。両方を残すと、`@keyframes page-loading-spin`が重複し、`.page-loading-overlay` / `.page-loading-spinner`の宣言が衝突します。どちらのルールが勝つかは記述順に依存し、間違えやすいものです。提供されるスタイルシートが今後は唯一の真実の源（single source of truth）です。