朝、Excel講座の contents.vue に手を入れたところから一日が転がった。冒頭フックに「30分」「3つの習慣」を埋め、第2章で答え合わせをする伏線回収の流れを組み込んだ。short-video-strategy スキルに入っているド素人ホテル再建計画氏の方法論——最初に謎を提示して、本編で正体を一気に明かす——を講座のスクロール体験に移植した形になる。
書き出してすぐ、伏線と回収が噛み合っていない箇所が浮き上がった。「3つのポイント、その正体」のセクションで章番号を伏字(→第2章で開示)にしておいたのに、本編では別の角度から答えていた。「2つのキー」「データの持ち方」「外側」の3点で受け止め直すよう書き換え、冒頭の問いと末尾の答えがつながるようにした。
画像モーダルとフォントの足回りを整える
image-modal.client.ts プラグインに .scroll-img と .exercise-img のセレクタを追加した。SVGをモーダルで開くと背景が透過して文字が見えなくなっていたので、背景色を白に固定する1行も足した。
ChatBox と VideoSubtitlePlayer のフォントも、イントロセクションの基準(font-size: 1.125rem / line-height: 2)に揃えた。3つのコンポーネントで微妙に値が違っていて、スクロールするたびに文字の重みが変わっていたのを撤去した形になる。
hardware章をスライド型から統合記事型へ
ここから本題。hardware章は ThinkPad / Monitor / PC の3パートに分かれていて、それぞれが独立したスライドとして表示されていた。1ページに集約する方が読み手の負荷が低いと判断し、article モード(統合記事型)に置き換えた。
TheaterViewer.vue の article モードには目次がなかったので、h3 を抽出して左サイドに並べる実装を足した。デプロイしてブラウザで開いた瞬間、ユーザーから指摘が飛んできた。
contents.vue と hardware側で目次レイアウトが違う
並べて見ると、たしかに目次の位置・幅・スクロール挙動がそれぞれ別物だった。原因を辿ると、両者で scroll-layout / scroll-content / scroll-inner の DOM 構造が分岐していた。contents.vue は独自に書いていて、TheaterViewer は別系統の CSS で組まれていた。
計画書を書いて Codex に3回レビューさせた
その場で直すと別の差異を生むと踏んで、まず memo/2026-04-29/scroll-layout-unification-plan.md に計画書を起こした。
codex exec -m gpt-5.4 "このプランをレビューして。瑣末な点へのクソリプはしないで。致命的な点だけ指摘して: ..."
3回回した。1回目は「URL hash を更新するか否か」を詰めろと言われた。スクロール連動で hash を書き換えると、戻るボタンが履歴で埋まって機能しなくなる懸念がある。案 X(hash を更新しない方針)を採用した。
2回目は h2/h3 の id 属性が DOM に正しく付与されるかを確認しろという指摘。MDC レンダラ経由だと id が落ちることがあるので、ScrollArticle 側で明示的に slug を生成して付ける実装に倒した。3回目は実装方針の最終確認だけで、致命的な指摘はなくなった。
ScrollArticle.vue を切り出す
共通コンポーネントとして ScrollArticle.vue を新規作成した。インターフェースはこの形に落ち着いた。
<ScrollArticle
:sections="sections"
:show-toc="true"
@section-enter="onSectionEnter"
/>
sections には h2 単位のブロックを配列で渡し、内部で h3 を抽出して目次を作る。contents.vue と TheaterViewer の article モードがこの1コンポーネントを呼ぶ形に置き換わり、scroll-layout / scroll-content / scroll-inner の3層構造が両者で揃った。
deep link をブラウザで踏んで、#section-2-1 のような h3 アンカーまで正しくスクロールすることを確認した。差し替え前は片方の経路でだけ id が消えていた。
モバイル幅で出た重複バグ
PC では問題なかったが、Chrome DevTools のモバイルビューに切り替えた瞬間、画面に ScrollArticle と theater-mobile が縦に2つ並んで描画された。CSS のメディアクエリで display: none を切り替える側が、共通化の過程で両方残ってしまっていた。条件分岐を v-if に寄せて DOM レベルで片方を消すよう直した。
stream-deck も統合記事型に寄せる
勢いで、stream-deck の chapter(si=0〜4)も「1 chapter = 1 統合記事」のフォーマットに集約した。si ごとに画像とテキストが小刻みに分かれていたのを、章単位の長尺ページにまとめた。スクロールが減って、読み手が自分のペースで進めるようになった。
/simplify で掃除
仕上げに /simplify を回して、staging に紛れ込んでいたデバッグPNG10枚を git から外した。ScrollArticle.vue に書き散らした使われていない class も削除した。
# 不要なclassを掃除
- .scroll-debug-overlay
- .toc-temp-fallback
- .legacy-section-wrapper
用語を固定した
説明するたびに「スクロール型」「統合型」「長尺型」と揺れていたので、コードベースとドキュメントの用語を「統合記事パターン」と「スライドパターン」の2つに固定した。明日以降、全ページをこの2分類でレビューしていく。
明日やること
- ゲシュタルトの章で使っている静止画を、GIF動画に置き換える(読み手が原理を1サイクル見られるようにする)
- 全ページを統合記事パターン / スライドパターンのどちらに寄せるか棚卸しする
- ScrollArticle のセクション境界アニメーションを 200ms 以内に収めて、スクロール時のカクつきを取る
- stream-deck の chapter ごとに目次の h3 数を5個以下に絞る