slide-authoring について
slide-authoring は、React で open-slide のページを作成・編集するための技術リファレンススキルです。ファイル仕様、キャンバスとレイアウトの制約、デザインシステム、アセット、発表者ノートを定めています。
主な機能
- 01React ページのファイル仕様
- 021920 × 1080 キャンバス規則
- 03文字組みと余白の指針
- 04デザイントークンとテーマ対応
- 05アセットと発表者ノートの規則
活用シーン
- 01新しいスライドページの作成
- 02既存スライドの編集
- 03ページレイアウトの統一
- 04アセットや発表者ノートの追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ open-slide/ open-slide --skill slide-authoringインストール後、Claude Code で/slide-authoringを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/assets.mddesign-system.mdmorph.mdpage-numbers.mdsteps.mdtransitions.mdwebfonts.md詳細
open-slide ページの作成
このスキルは、slides/<id>/index.tsx 内で行うすべての作業の技術リファレンスです。ワークフローは担当しません。
create-slideは「新しいデッキの下書きを作成する」役割を担います。ユーザーにスコープに関する質問をしたうえで、方法をこのスキルに委ねます。apply-commentsは「インスペクターマーカーを処理する」役割を担います。マーカーを見つけて編集を適用しますが、編集自体にはここに記載されたルールが適用されます。current-slideは、指示語による参照(「このページ」「今表示しているスライド」)を具体的なslideIdとpageIndexに解決します。ユーザーが現在のスライドに言及しているものの、名前を指定していない場合は、まずこれを参照してから、編集方法についてこのスキルに戻ってください。- そのほかの個別のスライド編集(手動での調整や一度限りの修正)でも、ファイルに手を加える前にこのスキルを参照してください。
これらの経路のいずれかで、ページ用の React コードを記述する段階に進んだら、これが信頼できる唯一の情報源です。以下の内容をほかのスキルに重複して記載せず、代わりにここへリンクしてください。
プリミティブのリファレンス
各フレームワークプリミティブには、このスキルの references/ に完全なリファレンスファイルがあります。そこには仕様、具体例、各プリミティブ固有のアンチパターンが記載されています。このファイルには、常に適用されるルールと各プリミティブの簡単な概要のみを記載しています。ページでプリミティブを使う前に、該当するリファレンスファイルを読んでください。
| プリミティブ | 事前に読むタイミング | ファイル |
|---|---|---|
design const + var(--osd-X) tokens | 新しいスライドを作成する際(デフォルトの基準) | references/design-system.md |
アセット + <ImagePlaceholder> | 画像や動画を読み込むとき、またはプレースホルダーを残すとき | references/assets.md |
| Web フォント | システムフォント以外を読み込むとき | references/webfonts.md |
useSlidePageNumber() | ページ番号のフッターを表示するとき | references/page-numbers.md |
<Steps> / <Step> | ページの表示順を段階的に設定するとき | references/steps.md |
SlideTransition | 入場または退場のアニメーションを指定するとき | references/transitions.md |
MorphElement + morph | 複数のページにわたって共有要素をモーフィングするとき | references/morph.md |
必須ルール
- スライドは
slides/<kebab-case-id>/の下に置いてください。 - エントリポイントは
slides/<id>/index.tsxです。画像、動画、フォントはslides/<id>/assets/の下に置いてください。 package.json、open-slide.config.ts、ほかのスライドには手を加えないでください。- 依存関係を追加しないでください。使用できるのは
react、@open-slide/core、標準の Web API のみです。 - スライドは 1つの
index.tsxとassets/で構成されます。それ以外は追加しないでください。兄弟ファイルとして.tsx/.tsファイル(Card.tsx、components/、helpers.tsなど)を作成しないでください。ヘルパーコンポーネントや定数はindex.tsx内に記述してください。また、README.mdなどの説明ファイルも作成しないでください。発表者ノートや講演原稿は Markdown ファイルではなく、notesエクスポートに記述します(下記の発表者ノートを参照)。
ファイルの仕様
// slides/<id>/index.tsx
import type { Page, SlideMeta } from '@open-slide/core';
const Cover: Page = () => <div>…</div>;
const Body: Page = () => <div>…</div>;
export const meta: SlideMeta = {
title: 'My slide',
createdAt: '2026-05-16T12:00:00Z',
};
export default [Cover, Body] satisfies Page[];
export defaultは、ページごとに1つずつ順番に並んだ、props を受け取らない React コンポーネントの空でない配列です。meta.title(任意)は、スライドのヘッダーに表示されます。デフォルトはフォルダー名です。- スライド ID は kebab-case のフォルダー名です。短く、内容がわかる名前を選んでください(
q2-roadmap、team-offsite-2026など)。 meta.theme(任意)は、themes/内のテーマを基に作成されたスライドであることを示します。ID は<id>.mdのベース名と一致している必要があります。スライドカードに戻るリンクのチップを表示し、/themes/<id>にスライドを一覧表示します。スライドが登録済みのテーマを基にしていない場合は、省略してください。meta.createdAt(任意)は、スライドのひな形を作成したときに一度設定するISO 8601 形式の文字列リテラルです(例:'2026-05-16T12:00:00Z')。ホームページでは、デフォルトの「新しい順」ソートに使用します。新しいスライドには必ず設定してください。ファイルを書き込む直前に、Bash 経由でnode -e "console.log(new Date().toISOString())"を実行し、その出力を正確に貼り付けてください。タイムスタンプを記憶で入力しないでください。日付や時刻を間違える可能性があります。通常の文字列リテラルでなければなりません(スライド自体にnew Date(...)やインポートを記述しないでください)。フレームワークはビルド時にモジュールを実行するのではなく、正規表現で値を読み取ります。
スピーカーノート(notes エクスポート)
このフレームワークにはスピーカーノートが組み込まれています。index.tsx に任意の notes エクスポートを追加でき、デフォルトのページ配列とインデックスが対応します。ビューアーではノートドロワーに表示され、プレゼンモードではタイマーの横にあるプレゼンタービューに表示されます。
ユーザーから、言語を問わず、スピーチ原稿、トークトラック、またはプレゼンターノートを求められた場合は、Markdown/テキストファイルではなく、必ずこのエクスポートに記述してください。 script.md や notes.md は実行時には表示されません。プレゼンタービューが読み込むのは notes エクスポートだけです。
export const notes: (string | undefined)[] = [
'Open with the analyst quote, then introduce yourself.',
undefined,
`Walk through the three pillars, one beat each.
Pause for questions before moving on.`,
];
- ページごとに1つ、
export default配列と同じ順序で記述します。notes[0]は最初のページに対応します。 - エントリーはプレーンテキストです。改行は保持されますが、Markdown はレンダリングされません。複数行のノートにはテンプレートリテラル、1行のノートには通常の文字列を使用します。
- ノートがないページには
undefinedを使用します。末尾のundefinedエントリーは省略できます。 - ページを追加、削除、並べ替えるときは、同じ編集内で
notesの対応も調整してください。インデックスがずれると、それ以降のすべてのページに誤った原稿が気づかれないまま紐づきます。 - ノートは画面に表示されている内容の要約ではなく、実際に話す内容として記述します。導入、つなぎ、引用する数値、タイミングの合図などを含めてください。
既存のスライドを編集する
完成したスライドは、一般に1000~1800行になります。1つのページだけを編集する場合は、ファイル全体を読まないでください。まず対象のページを特定し、その範囲だけを読みます。
grep -n ": Page = " slides/<id>/index.tsx
これにより、すべての const Foo: Page = … 宣言が行番号付きで一覧表示されます。Read を offset と limit とともに使って対象ページを読みます(通常、1ページとそのヘルパーコンポーネントを把握するには約150行で十分です)。ページをまたぐコンテキスト(配色の確認、並べ替え、デザイン定数の調整など)が必要な場合に限り、ファイル全体を読んでください。
キャンバス
各ページは固定サイズの 1920 × 1080 キャンバスに描画されます。フレームワークが拡大縮小するため、ビューポートが文字どおり1920×1080であるかのようにデザインしてください。
font-size、パディング、位置指定には絶対ピクセル値を使用します。rem、vw/vh、文字サイズに対する%は使用しないでください。- 各ページのルート要素はキャンバス全体に広げます。
width: '100%'; height: '100%' - インラインの
style={{ … }}を優先します。読み込む CSS はグローバルに適用されるため、クラス名のスコープを慎重に設定してください。
文字サイズの目安(まずはこの値から調整してください)
| 要素 | サイズ |
|---|---|
| ヒーロータイトル | 140–200px |
| セクション見出し | 80–120px |
| ページ見出し | 56–80px |
| 本文 | 32–44px |
| キャプション/ラベル | 22–28px |
間隔
- コンテンツのパディング:キャンバスの端から100–160px。テキストを端に触れさせないでください。
- 行の高さ:見出しは1.2、本文は1.5~1.7。
- 要素間の余白:32–64px。
縦方向の予算 — コンテンツは必ず1080px以内に収める
キャンバスはスクロールしません。1080pxの下端を超えた部分は、予告なく切り取られます。JSXを書く前に紙に計算を書き出し、ページが収まることを確認してください。スライドが崩れる原因の第1位です。確認しない限り、はみ出すものと考えてください。
使用可能な高さ = 1080 − top_padding − bottom_padding。左右それぞれ120pxのパディングなら840px、それぞれ160pxなら760pxです。まずパディングを決め、その予算内でデザインしてください。
要素の高さ = font_size × line_height × number_of_lines。2行に折り返される箇条書きは、2行分として数えます。次の要素を合計する前に、その下の間隔(32〜64px)も加えてください。
計算例 — パディング120pxの単一コンテンツページ(予算 = 840px):
| 要素 | 高さ |
|---|---|
| 見出し:80px × 1.2 × 1行 | 96px |
| 間隔 | 64px |
| 本文の段落:40px × 1.6 × 3行 | 192px |
| 間隔 | 48px |
| 箇条書き5項目:各40px × 1.6 × 1行 | 320px(5 × 64px) |
| 箇条書き間の間隔4つ:各24px | 96px |
| 合計 | 816px ✅ 840px以内 |
見出しを120pxに変えるか、箇条書きを1つ追加すると、予算を超えます。ページを書く前に、必ずこのように1ページずつ確認してください。
ページごとのルール:
- 見出し+本文、または見出し+短い箇条書き5項目以内のどちらかにします。本文と長い箇条書きリストの両方を載せないでください。
- 箇条書きは、選んだフォントサイズで1行に収めてください。折り返される場合は、文章を短くするか、独立したページに移してください。
- ヒーロータイトルページ(140〜200px)に載せるのは、タイトル+サブタイトル1つ+必要に応じて短い補足見出しだけです。それ以外は載せません。
- セクション見出し(80〜120px)のページには、ほかにほとんど何も載せません。
- 収めるためにパディングを増やしたり、スケールの下限未満に文字を縮小したり、本文の行間を1.4未満に詰めたりする必要があるなら、代わりに2ページに分割してください。予算が厳しいときは、常に分割するのが正解です。
はみ出しを隠すために、overflow: auto/scroll、負のマージン、変形を絶対に使わないでください。キャンバスのサイズは固定です。切り取られたコンテンツは失われます。
ビジュアルの方向性
統一感のある見た目を選び、すべてのページで維持してください。
- 配色 — 背景色1色、主要なテキスト色1色、アクセントカラー1色、控えめな色1色にします。bg/text/accentは
design定数に設定します(下記のデザインシステムを参照)。控えめな色などの追加色は、通常の定数に設定します。 - タイポグラフィ — ディスプレイ用フォント1種類と本文用フォント1種類を使います。ユーザーから指定がない限り、システムフォントスタックを使ってください。見出しは太字(800〜900)、本文は標準の太さ(400〜500)にします。
- レイアウトグリッド — コンテンツのパディングを1つ(例:120px)選び、統一します。左揃えは編集的な印象に、中央揃えは格式ある印象になります。
- 美的スタイルの統一 — 次のうち1つだけを選びます:ミニマル、マキシマリスト、エディトリアル、レトロ、ブルータリスト、ソフト/パステル、ネオン、紙/印刷。混在させないでください。
frontend-designスキルが利用できる場合、ユーザーが大胆なデザインを求めているときは、より深い美的指針を得るために参照してください。
Webフォント
デフォルトではシステムフォントスタックを使います。Webフォントは必要な場合に限って使ってください(ブランドフォント、またはシステムフォントの対応が不十分なCJK/タイ語/アラビア語の場合)。まずreferences/webfonts.mdを読んでください。ページコンポーネント内でスタイルシートを読み込むと、マウントされたページごとに@font-face一式が一度登録されます。また、CJKファミリーではサブセット化が必要です。
テーマ
プロジェクトルートに themes/<id>.md があり、スライドがそのテーマに従う場合、テーマファイルがこのスキルのデフォルト設定より優先されます。配色、タイポグラフィ、レイアウトのパディング、Title/Footer コンポーネントはテーマファイルの指定に従ってください。このセクションのほかの内容を適用する前に、テーマファイルを読み込んでください。
テーマは create-theme スキルによって作成される純粋なドキュメントです。配色と、貼り付けてすぐ使える Title/Footer/Eyebrow コンポーネントを、そのままスライドにコピーしてください。テーマのフロントマターに mode: dark または mode: light がある場合は、それをスライドの背景モードとして扱ってください(たとえば、インポートするロゴのバリエーションを選ぶ場合など)。
デザインシステム(スライドごとに任意)
スライドでは、index.tsx の先頭で型付きのデザイントークンを宣言し、インラインスタイル内で var(--osd-X) を使って参照できます。宣言の形式は export const design: DesignSystem = { palette, fonts, typeScale, radius } です。フレームワークはキャンバスのルートに CSS 変数を挿入し、開発用 UI の Design パネルからリアルタイムで調整できます。
原則として使用してください。 新しいスライドには必ず design 定数を宣言し、生成後もパネルから調整できるようにしてください。意図的に配色を固定する単発スライドの場合に限り、通常の配色定数に切り替えてください(代替方法については references/design-system.md を参照してください)。
references/design-system.md には、トークンの全体構造、2 種類の参照方法(var(--osd-X) と design.X の直接参照)、Design パネルの動作、およびパネルの AST ライターが必要とする形式上の制約が記載されています。定数を記述する前に読んでください。
スターターテンプレート
import type { DesignSystem, Page, SlideMeta } from '@open-slide/core';
export const design: DesignSystem = {
palette: { bg: '#0f172a', text: '#f8fafc', accent: '#fbbf24' },
fonts: {
display: 'system-ui, -apple-system, sans-serif',
body: 'system-ui, -apple-system, sans-serif',
},
typeScale: { hero: 180, body: 40 },
radius: 12,
};
// Extra colors / sizes outside the DesignSystem shape stay as plain consts.
const muted = '#94a3b8';
const fill = {
width: '100%',
height: '100%',
fontFamily: 'var(--osd-font-body)',
} as const;
const Cover: Page = () => (
<div
style={{
...fill,
background: 'var(--osd-bg)',
color: 'var(--osd-text)',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '0 160px',
}}
>
<div style={{ fontSize: 28, color: 'var(--osd-accent)', letterSpacing: '0.2em' }}>
CHAPTER 01
</div>
<h1
style={{
fontFamily: 'var(--osd-font-display)',
fontSize: 'var(--osd-size-hero)',
fontWeight: 900,
margin: '32px 0',
lineHeight: 1.05,
}}
>
The Big Idea
</h1>
<p style={{ fontSize: 'var(--osd-size-body)', color: muted, maxWidth: 1200 }}>
A short subtitle that explains what this slide is about.
</p>
</div>
);
const Content: Page = () => (
<div style={{ ...fill, background: 'var(--osd-bg)', color: 'var(--osd-text)', padding: 120 }}>
<h2 style={{ fontFamily: 'var(--osd-font-display)', fontSize: 80, fontWeight: 800, margin: 0 }}>
Section heading
</h2>
<ul style={{ fontSize: 'var(--osd-size-body)', lineHeight: 1.6, marginTop: 64, paddingLeft: 48 }}>
<li>One clear point per line</li>
<li>Keep to 3–5 bullets</li>
<li>Let the space breathe</li>
</ul>
</div>
);
export const meta: SlideMeta = {
title: 'The Big Idea',
createdAt: '2026-05-16T12:00:00Z',
};
export default [Cover, Content] satisfies Page[];
アセット
スライド固有のアセットは slides/<id>/assets/ に配置し、ES モジュールとしてインポートします(import hero from './assets/hero.jpg')。複数のデッキで共有するグローバルアセット(ロゴ、アバター、繰り返し使うアイコン)は、プロジェクトルートの assets/ に配置し、@assets エイリアスを使ってインポートします。純粋なテキストのスライドでは、slides/<id>/assets/ は作成しないでください。
references/assets.md では、インポート形式(モジュールと new URL(...))、@assets エイリアス、テーマでアセットパスを指定する方法を説明しています。
画像プレースホルダー
実際の画像が本当に必要で、ユーザーが提供しなければならない場合(製品のスクリーンショット、チーム写真、ユーザーデータのグラフなど)は、代わりの画像をでっち上げず、@open-slide/core の型付き <ImagePlaceholder hint="…" /> を配置してください。ユーザーは Assets パネルとインスペクターから画像を差し替えられます。装飾や汎用的なストック写真の代わりにプレースホルダーを使わないでください。ページの目的を文字、レイアウト、色で表現できる場合は、その方法を使ってください。
references/assets.md には、使用ルールの詳細、サイズのガイダンス、プレースホルダーが適切な場合とそうでない場合の例が記載されています。
ページ番号
フッターに現在のページ番号(03 / 12)を表示する場合は、useSlidePageNumber() から取得してください。n や TOTAL をハードコードしないでください。フックの仕様と呼び出し可能な場所については、references/page-numbers.md を参照してください。
段階的な表示(<Steps>/<Step>)
ページの内容を段階的に表示するには、後から表示する部分を <Step> で囲み、それらをまとめて <Steps> で囲みます。→ を押すたびに次のステップが表示されます。アイデアを提示する「順序」が重要な場合に使用し、すべてのページでむやみに使わないでください。重要なルールが 2 つあります。<Step> は <Steps> の直接の子要素でなければなりません(そうでない場合、すべてが最初から表示されます)。また、概要グリッドから移動した場合にも、ページが完成した状態で読める必要があります(移動時にはすべてのステップが表示されます)。
段階表示のページを作成する前に references/steps.md を読んでください。複数の <Steps> ブロックをまたぐ構成順序、「見出しは常に表示し、本文は順番に表示する」パターン、表示開始方向について説明されています。このプロジェクトに slides/build-on-reveal/ がある場合は、これを標準的な実装例として参照してください。
ページ遷移
フレームワークでは、スライドを切り替える際に、SlideTransition として宣言した入場/退場アニメーションを実行できます(モジュールレベルのデフォルト、ページごとの上書きが可能です。遷移先のページが優先されます)。デフォルトの遷移はありません。オプトインしない場合、ページは即座に切り替わります。スナップ切り替えも、十分に洗練されたデフォルトです。オプトインする場合は、デッキ全体でモーションの方向性を統一し、200~280 ms、移動量は 12 px 未満/スケールは 3% 未満にしてください。遷移先のページは、遷移元のページの上にフェードインする間、遷移元のページが表示されたままになります(throughBackground: true を指定しない限り、フレームワークは退場時の不透明度を無視します)。
遷移を宣言する前に references/transitions.md を読んでください。型の仕様、デザイン原則、同じ方向性を共有するすぐに使える「洗練されたファミリー」の 6 種類の遷移、方向に応じたキーフレーム、アンチパターンの一覧が記載されています。
モーフ遷移
隣接する 2 つのページに同じビジュアルオブジェクトがある場合は、両方のページで MorphElement で囲み、同じ id を指定したうえで、遷移先のページの遷移設定で morph を有効にします。位置、サイズ、角丸、色が連続して変化します(Keynote の「マジックムーブ」)。モーフは状態の連続性(スライドするトグル、展開するカード、横一列に加わるボックスなど)のためのものです。装飾をモーフさせないでください。
モーフを記述する前に references/morph.md を読んでください。そこにある 7 つのルール(退場時は表示したままにし、入場時は不透明度のみ変化させる、ジオメトリを決定的にする、モーフノードに transform を指定しない、useIsActivePage() で制御する、など)は、実際のデッキで検証を重ねたものです。どれか 1 つでも守らないと、モーフが目に見えて崩れます。
繰り返し要素:map ではなくコンポーネント
ページ内にカード、ロゴの列、ギャラリーのタイル、リストの行、ステップインジケーターなど、見た目が繰り返される要素がある場合は、小さなコンポーネントを定義し、要素ごとに 1 回ずつインスタンス化してください。データ配列に対する array.map でグループを描画しないでください。
コンポーネントは、Page コンポーネントと並べて、同じ index.tsx 内に定義してください。Card.tsx のような別ファイルに分割しないでください。スライドは常に、単一の index.tsx とその assets/ で構成されます。
// ✅ Each card is its own JSX node — inspector edits one at a time.
const Card = ({ src, label }: { src: string; label: string }) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<img src={src} style={{ width: 320, height: 320, objectFit: 'cover', borderRadius: 12 }} />
<p style={{ fontSize: 32 }}>{label}</p>
</div>
);
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 64 }}>
<Card src={alpha} label="Alpha" />
<Card src={beta} label="Beta" />
<Card src={gamma} label="Gamma" />
</div>
// ❌ One shared template — replacing the image or text in the inspector
// changes every rendered card at once.
const items = [
{ src: alpha, label: 'Alpha' },
{ src: beta, label: 'Beta' },
{ src: gamma, label: 'Gamma' },
];
items.map((item) => (
<div>
<img src={item.src} />
<p>{item.label}</p>
</div>
));
インスペクターは、ソースの JSX をその場で編集します。map の本体は、レンダリングされるすべてのインスタンスで共有される1 つのソース位置です。そのため、ユーザーがそこで画像を差し替えたりラベルを調整したりすると、すべてのカードが一緒に変わります。インスタンスを明示すれば、カードごとに独自の JSX ノードと props を持たせられます。これはインスペクターが対象にできる単位です。
コンポーネント定義をレイアウトやスタイルの唯一の情報源として保ちます(1 回変更すれば、すべてのカードに反映されます)。インスタンスごとのデータ(src、label、アクセントカラー)のみを呼び出し側に置きます。
これは、データではなくビジュアル要素が繰り返される場合に適用されます。純粋なテキストのリスト(<ul><li> の箇条書き)は問題ありません。各 <li> がすでに独自の JSX ノードなので、単純なリテラルのマークアップが適切な形です。コンポーネントで囲む必要はありません。
すぐに使えるランタイムの動作
- ホームページには、
slides/内のすべてのフォルダーが一覧表示されます。 - スライドをクリックすると、左側にサムネイルレール、メインページ、前/次ボタン、ページ番号が表示されます。
- 矢印キー/PageUp/PageDown で移動できます。
Fキーを押すと、全画面再生モードになります。 - 再生モードでは、Space/→ で次へ、← で前へ、Esc で終了します。
- ホットリロード:
index.tsxを編集すると、ブラウザーがリアルタイムで更新されます。 - 開発時のみ利用できるプレビュールート
/s/<id>/preview?p=Nでは、エディターの UI なしで 1 ページを表示します。ページを視覚的にプレビューするを参照してください。
ページを視覚的にプレビューする
ページを書いたり編集したりした後は、実際に見て確認します。垂直方向の予算計算でははみ出しは検出できますが、色の衝突や不自然な折り返し、画像の表示崩れは検出できません。開発サーバーの実行中は、各ページにブラウザーの UI を表示しないプレビュー URL があります。そこへ移動するために、エディター全体の UI(ホーム → デッキ → サムネイル)を操作する必要はありません。
-
開発サーバーを見つけます。 起動時に書き込まれ、停止時に削除される
node_modules/.open-slide/server.jsonを読み取ります。{ "url": "http://localhost:5173/", "port": 5173, "pid": 41234, "startedAt": "…" }ファイルがない場合、開発サーバーは実行されていません。ユーザーから依頼されていない限り、自分で起動しないでください。視覚的な確認を省略し、引き継ぎ時にその旨を伝えてください。
-
<url>s/<slideId>/preview?p=<pageNumber>を開きます。pは 1 始まりです。段階的に表示するページで最初のn個の<Step>だけを表示するには、&step=<n>を追加します(デフォルトではすべて表示されます)。 -
[data-osd-preview="ready"]を待ちます。 フォント、data-waitforの対象、画像、表示開始アニメーションの準備が整うと、この状態になります。スライド ID が不明な場合、ページ番号が範囲外の場合、レンダリング中にページでエラーが発生した場合、または 10 秒以内に準備が整わない場合(フォントや画像が読み込まれない、data-waitforの対象が出現しない、アニメーションが実行中のままなど)は、代わりに[data-osd-preview="error"]になります。[data-osd-preview-error]にメッセージが表示されます。そのままスクリーンショットを撮るのではなく、原因を修正してください。 -
1920×1080 のビューポートでスクリーンショットを撮ります。 これにより、ページはネイティブサイズでレンダリングされます(ビューポートが小さい場合は、収まるように縮小されます)。使用できるブラウザーツール(Playwright / Chrome MCP、ブラウザー自動化ツールなど)を使ってください。プロジェクトに Playwright がすでにインストールされていれば、CLI も使えます。
--noを指定すると、npxによるダウンロードを防げます。npx --no playwright screenshot --viewport-size "1920,1080" \ --wait-for-selector '[data-osd-preview="ready"]' \ "http://localhost:5173/s/<slideId>/preview?p=3" /tmp/<slideId>-p3.pngブラウザーツールが使用できない場合は、許可なくインストールしないでください。視覚的な確認を省略します。
その後、画像を確認し、見つかった問題を修正してください。プレビューはエディターと同様にホットリロードされ、current.json 内のユーザーのカーソル位置が移動することはありません。
完了前のセルフレビュー
-
slides/<id>/index.tsxが空でないPage[]をexport defaultしている。 - すべてのページのルートが
100% × 100%を占めている。 - コンテンツがパディング内に収まっている(テキストが端に触れていない)。
- 各ページについて、(font_size × line_height × lines)の合計 + 間隔 + 2×padding ≤ 1080px であること。 余裕がなければ、ページを分割してください。
overflow: autoで回避しないでください。 - 選択したフォントサイズで、箇条書きが2行目に折り返されない。
- すべてのページで、統一感のあるビジュアルの方向性(配色 + 文字のスケール)になっている。
- スライドでトップレベルの
export const design: DesignSystem = { … }を宣言し、値をvar(--osd-X)経由で参照している(算術演算用に JS の数値が必要な場合のみdesign.Xを使用)。意図的に配色を固定した単発のスライドに限り、design定数を省略できます。 - 1ページにつきアイデアは1つ。
- 視覚的に繰り返される要素(カード、タイル、ロゴの行)は、データリストに対する
array.mapではなく、明示的な<Component />インスタンスとして描画されている。 - インポートしたすべてのアセットがディスク上に存在している。スライド固有のアセットは
slides/<id>/assets/に、共通アセットはassets/に配置し、@assets/...経由でインポートしている。 - すべての
<ImagePlaceholder>が、ユーザーが用意する必要のある実画像に対応している(装飾用の埋め草ではない)。タイポグラフィやレイアウトで置き換えられる場合は、置き換えてください。 - ページで
<Steps>/<Step>を使う場合、すべての<Step>が<Steps>の直接の子であり、概要グリッドから移動してもページが完成した状態で表示される(順に進むと内容が段階的に表示され、途中のページに直接移動すると全内容が表示される)。 -
SlideTransitionを宣言する場合、すべてのページで同じファミリーに統一されている(同じ継続時間の範囲(200~280 ms)、同じイージングの組み合わせ、同じ「ホールド後にフェードイン」の形状(入場遅延なし、意図的なセクション区切り以外ではthroughBackgroundを使わない)、変化量は 12 px/3% 未満)。語彙が6種類も異なるデッキにしないでください。迷った場合は、トランジションを完全に省略してください。morphを選択したページは、morph に合わせてこの範囲を超えてもかまいません(references/morph.mdを参照)。 - トランジションで
morphを選択する場合、各ページの morphidが一意で、対応するページ間で安定しており、morph のジオメトリがピクセル単位で一定(マウント後に測定しない)で、morph ノードにtransformが設定されておらず、入場アニメーションがuseIsActivePage()の背後で制御されている。 - ユーザーがスピーチ原稿/スピーカーノートを求めた場合、
export const notesに格納され、ページ配列とインデックスが一致している(Markdown ファイルやテキストファイルには格納しない)。 - 開発サーバーが稼働している場合、変更したすべてのページをプレビューし(ページを視覚的にプレビューするを参照)、見た目に問題がある箇所を修正した。
-
slides/<id>/の外部は一切編集していない。
アンチパターン
- ❌ テキストの壁。1ページが約40語を超える場合は、ページを分割してください。
- ❌ 本文にキャンバス全体を使う。100~160px のパディングを確保してください。
- ❌ 垂直方向に1080pxを超える。切り取られたコンテンツは見えません。ページを分割してください。
- ❌ コンテンツが多すぎるのを「隠す」ために
overflow: auto/overflow: scroll/overflow: hiddenを使う。キャンバスはスクロールしません。問題を隠しているだけです。 - ❌ さらに詰め込むために、文字をスケールの下限より小さくしたり、パディングを100px未満にしたりする。代わりにページを分割してください。
- ❌ 箇条書きが2行目に折り返される。短くするか、独立したページに移してください。
- ❌ 本文の文字サイズが28px未満。プロジェクターでは読めません。
- ❌ ページ間で配色に一貫性がない。
- ❌ パッケージをインストールする。使用できるのは
react、@open-slide/core、標準の Web API のみです。 - ❌ 共有ファイルに CSS を記述する。インラインスタイルまたはスコープ付きクラス名のみ使用してください。
- ❌ スライドフォルダー内に
README.mdやその他の文章ファイルを作成する。 - ❌ スピーチ原稿を Markdown ファイルやテキストファイルとして納品する。ランタイムで表示されるのは
notesエクスポートのみです。それ以外はプレゼンタービューに表示されません。 - ❌
package.json、open-slide.config.ts、その他のスライドを編集する。 - ❌ リファレンスファイルを読まずにプリミティブを使う。各
references/*.mdには、そのプリミティブ固有のアンチパターン(プレースホルダーの誤用、トランジションの語彙、<Step>のネスト、morph のジオメトリ)が記載されています。