---
title: "ひとりと AI の1年で、ロボットは動き始めた ― 次は店舗や病院へ"
slug: one-year-lp-charts
date: 2026-10-25
day: 381
meta_description: "1年前、ロボットは画面の中の絵だった。いまは自分で学習させたAIが机の上のアームを動かす。その1年をグラフで見せるLPを公開し、初めて見る人に伝わる形へ作り直した記録。次は店舗や病院へ。"
tags: [LP, データビジュアライゼーション, Astro, SVG, Claude, codex, フィジカルAI, ロボットアーム, 配膳ロボット, Webデザイン]
series: "ひとり研究所の裏側"
---

1年前、このプロジェクトのロボットは、パソコンの画面の中の絵でした。いまは机の上で本物のロボットアームが動き、自分で学習させた AI の指示で、赤いブロックをつかんで白い箱に入れています。その間に公開した作業ログ（その日にやったことを、コードと実行の記録まで含めて残した記事）は379本になりました。

この1年をグラフで見せるページを作り、pixelstomotors.com/one-year で公開しました。LP（ランディングページ。1つのテーマだけを伝える縦長のページ）と呼んでいるものです。言いたいことは1つで、ページの一番上にこう書いています。

> 画面の中で AI を使いこなした土台が、ロボットを動かす AI への道になる。

記事を書く、コードを書く、それを確かめる、サイトに公開する。画面の中の仕事を AI に任せる範囲を少しずつ広げてきた進め方が、そのままロボットの研究でも使えた、という話です。1年で変わったことを並べるとこうなります。

| | 2025年12月 | 2026年10月 |
|---|---|---|
| 記事を送ってから、サイトに公開されるまで | 30〜45分（手作業・本人の目安） | 約1分（AI と仕組みが公開する） |
| 記事1本の文字数（中央値） | 2,794字 | 49,746字（コードとログを全部載せる） |
| 月に公開した作業ログ | 23本 | 59本（10月は25日まで） |
| 公開するものの種類 | 2種類（作業ログ・4コママンガ） | 8種類（動画・試算・LP なども） |
| 動かしたロボット | 0台（画面の中の絵だけ） | 1台（机の上のロボットアーム） |
| 自分で学習させた AI がブロックを箱に入れた場所 | 0か所 | 3か所（お手本を取った3か所すべて） |

この先に向かう場所も、ページの最後に書きました。画面の中で積み上げた土台を、ロボットが働く店舗や病院へ持っていくことです。Unity（建物や人を3Dで置いて動かせるソフト）で作ったファミレスの中で配膳ロボットを走らせ、60秒以上止まる回数を57回から20回まで減らしたシミュレーションが、その最初の一歩になっています。

この記録は、そのページを公開した日の作業ログです。朝10時17分に最初の版を公開してから、昼の13時すぎまで、サイトに反映しながら見せ方を直し続けました。直したことの多くは、グラフの形よりも言葉の側でした。グラフの上の見出しから「何の」が抜けていると、初めて見る人はそこで止まります。「手で回していた作業が、分単位に」では、何の作業かが分かりません。**初めて見る人に伝わるかどうかは、見出しに「何の数字か」が書いてあるかで決まりました。** 最後は「記事の公開は手作業の30分以上から約1分に、3Dの店の営業135分は PC で約8分に」と書き直しています。

やったことの全体はこうです。

| やったこと | なぜ | 結果 |
|---|---|---|
| ページの方針の文書（MASTER）の決まりを2つに分けた | 「AI 活用の相談の窓口は出さない」決まりと、1年の LP をトップと SNS に出すことが食い違った | 相談の窓口は今までどおり記事とフォームだけ。土台を示すページはトップ・SNS に出してよい |
| 最初の版をトップの入口と一緒に公開した | 別の AI（codex）の指摘33件を直した版ができた | 公開できたが、見た目は下書きの段階のままだった |
| グラフだけで分かるページに作り直した | 文章を読まないと分からない作りだった | 白・青・黒の3色、幅1080px、文字は補助 |
| 最初の画面の「土台の帯」を、毎日の本数の縦線にした | 379個の四角では、1年の流れが見えなかった | 320日のうち216日に作業ログを出したことが一目で分かる |
| 8つの見出しに「何の」を入れた | 初めて見る人が見出しで止まった | 主語のない見出しがなくなった |
| 6つのグラフの形を作り直した | 目盛りが10倍ずつ・何の8回か分からない・割合の円に見える、など | ふつうの物差し・2段の比べ・1周24時間の輪などに |
| 最後の CTA（読む人に次の行動を促す面）とトップの入口に写真を入れた | 青一色では、次に向かう場所が見えない | 店舗と病院の写真を青に重ねた |
| スマホで言葉の途中で折り返さないようにした | 「次／は」のように切れていた | 意味の切れ目でだけ折り返す |

効果の書き方について先に断っておきます。この記録で「見込める効果」と書いたものは、まだ測っていない見込みです。測ったのは、ページの重さ（HTML だけで、作り直した11時24分の版が73,317バイト、13時の版が76,198バイト）と、公開がうまくいったかどうかだけです。読んだ人に伝わったかは、このページの閲覧数と、トップの入口のボタン（`TOP:1年のLP`）と最後のボタン（`1年LP:作業ログへ`）が押された回数で、これから確かめます。


## 作業ログ ― 1年の LP を公開して、グラフで伝わる形に作り直す

### MASTER の決まりを、相談の窓口と1年の LP で分けた

このプロジェクトには、すべての判断の上に置く MASTER という文書があります。10月17日に、そこへ「AI 活用の相談は研究の副産物として受け、サイトのトップと SNS には相談の窓口を出さない」という決まりを足していました。研究の記録より、AI の使い方の話が目立ちすぎていたためです。

1年の LP は、半分が AI の使い方の話です。この決まりのままだと、トップにも SNS にも出せません。そこで、どう扱うかを決めてもらいました。

> 今の構成のまま LP をサイトのトップや SNSにも出していいよ。フィジカルAIを進めて行くためには、必ずデジタルAIの構築は必須なのでそこは地続きだよ。

相談の窓口と、土台を示すページを分けて書くことにしました。MASTER の「運用の上位ルール」は、いまこの2行になっています。

```
- AI活用の相談は研究の副産物として受ける。相談の窓口は、AI活用の記事とメッセージフォームからの導線に限る（サイトのトップ・SNSには相談の窓口を出さない）
- デジタルの AI の土台づくりは、フィジカルAI の研究と地続きの仕事として扱う。その土台を記録から示すページ（1年の記録の LP など）と、研究の進め方としての AI 活用は、サイトのトップ・SNS・動画に出してよい。どちらも相談の看板にはしない
```

更新履歴には「1年の記録の LP をトップと SNS に出すとエイイチが決め（フィジカルAI を進めるには、デジタルの AI の土台づくりが欠かせないため）、10-17 に足した行と食い違ったため」と残しています。

### 10時17分に、最初の版をトップの入口と一緒に公開した

最初の版は、デザインの案を並べる作業場（Claude のアーティファクト）で作った「案C」をそのままページにしたものです。公開の前に、別の AI の codex（OpenAI のコードを読む AI）に、数字と言い方の確かめを頼みました。指摘は33件で、重さの内訳は「誤り」2件・「誤解を招く」26件・「改善」5件でした。

誤りの1つは、10月の本数を「過去最多」と書いていたことです。作業ログの一覧を月ごとに数えると、4月が61本で、10月（25日まで）より多くありました。誤解を招く例は、「記事の手作業だけで、月に約29〜44時間が研究に戻った」という書き方です。1本あたりの手作業の時間（本人の目安）に本数を掛けた見込みで、研究に使えた時間は測っていません。これらは、見込みには見込みと書き、測った数字と分けて直しています。いまのページの「1日に何本も出すようになり記事の分け方も変わったので、生産性の倍率ではない」「人の手が同じだけ空いたという意味ではない」といった断り書きは、この指摘から来ています。

10時17分に、ページ・トップの入口・SNS 用の画像を一緒に公開しました。SNS の投稿も作り、X と LinkedIn は10月28日（水）12時30分に予約しています。

ところが、公開したページを見て、すぐにこう返ってきました。

> あれ？デザインこのまま？これワイヤーフレームじゃないの？

その通りで、案C は配置を決めるための下書きの段階でした。ページは公開したまま、見た目を作り直すことになりました。

【画像】LOG381_01_first_vs_rebuild.jpg 左は10時17分に公開した最初の版（灰色の棒グラフと黒い文字だけの下書きの見た目）。右は11時24分に作り直した版（白い地に青い帯と、青い横棒の比べる行）

### 11時24分に、グラフだけで分かるページへ作り直した

作り直しの方向は、いくつかの案を見比べながら決まっていきました。決まったことを並べます。

- グラフが主役で、文章は補助にする

> 何ならグラフだけですぐわかる！くらいにしてテキストは補助的な構成に修正

- 色は白・青（#1a3fcc）・黒の3つだけ。節の背景は白と青を交互にする
- 角丸・影・意味のない飾りは入れない
- 中身の幅は1080px。最初の案は広すぎて、グラフの間が間延びしていた
- 文字は Noto Sans JP（日本語）と Inter（数字・英字）
- 文字をグラフの絵の上に重ねない
- 結論を上に置く

ページは、Python のスクリプト `tools/one_year_lp.py` が HTML と SVG（線や四角を数字で描く画像の形式）を書き出し、サイトの Astro のページがそれを読み込む形にしました。グラフの部品はすべてこのスクリプトの中で描いていて、グラフ用のライブラリは使っていません。動きは CSS だけで付けています（数字が0から379まで数え上がる、棒が伸びる、など）。JavaScript を使うのは、図が画面に入ったことを知らせる数行だけです。

- ねらい：文章を読まなくても、グラフを見れば1年の変化が分かるようにする
- 変わったこと：灰色と黒の下書きの見た目から、白と青の面が交互に続くページになった。数字の大きさで、前と後の差が目に入る
- 見込める効果：トップや SNS から来た人が、最初の画面で読むのをやめずに下まで進む。押された回数は、最後のボタン（`1年LP:作業ログへ`）で確かめる

### 最初の画面の「土台の帯」を、379個の四角から毎日の本数の縦線に替えた

最初の画面には、見出しのすぐ下に青い帯を置いています。「AI に書かせ、確かめさせ、記録させる」進め方で残した作業ログ379本を、この1年の土台として見せる帯です。数字は、ページを開くと0から379まで数え上がります。

最初は、作業ログ1本を白い四角1つにして、379個を下の段から積んでいました。数え上がる数字は好評でしたが、四角の積み方には別のデザインを求められました。四角の並びには時間の順がなく、1年の流れが見えないためです。

そこで、別案を6つ並べた確認用のページ（`/one-year-lab`。検索に出さず、決まったあとに消した）を作り、動きつきで見比べてもらいました。

| 案 | 見せ方 |
|---|---|
| 今の形 | 四角を下の段から積む（左右は時間の順ではない） |
| 案1 | 月ごとの柱に四角を積む |
| 案2 | 1マスを1日にしたカレンダー。出した日だけ白くする |
| 案3 | 1日を縦線1本にし、高さをその日に出した本数にする |
| 案4 | 1か月を1つの層にして、地層のように積む |
| 案5 | 始めてからの合計を、左から伸びる面にする |

選ばれたのは案3です。作業ログの公開日（日本時間）から毎日の本数を数えると、2025年12月10日から2026年10月25日までの320日のうち、216日に作業ログを出していました。1日で一番多かったのは10月24日の10本です。縦線は、数字が0から379まで数え上がるのと同じ2.27秒で、左から現れます。合計が379本にならないときは、スクリプトが書き出しを止めるようにしています（`assert sum(daily.values()) == 379`）。

前と比べるときの塗り方も変えました。前の値（2025年12月）は薄い青で塗っていましたが、「水色をやめて斜めストライプに」と言われ、斜めの線にしています。線の間は、白い面では4px おき（線2px・隙間2px）に詰めました。青い面のグラフとトップの入口は、元の7px おきのままです。前の値が0のときは、ストライプの切れ端が残らないよう、起点の縦線（2px）だけにしています。

【画像】LOG381_02_fv_before_after.jpg 最初の画面の前と後。左は379個の四角を積んだ帯と、ロボットの台数から始まる比べる行。右は毎日の本数の縦線の帯と、記事の公開までの時間から始まる比べる行

### 最初の画面の比べる行を、AI の効果が見える順に並べ替えた

帯の下には、2025年12月と2026年10月を横棒で比べる行を6つ置いています。最初は「動かしたロボット」から始まっていました。ページの言いたいことは「画面の中の AI の土台が、ロボットへの道になる」なので、先に AI に任せた効果を見せ、そのあとにロボットを置く順に並べ替えました。

1. 記事を送ってから公開まで（30〜45分 → 約1分）
2. 記事1本の文字数（2,794字 → 49,746字）
3. 月に公開した作業ログ（23本 → 59本）
4. 公開するものの種類（2種類 → 8種類）
5. 動かしたロボット（0台 → 1台）
6. 自分で学習させた AI がブロックを箱に入れた場所（0か所 → 3か所）

行の上の1行も、順番に合わせて「AI に任せた作業は速く、多く、広くなり、その上でロボットは自分で学習させた AI が動かすものになった。」に替えています。

### 8つの見出しに「何の」を入れた

この日いちばん多かった指摘は、見出しに主語や対象が抜けている、というものでした。「公開の手作業」に「何の公開？」、「学習し直す前に」に「何の学習？」、「任せすぎた失敗」に「何に任せたの？」と返ってきます。書いた側は1年分の文脈を持っているので、抜けていても読めてしまいます。初めて見る人には、そこで何の話かが分からなくなります。

| 節 | 11時24分の見出し | 13時の見出し |
|---|---|---|
| 01 | AI に任せる範囲を広げるたびに、公開する作業ログが増えた | 本業が増えて月6本まで落ちた作業ログが、公開まで AI に任せた10月は月59本に戻った |
| 02 | 公開するものは、2種類から8種類に | （そのまま） |
| 03 | 記録を全部残す形にして、記事1本は約18倍の長さに | 誰でも同じ手順で再現できるよう、コードとログを省かず載せた。記事1本は約18倍の長さに |
| 04 | 手で回していた作業が、分単位に | 記事の公開は手作業の30分以上から約1分に、3Dの店の営業135分は PC で約8分に |
| 05 | 別の目で先に直すと、アームは8回とも止まらなくなった | アームを動かす AI を学習し直す前に、別の AI がお手本のデータの崩れを見つけた。直したデータで学び直すと、アームは8回とも途中で止まらなくなった |
| 06 | 公開の手作業は、41本で約21〜31時間ぶん減った計算 | 作業ログの記事をサイトに公開する手作業は、41本で約21〜31時間ぶん減った計算 |
| 07 | 任せすぎた失敗が、いまの決まりになった | AI に作業を任せすぎて起きた失敗が、いまの AI との決まりになった |
| 08 | 画面の中の AI の土台が、机の上のロボットでも働いた | （そのまま） |

01 の見出しは、ただ「増えた」だけでは何が起きたか伝わらない、という指摘からも直しています。「増えた？どうなった？どんな効果があった？」です。8月と9月に本数が6本と8本まで落ち、公開を AI に任せた10月に59本へ戻った、という流れを見出しに入れました。03 の見出しには、記事を長くした理由（誰でも同じ手順で再現できるように）を足しています。

- ねらい：見出しだけを縦に読んでも、何の数字がどう変わったかが分かるようにする
- 変わったこと：主語や対象のない見出しが8つのうち6つあり、すべてに「何の」を入れた。見出しは長くなったので、広い画面では意味の切れ目で改行している
- 見込める効果：グラフの前で止まる人が減る。確かめるには、ページの途中で離れた位置が分かる計測が要る（いまは取っていない）

【図解】LOG381_headings.html

### 01：AI との分担の段の区切りを、縦線と隙間で見せた

01 は、月ごとに公開した作業ログの本数の棒グラフです。下に「AI との分担」を5段で並べています（1 画面を作る道具 → 2 コードを書かせる → 3 全部を記録に残す → 4 書く AI と確かめる AI → 5 公開と発信まで任せる）。最初は段がつながって見え、「どこで区切りかわからない」と言われました。段の始まりに縦の線を立て、段と段の間を10px 空け、名前は段の幅に収まるよう2行に分けています。59 の数字の上の余白も16px 詰めました。

【画像】LOG381_03_01_monthly.jpg 01 の月ごとの本数の前と後。後は棒が斜めのストライプになり、下の「AI との分担」の5段が縦線で区切られている

### 02：公開するものの種類を、「2 → 8」と名前の入った四角8つにした

02 は、公開するものの種類が2種類から8種類になったことを見せる図です。最初は四角を10個並べ、名前を四角の下に書いていました。「違うレイアウト。ストライプ以外のデザイン」「テキストが四角の中とか？」と言われ、名前を四角の中に入れた案を4つ作りました（`/one-year-lab` に並べた）。

一度は8つを1列に並べる案D を入れましたが、すぐに「間違い、案C だった」と直りました。いまは、左に大きく「2 → 8」、右に4×2の四角です。前からある2つ（作業ログ・4コママンガ）は線の四角、1年で増えた6つ（解説動画・ショート動画・配膳ロボットの試算・LP と考えのページ・課題地図・SNS の予約投稿）は塗りの四角にしています。

【画像】LOG381_04_02_outputs.jpg 02 の前と後。前は四角10個の下に名前。後は左に大きな「2 → 8 種類」、右に名前の入った四角8つ（前からある2つは線、増えた6つは塗り）

### 03：記事の文字数は、面を塗らずに線だけにした

03 は、記事1本の文字数の中央値の推移です（2,794字 → 3,042字 → 13,991字 → 49,746字）。前は線の下をストライプで塗っていましたが、「このグラフはストライプはなし。ラインが動くように修正」と言われました。いまは線だけで、図が画面に入ると線が左から伸び、点と数字は線が届いたときに出ます。13,991 の数字は5月の縦線と線に重なっていたので、点の右下に移しています。

【画像】LOG381_05_03_chars.jpg 03 の記事1本の文字数の前と後。前は線の下をストライプで塗った面、後は線だけで、見出しに「誰でも同じ手順で再現できるよう」が入った

### 04：かかる時間は、前を枠、いまを塗りで同じ物差しに並べた

04 は、かかる時間がどれだけ短くなったかの図です。前は、目盛りを10倍ずつ（1分・10分・100分）にした横軸に点を置いていました。10倍ずつの目盛りだと、45分と1分の差が、見た目では短く見えます。「前の時間」と「いまの時間」も、点の大きさの違いでしか分かりませんでした。

いまは、ふつうの目盛りで、枠いっぱいを前の時間（45分・135分）、塗りの棒をいまの時間（約1分・約8分）にしています。図が画面に入ると、塗りの棒が枠いっぱいから、いまの長さまで縮みます。行の名前は「記事を送ってから、サイトに公開されるまで」「3Dの店で、営業135分ぶんを回すのにかかる時間」と、何の時間かが分かる形にしました。2行目は、PC の中で営業を回す速さの話で、人の手作業が速くなった話とは違います。その違いは行の右に「実際の店なら135分かかる営業を、PC の Unity で回す」と添えています。

- ねらい：前といまの差を、目盛りを読まずに長さで分かるようにする
- 変わったこと：10倍ずつの目盛りの点から、同じ物差しの枠と塗りの棒に。「30倍以上速く」「約17倍速く」を右に大きく書いた
- 見込める効果：時間の差が、数字を読む前に目に入る

【画像】LOG381_06_04_speed.jpg 04 の前と後。前は10倍ずつの目盛りに点を置いた図、後は枠いっぱいが前の時間、塗りの棒がいまの時間の横棒2本

### 05：何の8回かを、朝と夕方の2段で見せた

05 は、ブロックを箱に入れる AI（Physical Intelligence の π0.5）を、机の上のアームで動かした1日（LOG358）の話です。前は四角8つに「8回とも、途中で止まらなかった」とだけ書いていて、「何が？何の話？」と言われました。

いまは2段にしています。上の段が朝で、直す前のお手本で学習した AI は、ブロックの近くで止まって動かなくなりました。お手本の4割が、止まって待っている時間だったためです。下の段が夕方で、お手本の待ち時間を削ったデータを、学習の前に codex が確かめて崩れを直し、学習し直しました。8回とも途中で止まらず、2回はブロックをつかめています。箱に入ったのは0回です。

【画像】LOG381_07_05_runs.jpg 05 の前と後。前は四角8つだけ。後は「朝：直す前のお手本で学習」の四角1つと「夕方：直したお手本で学習し直し」の四角8つの2段

### 06：割合の円ではなく「円1周＝24時間」の輪にした

06 は、記事の公開で減った手作業の見込み（約21〜31時間）と、私が見ていない間に PC が3Dの店のシミュレーションを回した時間（6時間40分）です。前は横棒2本でしたが、「円グラフにできる？」と聞かれました。

この2つは、1つの全体を分けた内訳ではありません。割合の円にすると、2つで全体になっているように読めてしまいます。そこで、円1周を24時間とした時計の輪で、長さを見せる形にしました。21〜31時間は1周と少しなので、外の輪が1周目、内の輪が2周目です。確かな21時間までを塗り、見込みの幅の分をストライプにしています。6時間40分は、4分の1周と少しです。

【画像】LOG381_08_06_time.jpg 06 の前と後。前は横棒2本。後は円1周を24時間とした輪2つ（21〜31時間は1周と少し、6時間40分は4分の1周と少し）

### 08：斜めの線の図を、「使い方 → ロボットで起きたこと」の対にした

08 は、ページの言いたいことをそのまま見せる図です。前は、上に画面の中の AI の使い方5段階、下にロボットで起きたこと4つを置き、斜めの線でつないでいました。月の軸もあり、線が交差して「もう少しわかりやすく！」と言われました。

いまは1行ずつの対です。左に使い方（番号・名前・使い始めた月）、右にそれでロボットに起きたこと（日付・出来事）を置き、矢印でつないでいます。行はロボットで起きた順です。

| 画面の中の AI の使い方 | ロボットに起きたこと |
|---|---|
| 2 コードを書かせる（2026.2〜） | 2026.4.2 アームの全関節が、AI の書いたコードで動いた |
| 4 書く AI と確かめる AI（2026.5〜） | 2026.5.14 実機を動かす前に、致命的なバグを4件直した |
| 3 全部を記録に残す（2026.4〜） | 2026.10.22 お手本から学んだ AI が、初めてブロックを箱に入れた |
| 5 夜も仕組みが回る（2026.10〜） | 2026.10.24 3Dの店で、配膳ロボットが止まった回数が 57 → 20回 |

矢印は、ページの中のものをすべて同じ形にそろえました。細い線に開いた矢じりで、Inter の「→」と同じ形です。色は青で、青い面の上だけ白にしています。トップの入口のボタンの矢印だけは、トップのほかのボタンと同じ形（Material Symbols の arrow_forward）のままです。

08 の上の2つの図にも、頭に何の数字かを書きました。左は「ロボットの AI の学習に使った計算機の費用」（学習用の PC を買う見積もり75万円に対し、クラウドの GPU を時間で借りた7回の合計4,677円）、右は「3Dのファミレスで、配膳ロボットが60秒以上止まった回数」（57回 → 20回）です。

その下の動画（アームがブロックを箱に入れる、6倍速）は、幅を最大720px に狭めて中央に置き、色を整えました。色温度を少し下げ、明るさ・コントラスト・彩度を少し上げています。使った ffmpeg の設定は下の「コード」に載せています。

- ねらい：画面の中の AI の使い方が、ロボットのどの出来事につながったかを、線をたどらずに読めるようにする
- 変わったこと：交差する斜めの線5本から、左右の対4行に。月の軸をやめ、日付を右の面に入れた
- 見込める効果：ページの言いたいこと（画面の中の土台がロボットへの道になる）が、この図だけで伝わる

【画像】LOG381_09_08_bridge.jpg 08 の前と後。前は上下2本の横線を斜めの線でつないだ図と、灰色の動画の枠。後は「使い方」の枠と「起きたこと」の青い面を矢印でつないだ4行と、色を整えた動画

### 最後の CTA：店舗と病院の写真を、青に乗算で重ねた

ページの最後は、「画面の中で積み上げた土台を、次はロボットが働く店舗や病院へ。」という見出しと、作業ログを読むボタンです。青一色だった面に「CTA になるので背景の画像を入れる。青ベースはそのまま（写真は濃い方で乗算とか？）」と言われました。

最初は、アームが箱にブロックを入れる動画の1コマを入れました。「背景写真もっと違うもので！」と言われ、サイトの導入前ガイドで使っている配膳ロボットの店と搬送ロボットの病院の画像に替えています。白黒にして黒を持ち上げ（文字が読める濃さにするため）、青の上に乗算で重ねました。写真の白い所は青のまま、暗い所だけが濃くなります。

ほかの見せ方も求められ、6案（今の形・左右に分ける・次の現場を4つ並べる・動くアームの動画・土台の帯でしめる・白の面で2つの入口）を確認用ページに並べました。決まったのは今の形です。文字の塊は中央に置き、塊の中は左ぞろえのままにしています。

見出しの下の1行も変えました。前は「数字の多くは公開している作業ログから数えた。本人の目安と見込みには、そう書いている。」で、「なんか伝わる？これで。」と言われました。いまは「このページの数字の出どころは、公開している379本の作業ログ。コードも実行ログも失敗も省かず載せているので、同じ手順でたどれる。」です。最後のボタンを押す理由を、ここで言うようにしました。

【画像】LOG381_10_cta.jpg 最後の CTA の前と後。前は青一色の面、後は配膳ロボットの店と搬送ロボットの病院の写真を青に重ねた面で、文字が中央に寄っている

### トップの入口も、CTA と同じ写真にした

サイトのトップに置いた LP の入口は、右側に月ごとの本数の棒グラフを置いていました。これも最後の CTA と同じ写真に替えています。スマホでは写真が上に来ます。

【画像】LOG381_11_top_banner.jpg トップの入口の前と後。前は右に月ごとの本数の棒グラフ、後は右半分に店舗と病院の写真を青に重ねた形

### スマホでは、見出しを文節の間でだけ折り返す

最後に、スマホで見出しが言葉の途中で折り返していました（「次／は」「月／59本」など）。iPhone の Safari は、文節で折り返す CSS の指定（`word-break: auto-phrase`）を無視します。トップの入口と同じように、見出しを「|」で文節に区切り、区切った塊ごとに `inline-block` にして、塊の途中では折り返さないようにしました。広い画面のための改行は、760px 以下では出しません。「AI」「PC」の前の空白は、折り返さない空白（`&nbsp;`）にしています。

### この記事を公開したあとに、08 と 06 を直した

この記事を13時20分ごろに公開したあと、スマホで LP を見直して、08 と 06 をもう一度直しました。

まず、08 の動画です。08 の文と図は、机の上のロボット（アームがブロックを箱に入れた）と、3Dの店舗のシミュレーション（配膳ロボットの止まった回数）の両方を言っています。なのに動画はアームの1本だけでした。

> 08のセクションだけど、このテキスト構成だとアームの動画だけではなくて導入シュミレーションも掲載した方がいいね。

アームの動画の横に、3Dの店舗のシミュレーションの動画（Unity のファミレスを真上から見た画面。サイトの導入前ガイドで使っているもの）を並べました。2本とも 16:9 にそろえ、下に「机の上のロボット」「3Dの店舗」の名前と説明を置いています。説明の終わりには、それぞれのシリーズのページへのリンク（「フィジカルAI研究室を見る →」「導入シミュレーションを見る →」）を足しました。スマホでは縦に並びます。この動画は、止まった回数を57回から20回に減らす前の版で録ったものなので、説明文には20回の版の画面だとは書いていません。

次に、08 の下の対応図です。左の箱の小さい字は「2026.2〜 から使っている」のような、使い始めた時期だけでした。

> 2026.02〜つかっている とかではなくて、もっと具体的にどんなことを行なったのかを書いて欲しい

右の出来事の元になった作業ログに書いてあることから、その使い方で具体的に何をしたかを2行で書きました。

| 使い方 | 何をしたか | 元の作業ログ |
|---|---|---|
| コードを書かせる | アームを動かす Python を AI が書き、実機で動かして目で確かめる | LOG123 |
| 書く AI と確かめる AI | AI が書いたアームのコードを、実機で動かす前に別の AI に読ませる | LOG200 |
| 全部を記録に残す | コマンドと実行ログを全部記事に残し、肩が毎回約2°届かない原因を見つけた | LOG361 |
| 夜も仕組みが回る | 3Dの店舗のシミュレーション18回を、PC がまとめて回し続ける（6時間40分） | LOG374 |

箱の左にあった数字（2・4・3・5）も消しました。01 の「AI との分担」の段の番号でしたが、08 ではロボットの出来事の順に並べているので番号が飛び、しかも 01 の番号だとはどこにも書いていませんでした。「左の数字ってなんの意味？」と聞かれ、名前だけで伝わるので消しています。

言葉もそろえました。「3Dの店」と書いていた4か所（04 の見出しと行の名前、06 の輪の説明、08 の出来事）を、「3Dの店舗」にしています。

- ねらい：08 の文が言っている2つ（机の上のロボットと3Dの店舗）を、どちらも動く画面で見せ、そこから各シリーズへ進めるようにする
- 変わったこと：動画1本から2本に。使い方の箱は、時期と番号から、具体的にしたことの2行に
- 見込める効果：LP から研究のシリーズ（/series6・/series8）へ進む人が出る。押された回数で確かめる

【画像】LOG381_12_08_after_publish.jpg 08 の前と後。前はアームの動画1本と、番号と「から使っている」の箱。後はアームと3Dの店舗の動画2本を並べ、箱には具体的にしたことを2行で書いた

06 の「円1周＝24時間」の輪は、スマホで下が見切れていました。図の高さ（330）が、輪の中心（190）と半径（132）に輪の太さの半分（13）を足した高さに足りていなかったためで、352 にしました。スマホでは、幅の広いグラフを横にずらして見る枠に入れています。この枠は横だけでなく上下も切るので、図の外に少し出る数字（01 の「59」など）も切れていました。枠の上下に 24px ずつ余白を足しています。図ごとに、中身が図の枠の外に出ていないかを確かめるスクリプトも書きました（下の「コード」）。

もう1つ、この記事を公開したことで、LP の生成スクリプトが止まりました。毎日の本数を数え直すと LOG381 も入って合計が380本になり、`assert sum(daily.values()) == 379` に引っかかったためです。LP の379本は LOG380 までの時点の数字なので、数えるのは LOG380 までと決めて（`last=380`）、379本のまま変わらないようにしました。

### この日の公開（37回のコミット）

ページを直すたびに GitHub に送り、約1分でサイトに出しています。10時17分の最初の公開から、この記事を公開したあとの直し（14時20分）までで、LP に関わるコミットは37回でした。コミットのメッセージを、そのまま載せます。

#### 5b721f5 2026-10-25 10:17

```
1年の LP（/one-year）を公開し、TOP に入口を置いた

- 「画面の中で AI を使いこなした土台が、ロボットを動かす AI への道になる。」デザインのキャンバスの案C をサイトのページにした（codex の指摘33件を反映した版）
- 数字は 2026-10-25 の時点に合わせた（10月は LOG380 を足して59本、10月16日からは41本・約21〜31時間の見込み）
- 各数字の出典の LOG 番号を、その作業ログへのリンクにした
- TOP の動画の次に「Year One | Pixels to Motors の1年」の入口（OneYearBanner）を足し、記録の節に id="record" を付けた（LP のボタンの行き先）
- SNS・検索結果用の画像 public/og/one-year.jpg

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### a8a52ae 2026-10-25 10:20

```
TOP の1年の入口：スマホでは期間のラベルを出さない（棒の下の期間と同じ内容が続くため）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 5167e07 2026-10-25 11:24

```
1年の LP（/one-year）をグラフに特化した作りに作り直した（白・青・黒）

- 最初の画面：土台の帯（AI に書かせ・確かめさせ・記録させる／作業ログ379本）。作業ログ1本を白い四角1つで下の段から積み、数字を0から379まで数え上げる。続いて12月と10月の前とあと6行の棒が伸びる
- 01〜08：各セクションの主役をグラフ1枚にし、文は補助に。文字はグラフの線・面に重ねない
- 08 の写真は、ブロックを箱に入れた回の動画（6倍速・public/site/oy/）
- 本文は tools/one_year_lp.py が src/data/one-year/body.html に書き出す。見た目は one-year.astro の style
- 幅の狭い画面では、グラフを横にずらして見る（720px より縮めない）
- TOP の入口と SNS 用の画像も、同じ青の作りにそろえた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 89ff471 2026-10-25 11:24

```
1年の LP：08 の動画（ブロックを箱に入れた回・6倍速）と表紙の画像を足した（入れ忘れ）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 99f02c5 2026-10-25 11:40

```
1年の LP：前・比べる相手の薄い青を斜めのストライプに替え、見出しの上の行を外した

- 薄い青（水色）をやめ、斜めのストライプ（線2px・7px おき・45°）に統一。白い段は白地に青い線、青い段は青地に白い線。TOP の入口の棒も同じ
- 見出しの上の「PIXELS TO MOTORS　2025.12 — 2026.10」を外した（ヘッダーにロゴがあるため）。期間は土台の帯の見出しの横へ

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 4836776 2026-10-25 11:43

```
1年の LP：01 の見出しを、何が起きてどうなったかが数字で分かる文に替えた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### fa45d60 2026-10-25 11:45

```
1年の LP：ストライプの隙間を詰めた（7px おき → 4px おき）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 5bca480 2026-10-25 11:46

```
1年の LP：01 の見出しを意味の切れ目で折り返す。土台の帯の別案を見比べる確認用ページ /one-year-lab を足した

- 見出しの改行は tools/one_year_lp.py の文の「\n」で決める（01 は「作業ログが、」のあとで改行）
- /one-year-lab：今の形と別案5つ（毎日のます目・毎日の本数・月ごとの柱・地層・合計の面）を「もう一度再生」つきで並べた。検索に出さず、サイトマップにも載せない。決まったら消す

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 717c2ce 2026-10-25 11:52

```
1年の LP の 01：AI との分担の段の区切りが分かるようにした

段の始まりに縦の線を立て、段と段の間を10px空けた。名前は段の幅に収まるよう2行に分け、4の名前だけ下にずらしていたのをやめた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 466436b 2026-10-25 11:54

```
1年の LP：青い面のストライプを7px おきに戻した。0 の棒は縦線だけにした

- 4px おきに詰めるのは白い面（FV の比べる行）だけでよかった。青い面のグラフと TOP の入口は元の7px おきに戻す
- 0台・0か所は棒の切れ端を出さず、起点の縦線（2px）だけにする

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 90ec86f 2026-10-25 11:57

```
1年の LP：土台の帯を「毎日の本数の縦線」に替えた（/one-year-lab の案3）

- 縦線1本が1日、高さがその日に出した本数。本数は作業ログの公開日（日本時間）で数え、合計が379本になることを確かめてから書き出す
- 数字（0→379）と同じ2.27秒で、左から現れる
- 狭い画面では月の「1」を出さない（「2025.12」に重なるため）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 70d4fee 2026-10-25 12:00

```
1年の LP：比べる行を AI の効果が伝わりやすい順に並べ替え、01 の図の上の空きを詰めた

- 並び：公開までの時間 → 文字数 → 月の本数 → 種類 → その土台で動いたロボット（台数・箱に入れた場所）
- 01：59 の数字の上端が図の上端に来るよう、図の中の上の余白を16px 詰めた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### a58bdd8 2026-10-25 12:03

```
確認用ページ /one-year-lab を、02（公開するものの種類）の別案4つに差し替えた

土台の帯は案3（毎日の本数）に決まったので、帯の別案は外した。02 はストライプを使わず、名前を四角の中に入れる。前からある2つは線の四角、増えた6つは塗りの四角

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 979bde6 2026-10-25 12:05

```
1年の LP の 03：ストライプの面をやめて線だけにし、画面に入ったら線が伸びるようにした。見出しに「なぜ全部残したか」を入れた

- 見出し：誰でも同じ手順で再現できるよう、コードとログを省かず載せた。記事1本は約18倍の長さに
- 補足：手で書けば省いていた部分も、AI に書かせて全部残した
- 線は図が画面に入ったときに左から伸び、点と数字は線の到着に合わせて出る。JS が動かないときと、動きを減らす設定のときは最初から全部見える
- 13,991 を5月の線にかからない位置へ、「字」を数字の右へ移した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 7bae597 2026-10-25 12:09

```
1年の LP の 02：公開するものの種類を、8つを1列に並べる形（案D）にした。確認用ページ /one-year-lab を消した

- ストライプをやめ、前からある2つは線の四角、1年で増えた6つは塗りの四角。名前は四角の左上
- 上の線で「2025.12 は左の2つまで」「2026.10 は8つ全部」を示し、間に「＋6 種類」
- 帯と 02 の案が決まったので、確認用ページとサイトマップの除外を消した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 1b90986 2026-10-25 12:12

```
1年の LP：02 を案C（左に「2 → 8」、右に4×2の四角）に替え、FV の比べる行の上の1行と 03 の 13,991 の位置を直した

- 02：前からある2つは線の四角、1年で増えた6つは塗りの四角。名前は四角の左下。凡例は左の下
- FV：比べる行を AI の効果から並べたのに合わせ、上の1行を「AI に任せた作業は速く、多く、広くなり、その上でロボットは自分で学習させた AI が動かすものになった。」に
- 03：13,991 を線にも5月の縦線にもかからない点の右下へ
- astro.config.mjs：消した確認用ページの注記を消した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### a069f06 2026-10-25 12:14

```
1年の LP の 04：かかる時間の図を作り直した。前の時間を枠、いまの時間を塗りで同じ物差しに並べ、塗りが縮む動きを付けた

- 10倍ずつの目盛りをやめ、ふつうの目盛りに。枠いっぱいが前の時間（45分・135分）、塗りがいまの時間（約1分・約8分）
- 大きな数字で「前 → いま」、右端に「30倍以上速く」「約17倍速く」
- 図が画面に入ると、塗りが枠いっぱいから、いまの長さまで縮む。JS が動かないときと、動きを減らす設定のときは最初から縮んだ形

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### c055654 2026-10-25 12:15

```
1年の LP：FV の凡例のストライプの四角から枠線を外した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 972520e 2026-10-25 12:19

```
1年の LP：05 を「何の8回か」が分かる形に、06 を「円1周＝24時間」の輪に替えた。08 の動画を狭くし、色を整えた

- 05：見出しと補足に、ブロックを箱へ入れる AI（π0.5）を机の上のアームで動かした話だと書いた。図は朝（直す前・止まった）と夕方（直したあと・8回とも止まらず）の2段
- 06：2つの量は全体の内訳ではないので割合の円にはせず、円1周＝24時間の時計の輪で長さを見せる。21〜31時間は外の輪（1周目）と内の輪（2周目）、幅の分はストライプ。画面に入ると輪が伸びる
- 08：動画の幅を最大720px に。色温度を少し下げ、明るさ・コントラスト・彩度を少し上げた（ffmpeg の colortemperature と eq）。表紙の画像も同じ補正

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### bf23a74 2026-10-25 12:21

```
1年の LP の 04：見出しと行の名前に、何の時間かを書いた

- 見出し：記事の公開は手作業の30分以上から約1分に、3Dの店の営業135分は PC で約8分に
- 行の名前：「記事を送ってから、サイトに公開されるまで」「3Dの店で、営業135分ぶんを回すのにかかる時間」。右に前といまの中身を添えた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 38df33e 2026-10-25 12:23

```
1年の LP の 08：動画と説明文を中央にそろえた

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### c372021 2026-10-25 12:26

```
1年の LP の 08：2つの図の頭に「何の数字か」を書き、行の名前にも主語を入れた

- 左：ロボットの AI の学習に使った計算機の費用（学習用の PC を買う見積もり 75万円／クラウドの GPU を時間で借りた7回の合計 4,677円）
- 右：3Dのファミレスで、配膳ロボットが60秒以上止まった回数（地図を渡す前 57回／地図を渡し、ドリンクバーの位置を動かした後 20回）
- 図の頭で言い切ったので、見出しの下の「右は Unity の数字」の補足は外した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### ea44ba7 2026-10-25 12:29

```
1年の LP：08 の対応図を「使い方 → ロボットで起きたこと」の対に作り直し、最後の CTA に写真を入れた

- 08：斜めの線と月の軸をやめ、左に画面の中で作った AI の使い方（番号・名前・使い始め）、右にそれでロボットに起きたこと（日付・出来事）を1行ずつ矢印でつないだ。ロボットで起きた順。画面に入ると上から順に出る
- CTA：青の面に、箱へブロックを入れるアームの写真（白黒）を乗算で重ねた（public/site/oy/cta-bg.jpg）。見出しを「店舗や病院へ」に

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 4cb48f7 2026-10-25 12:33

```
1年の LP：CTA の背景を店舗と病院の画像に替え、CTA の別案6つを確認用ページ /one-year-lab に並べた。08 の矢印を整えた

- CTA の背景：配膳ロボットの店と搬送ロボットの病院の画像（public/site/sim の2枚）を左右に並べ、白黒にして青に乗算
- /one-year-lab：A 今の形／B 左右に分ける／C 次の現場を4つ並べる／D 動くアームの動画を背景に／E FV の土台の帯でしめる／F 白の面で2つの入口。検索に出さず、サイトマップにも載せない。決まったら消す
- 08：矢印を、線の先に塗りの三角が右の面に刺さる形にした
- tools/one_year_lp.py は、別のスクリプトから部品を読むときは書き出さないようにした

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 1d3c46d 2026-10-25 12:37

```
1年の LP：最後の CTA の文字の塊を中央に置いた（塊の中は左ぞろえのまま）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 7123f92 2026-10-25 12:39

```
TOP の1年の LP の入口：右の棒グラフを、LP の最後の CTA と同じ写真に替えた

配膳ロボットの店と搬送ロボットの病院の白黒画像を、青に乗算して右半分に端まで敷く。狭い画面では写真を上に置く

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### cf6edc8 2026-10-25 12:42

```
1年の LP：矢印をすべて Inter の「→」（細い線＋開いた矢じり）にそろえ、CTA の補足を「なぜ作業ログを読むのか」が分かる文にした

- 矢印：08 の対応図の矢印を細い線と開いた矢じり（灰色）に。CTA のボタン、07 の「→」、08 の「57 → 20回」、TOP の入口の「読む →」も Inter の「→」に
- CTA の補足：「このページの数字の出どころは、公開している379本の作業ログ。コードも実行ログも失敗も省かず載せているので、同じ手順でたどれる。」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### c7fb6b9 2026-10-25 12:45

```
1年の LP：05・06・07 の見出しに「何の」を入れ、矢印の色を青にそろえた

- 05：アームを動かす AI を学習し直す前に、別の AI がお手本のデータの崩れを見つけた。直したデータで学び直すと、アームは8回とも途中で止まらなくなった
- 06：作業ログの記事をサイトに公開する手作業は、41本で約21〜31時間ぶん減った計算（左の輪の名前も「記事の公開で減った手作業」に）
- 07：AI に作業を任せすぎて起きた失敗が、いまの AI との決まりになった
- 矢印：02・04・08 の矢印を青に

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### ae13cf0 2026-10-25 12:50

```
TOP の1年の LP の入口：ボタンの矢印を TOP のほかのボタンと同じ形に戻した。確認用ページ /one-year-lab を消した

- 入口の「読む」の矢印は、TOP のほかのボタンと同じ Material Symbols の arrow_forward に（LP の中の矢印は Inter の「→」のまま）
- CTA は今の形（店舗と病院の写真を青に乗算）で決まったので、別案を並べた確認用ページとサイトマップの除外を消した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 30019a7 2026-10-25 13:07

```
1年の LP：スマホで見出しと CTA の文が言葉の途中で折り返さないようにした

- 見出しと CTA の文を「|」で文節に区切り、区切りの間でだけ折り返す（iPhone の Safari は word-break: auto-phrase を無視するため。TOP の入口と同じやり方）
- 広い画面の見出しの改行（\n）は残し、760px 以下では出さない
- 「AI」「PC」の前の空白は折り返さない空白にした

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### c87c9a1 2026-10-25 13:59

```
1年の LP の 08：アームの動画の横に、3Dの店舗の導入シミュレーションの動画を並べた

- 08 の文（机の上のロボットと、3Dのファミレスの止まった回数）に合わせ、動画を2本並べる。右は /site/srl/top-busy.mp4（真上から見たファミレス）と、導入シミュレーション（/series8）へのリンク
- 2本とも 16:9 にそろえ、それぞれ名前と説明を下に置く。760px 以下では縦に並べる
- 土台の帯の毎日の本数は LOG380 までで数える（LOG381 を公開して合計が380になり、確かめの assert で止まったため。LP の数字は LOG380 までの時点）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 43e1eb6 2026-10-25 14:05

```
1年の LP の 08：アームの動画の説明に「フィジカルAI研究室を見る →」（/series6）を足した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### a6259b6 2026-10-25 14:07

```
1年の LP：06 の輪の下と、スマホでグラフの上下が切れていたのを直した

- 06：図の高さを 330 → 352 に（下の輪の太さの半分まで入る高さ）
- スマホ：横にずらして見るグラフの枠は上下も切るため、上下に 24px ずつ空けた（01 の 59 のように図の外に出る数字が切れていた）

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### bc6c87e 2026-10-25 14:10

```
1年の LP の 08：使い方の箱の小さい字を「いつから使っているか」から「具体的に何をしたか」に替えた

- 2 コードを書かせる：アームを動かす Python を AI が書き、私は実機で動かして目で確かめる（LOG123）
- 4 書く AI と確かめる AI：AI が書いたアームのコードを、実機で動かす前に別の AI に読ませる（LOG200）
- 3 全部を記録に残す：コマンドと実行ログを全部記事に残し、肩が毎回約2°届かない原因を見つけた（LOG361）
- 5 夜も仕組みが回る：3Dの店のシミュレーション18回を、PC がまとめて回し続ける（6時間40分・LOG374）
- 2行になったので箱の高さを 92 → 112 に

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### c86204c 2026-10-25 14:14

```
1年の LP の 08：「コードを書かせる」の説明から「私は」を外した

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 30d52dc 2026-10-25 14:17

```
1年の LP の 08：「3Dの店のシミュレーション」を「3Dの店舗のシミュレーション」に

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

#### 4e9b731 2026-10-25 14:20

```
1年の LP：「3Dの店」を「3Dの店舗」にそろえ、08 の使い方の箱から段の番号を外した

- 04 の見出し・行の名前、06 の輪の説明、08 の出来事の「3Dの店」を「3Dの店舗」に（4か所）
- 08 の左の番号（01 の「AI との分担」の段の番号）は、ここでは順が飛んで意味が伝わらないので出さない

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPmoJfV2LssXL3tkv6zt8s
```

### コード：ページの本文を書き出す tools/one_year_lp.py

LP の本文（HTML と SVG のグラフ）を書き出すスクリプトです。`python3 tools/one_year_lp.py` で `src/data/one-year/body.html` を書き出します。

```python
"""1年の LP（/one-year）の本文を作る。グラフは SVG で、文字は図の外に置く。

使い方:  python3 tools/one_year_lp.py   → src/data/one-year/body.html を書き出す
色は白・青・黒だけ。数字は公開している作業ログから数えた（2026-10-25 の時点。10月は25日まで）
見た目の決まり（CSS）は src/pages/one-year.astro にある
"""
import math, json, re, glob, datetime, collections
from pathlib import Path
SITE = Path(__file__).resolve().parent.parent
JP = 'Noto Sans JP'; NUM = 'Inter'
TH = {
    # ctx（前・比べる相手）は斜めのストライプ（白地に青い線／青地に白い線）。線を引くときは line を使う
    'w': dict(ink='#0a0a0a', mute='#6b6f78', ctx='url(#oy-st-w)', line='#1a3fcc', hl='#1a3fcc', grid='#e9ebf0', axis='#0a0a0a', bg='#ffffff'),
    'b': dict(ink='#ffffff', mute='#c9d6ff', ctx='url(#oy-st-b)', line='#ffffff', hl='#ffffff', grid='#ffffff30', axis='#ffffff', bg='#1a3fcc'),
}
M = [23, 29, 28, 41, 61, 33, 46, 45, 6, 8, 59]
ML = ['2025.12', '2026.1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
STG = [(0, '画面を作る道具'), (2, 'コードを書かせる'), (4, '全部を|記録に残す'), (5, '書く AI と確かめる AI'), (10, '公開と発信|まで任せる')]  # 「|」で改行

def svg(W, H, label):
    return f'<svg viewBox="0 0 {W} {H}" width="100%" role="img" aria-label="{label}" style="display:block;overflow:visible;font-family:\'{JP}\',sans-serif">'
def T(x, y, s, size=13, fill='#6b6f78', a='start', w=400, num=False, ls=None):
    f = f' font-family="{NUM}"' if num else ''
    l = f' letter-spacing="{ls}"' if ls else ''
    return f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" fill="{fill}" text-anchor="{a}" font-weight="{w}"{f}{l}>{s}</text>'
def R(x, y, w, h, fill, title=None):
    tt = f'<title>{title}</title>' if title else ''
    return f'<rect x="{x:.1f}" y="{y:.1f}" width="{max(w, 0):.1f}" height="{h:.1f}" fill="{fill}">{tt}</rect>'
def L(x1, y1, x2, y2, c, w=1):
    return f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" stroke="{c}" stroke-width="{w}"/>'

# ---------- 01：月ごとの本数（AI の分担の段を下に並べる） ----------
def g_monthly(t='w'):
    c = TH[t]; W, H = 1080, 490; top, base = 60, 314; cw = W / 11; bw = cw * 0.76  # 59 の数字の上端が図の上端に来るように詰める
    sc = lambda v: (base - top) * v / 61
    s = [svg(W, H, '月ごとに公開した作業ログの本数。2025年12月23本から2026年10月59本。4月は61本、8月6本・9月8本')]
    for i, v in enumerate(M):
        x = cw * i + cw / 2; now = i == 10; end = i == 0
        s.append(R(x - bw / 2, base - sc(v), bw, sc(v), c['hl'] if now else c['ctx'], f'{ML[i]}：{v}本'))
        if now:
            s.append(T(x, base - sc(v) - 16, '59', 72, c['ink'], 'middle', 500, True, '-2'))
        elif end:
            s.append(T(x, base - sc(v) - 14, '23', 36, c['ink'], 'middle', 500, True))
        else:
            s.append(T(x, base - sc(v) - 10, v, 15, c['mute'], 'middle', 400, True))
        s.append(T(x, base + 26, ML[i], 13, c['ink'] if now else c['mute'], 'middle', 600 if now else 400, True))
    s.append(L(0, base, W, base, c['axis'], 1.5))
    # 出来事
    x89 = cw * 9; y89 = base - sc(8) - 64
    s.append(L(cw * 8 + 10, y89 + 10, cw * 10 - 10, y89 + 10, c['mute']) + L(cw * 8 + 10, y89 + 10, cw * 8 + 10, y89 + 18, c['mute']) + L(cw * 10 - 10, y89 + 10, cw * 10 - 10, y89 + 18, c['mute']))
    s.append(T(x89, y89, '本業が増えた', 13, c['mute'], 'middle'))
    # AI の分担（段）：段の始まりに縦の線を立て、段と段の間を空ける。名前は段の幅に収まるよう2行に分ける
    ys = base + 80
    s.append(T(0, ys - 20, 'AI との分担', 12, c['mute'], w=700))
    for k, (i, lab) in enumerate(STG):
        x = cw * i + cw / 2 - bw / 2; now = i == 10
        x2 = (cw * STG[k + 1][0] + cw / 2 - bw / 2 - 10) if k < 4 else cw * 10 + cw / 2 + bw / 2
        col = c['hl'] if now else c['ink']
        s.append(R(x, ys, x2 - x, 14, c['hl'] if now else c['ctx']))
        s.append(R(x, ys - 8, 2, 52, col))
        s.append(T(x + 10, ys + 40, f'{k + 1}', 22, col, 'start', 500, True))
        for j, line in enumerate(lab.split('|')):
            s.append(T(x + 10, ys + 62 + j * 18, line, 12, col if now else c['mute'], 'start', 700 if now else 400))
    s.append('</svg>'); return ''.join(s)

# ---------- 08：画面の中の AI（上）が、ロボットを動かす AI（下）を進めた ----------
def g_bridge(t='w'):
    # 左に「画面の中で作った AI の使い方」、右に「それでロボットに起きたこと」を1行ずつ対にして、矢印でつなぐ
    # 行はロボットで起きた順。画面に入ると、上の行から順に出る（CSS の .pt）
    c = TH[t]; W = 1080; lw = 430; aw = 90; rx = lw + aw; rw = W - rx; hd = 44; rh = 112; gap = 12
    # 左の小さい字は、その使い方で具体的に何をしたか（「|」で改行）
    rows = [('2', 'コードを書かせる', 'アームを動かす Python を AI が書き、|実機で動かして目で確かめる', '2026.4.2', 'アームの全関節が、AI の書いたコードで動いた'),
            ('4', '書く AI と確かめる AI', 'AI が書いたアームのコードを、|実機で動かす前に別の AI に読ませる', '2026.5.14', '実機を動かす前に、致命的なバグを4件直した'),
            ('3', '全部を記録に残す', 'コマンドと実行ログを全部記事に残し、|肩が毎回約2°届かない原因を見つけた', '2026.10.22', 'お手本から学んだ AI が、初めてブロックを箱に入れた'),
            ('5', '夜も仕組みが回る', '3Dの店舗のシミュレーション18回を、|PC がまとめて回し続ける（6時間40分）', '2026.10.24', '3Dの店舗で、配膳ロボットが止まった回数が 57 <tspan font-family=\"Inter\" font-weight=\"400\">→</tspan> 20回')]
    H = hd + len(rows) * (rh + gap) - gap
    s = [svg(W, H, '画面の中で作った AI の使い方と、それでロボットに起きたこと。' + '。'.join(f'{a}（{st.replace("|", "")}）から、{d} {re.sub("<[^>]+>", "", e)}' for n, a, st, d, e in rows))]
    s.append(T(0, 18, 'PIXELS', 13, c['hl'], w=600, num=True, ls='1.5') + T(72, 18, '画面の中で作った AI の使い方', 14, c['ink'], w=700))
    s.append(T(rx, 18, 'MOTORS', 13, c['hl'], w=600, num=True, ls='1.5') + T(rx + 76, 18, 'それでロボットに起きたこと', 14, c['ink'], w=700))
    for k, (n, a, st, d, e) in enumerate(rows):
        y = hd + k * (rh + gap); my = y + rh / 2
        g = f'<rect x="1" y="{y + 1}" width="{lw - 2}" height="{rh - 2}" fill="none" stroke="{c['hl']}" stroke-width="2"/>'
        # 段の番号は出さない（01 の段の番号で、ここでは順が飛んで意味が伝わらないため）
        g += T(24, my - 18, a, 18, c['ink'], w=700) + ''.join(T(24, my + 8 + j * 20, ln, 13, c['mute']) for j, ln in enumerate(st.split('|')))
        # 矢印：ページの中の矢印はすべて Inter の「→」と同じ形（細い線＋開いた矢じり・青）にそろえる
        ax0, ax1 = lw + 22, rx - 22
        g += f'<path d="M{ax0},{my} H{ax1} M{ax1 - 9},{my - 8} L{ax1},{my} L{ax1 - 9},{my + 8}" fill="none" stroke="{c['hl']}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>'
        g += R(rx, y, rw, rh, c['hl']) + T(rx + 24, my - 6, d, 13, c['bg'], w=500, num=True) + T(rx + 24, my + 20, e, 17, c['bg'], w=700)
        s.append(f'<g class="pt" style="--t:{0.15 + k * 0.25:.2f}s">{g}</g>')
    s.append('</svg>'); return ''.join(s)

# ---------- 02：公開するもの 2 → 8 ----------
def g_units(t='w'):
    # 左に大きく「2 → 8」、右に4×2の大きな四角（案C）。前からある2つは線の四角、1年で増えた6つは塗りの四角。名前は四角の左下
    c = TH[t]; W, gap, lw = 1080, 6, 340; cw = (W - lw - 3 * gap) / 4; ch = 132; H = ch * 2 + gap
    items = ['作業ログ', '4コママンガ', '解説動画', 'ショート|動画', '配膳ロボット|の試算', 'LP・考え|のページ', '課題地図', 'SNS の|予約投稿']
    s = [svg(W, H, '公開するものの種類。2025年12月は作業ログと4コママンガの2種類。2026年10月は解説動画・ショート動画・配膳ロボットの試算・LPと考えのページ・課題地図・SNSの予約投稿が増えて8種類')]
    s.append(T(0, 120, '2', 120, c['mute'], w=500, num=True) + T(84, 104, '→', 40, c['hl'], w=400, num=True) + T(136, 120, '8', 120, c['hl'], w=500, num=True) + T(212, 120, '種類', 18, c['hl'], w=700))
    s.append(T(0, 160, '2025.12', 13, c['mute'], num=True) + T(136, 160, '2026.10', 13, c['ink'], w=600, num=True))
    # 凡例（左の下）
    ly = H - 6
    s.append(f'<rect x="1" y="{ly - 11}" width="10" height="10" fill="none" stroke="{c['hl']}" stroke-width="2"/>' + T(20, ly, '2025.12 からある', 13, c['ink']))
    s.append(R(150, ly - 12, 12, 12, c['hl']) + T(170, ly, '1年で増えた', 13, c['ink']))
    for k in range(8):
        r, col = divmod(k, 4); x = lw + col * (cw + gap); y = r * (ch + gap); old = k < 2
        if old:
            s.append(f'<rect x="{x + 1:.1f}" y="{y + 1:.1f}" width="{cw - 2:.1f}" height="{ch - 2:.1f}" fill="none" stroke="{c['hl']}" stroke-width="2"/>')
        else:
            s.append(R(x, y, cw, ch, c['hl']))
        lines = items[k].split('|')
        for n, ln in enumerate(lines):
            s.append(T(x + 16, y + ch - 18 - (len(lines) - 1 - n) * 22, ln, 16, c['hl'] if old else c['bg'], w=700))
    s.append('</svg>'); return ''.join(s)

# ---------- 03：記事1本の文字数 ----------
def g_chars(t='b'):
    # 目盛りの線と数字はなくし、値は点の左上の空いた所に書く。出来事の文字は図の上の段に置く
    c = TH[t]; W, H = 1080, 400; pl, pr, top, base = 8, 8, 96, 350
    P = [(0, 2794, '2025.12'), (3, 3042, '2026.3'), (5, 13991, '2026.5'), (10, 49746, '2026.10')]
    x = lambda i: pl + (W - pl - pr) * i / 10; y = lambda v: base - (base - top) * v / 50000
    s = [svg(W, H, '記事1本の文字数の中央値。2025年12月2,794字、2026年3月3,042字、5月13,991字、10月49,746字')]
    xa, xb = x(3.5), x(4.5)
    s.append(L(xa, 24, xa, base, c['mute']) + L(xb, 24, xb, base, c['mute']))
    s.append(T(xa - 8, 18, '4月 コードを全文で', 13, c['ink'], 'end', 700) + T(xb + 8, 18, '5月 実行ログも全部', 13, c['ink'], 'start', 700))
    d = 'M' + ' L'.join(f'{x(i):.1f},{y(v):.1f}' for i, v, _ in P)
    # 面は塗らず、線だけ。画面に入ったら線が左から伸び、点と数字が線の到着に合わせて出る（CSS の .draw）
    s.append(f'<path class="ln" pathLength="1" d="{d}" fill="none" stroke="{c["hl"]}" stroke-width="3" stroke-linejoin="round"/>')
    for i, v, lab in P:
        now = i == 10; tt = f' class="pt" style="--t:{0.2 + 1.6 * i / 10:.2f}s"'
        g = f'<circle cx="{x(i):.1f}" cy="{y(v):.1f}" r="{8 if now else 5}" fill="{c["hl"]}"/>'
        if now:
            g += T(x(i) - 36, y(v) - 6, '49,746', 48, c['ink'], 'end', 500, True, '-1') + T(x(i) - 16, y(v) - 6, '字', 14, c['ink'], 'end', 700)
        elif i == 0:
            g += T(x(i), y(v) - 16, f'{v:,}', 16, c['ink'], 'start', 400, True)
        elif i == 5:  # 線は右上へ上がるので、線にも5月の縦線にもかからない点の右下に置く
            g += T(x(i) + 14, y(v) + 30, f'{v:,}', 16, c['ink'], 'start', 400, True)
        else:
            g += T(x(i) - 12, y(v) - 12, f'{v:,}', 16, c['ink'], 'end', 400, True)
        s.append(f'<g{tt}>{g}</g>')
        s.append(T(x(i), base + 26, lab, 12, c['mute'], 'middle' if 0 < i < 10 else ('start' if i == 0 else 'end'), num=True))
    s.append(L(0, base, W, base, c['axis'], 1.5))
    s.append('</svg>'); return ''.join(s)

# ---------- 04：かかる時間 前 → いま ----------
def g_speed(t='w'):
    # 前の時間を枠（幅いっぱい）、いまの時間を塗りの棒にして、同じ物差し（ふつうの目盛り）で並べる
    # 画面に入ると、塗りの棒が前の長さから、いまの長さまで縮む（CSS の .shr）
    c = TH[t]; W = 1080; rh = 216; H = rh * 2 - 56; bh = 44
    rows = [('記事を送ってから、サイトに公開されるまで', '前は自分の手作業（本人の目安）、いまは AI と仕組みが公開する', '30〜45', '分', '約1', '分', 45, 1, '30倍以上速く'),
            ('3Dの店舗で、営業135分ぶんを回すのにかかる時間', '実際の店なら135分かかる営業を、PC の Unity で回す', '135', '分', '約8', '分', 135, 8, '約17倍速く')]
    s = [svg(W, H, 'かかる時間。記事の公開は30〜45分から約1分で、30倍以上速く。店の135分のシミュレーションは約8分で回り、約17倍速く')]
    for k, (lab, sub, a, au, b, bu, full, now, ratio) in enumerate(rows):
        y = k * rh
        s.append(T(0, y + 16, lab, 16, c['ink'], w=700) + T(W, y + 16, sub, 13, c['mute'], 'end'))
        # 大きな数字：前 → いま。右端に何倍速くなったか
        s.append(f'<text x="0" y="{y + 76}" font-family="{NUM}" font-weight="500" fill="{c['mute']}"><tspan font-size="40">{a}</tspan><tspan font-size="16" font-family="{JP}" font-weight="700" dx="4">{au}</tspan>'
                 f'<tspan font-size="28" dx="16" font-weight="400" fill="{c['hl']}">→</tspan><tspan font-size="64" fill="{c['hl']}" dx="16">{b}</tspan><tspan font-size="18" font-family="{JP}" font-weight="700" fill="{c['hl']}" dx="4">{bu}</tspan></text>')
        s.append(T(W, y + 76, ratio, 28, c['hl'], 'end', 700))
        by = y + 96
        s.append(f'<rect x="1" y="{by + 1}" width="{W - 2}" height="{bh - 2}" fill="none" stroke="{c['hl']}" stroke-width="2"/>')
        wn = max(W * now / full, 8)
        s.append(f'<rect class="shr" x="0" y="{by}" width="{wn:.1f}" height="{bh}" fill="{c['hl']}" style="--s:{W / wn:.2f}"/>')
        s.append(T(W, by + bh + 22, f'{full}分', 12, c['mute'], 'end', num=True) + T(0, by + bh + 22, '0', 12, c['mute'], num=True))
    s.append('</svg>'); return ''.join(s)

# ---------- 05：実機テスト8回 ----------
def g_runs(t='b'):
    # 朝（直す前）の1回と、夕方（直したあと）の8回を2段で並べる。何の8回かを図の中で言う
    c = TH[t]; W = 1080; lw = 200; gap = 6; sz = (W - lw - 7 * gap) / 8; hh = 72; r2 = 150
    H = r2 + hh + 64
    s = [svg(W, H, 'ブロックを箱へ入れる AI を実機で動かした結果。朝、直す前はブロックの近くで止まった。夕方、お手本を直して学習し直すと8回とも途中で止まらず、2回つかめた。箱に入ったのは0回')]
    def lab(y, a, b_):
        return T(0, y + 30, a, 18, c['ink'], w=700) + T(0, y + 54, b_, 13, c['mute'])
    # 朝
    s.append(lab(0, '朝', '直す前のお手本で学習'))
    s.append(f'<rect x="{lw + 1}" y="1" width="{sz - 2:.1f}" height="{hh - 2}" fill="none" stroke="{c['ink']}" stroke-width="2"/>')
    s.append(T(lw + sz + 20, 32, 'ブロックの近くで止まり、動かなくなった', 16, c['ink'], w=700) + T(lw + sz + 20, 56, 'お手本の4割が、止まって待っている時間だった', 13, c['mute']))
    # 夕方
    s.append(lab(r2 - 34, '夕方', '直したお手本で学習し直し'))
    s.append(T(W, r2 - 18, '8回とも止まらず ／ つかめた 2回 ／ 箱に入った 0回', 14, c['ink'], 'end', 700))
    for k in range(8):
        x = lw + k * (sz + gap); grasp = k < 2; y = r2
        s.append(R(x, y, sz, hh, c['hl'] if grasp else c['ctx'], f'{k + 1}回目'))
        s.append(T(x, y + hh + 24, f'{k + 1}回目', 13, c['mute']) + T(x, y + hh + 46, 'つかめた' if grasp else '止まらず', 13, c['ink'] if grasp else c['mute'], w=700 if grasp else 400))
    s.append('</svg>'); return ''.join(s)

# ---------- 06：時間（円1周＝24時間） ----------
def g_time(t='w'):
    # 2つの量は全体の内訳ではないので、割合の円ではなく「円1周＝24時間」の時計の輪で長さを見せる
    c = TH[t]; W, H = 1080, 352; R1, R2, SW = 132, 96, 26  # 下の輪の太さの半分まで入る高さ（190 + 132 + 13 + 余白）
    def pt(cx, cy, r, h):
        a = math.radians(h / 24 * 360 - 90); return cx + r * math.cos(a), cy + r * math.sin(a)
    def arc(cx, cy, r, h0, h1, col, cls='ln'):
        x0, y0 = pt(cx, cy, r, h0); x1, y1 = pt(cx, cy, r, h1); big = 1 if (h1 - h0) > 12 else 0
        return f'<path class="{cls}" pathLength="1" d="M{x0:.1f},{y0:.1f} A{r},{r} 0 {big} 1 {x1:.1f},{y1:.1f}" fill="none" stroke="{col}" stroke-width="{SW}"/>'
    def track(cx, cy, r):
        return f'<circle cx="{cx}" cy="{cy}" r="{r}" fill="none" stroke="{c['grid']}" stroke-width="{SW}"/>'
    s = [svg(W, H, '円1周を24時間として表した時間。減った手作業の見込みは約21〜31時間で、1周と少し。PC が回した時間は約6時間40分で、4分の1周と少し')]
    s.append(T(0, 16, '円1周 ＝ 24時間', 13, c['mute'], w=700))
    # 左：減った手作業 21〜31時間（外の輪が1周目、内の輪が2周目。確かな21時間までを塗り、幅の分をストライプ）
    cx, cy = 150, 190
    s.append(track(cx, cy, R1) + track(cx, cy, R2))
    s.append(arc(cx, cy, R1, 0, 20.5, c['hl']) + arc(cx, cy, R1, 20.5, 23.99, c['ctx']) + arc(cx, cy, R2, 0, 6.75, c['ctx']))
    s.append(T(cx, cy + 8, '21〜31', 34, c['hl'], 'middle', 500, True, '-1') + T(cx, cy + 32, '時間', 13, c['hl'], 'middle', 700))
    tx = 310
    s.append(T(tx, 128, '記事の公開で減った', 16, c['ink'], w=700) + T(tx, 152, '手作業（見込み）', 16, c['ink'], w=700) + T(tx, 178, '41本 × 1本30〜45分', 13, c['mute']) + T(tx, 198, '内の輪は2周目', 13, c['mute']) + T(tx, 218, 'ストライプは見込みの幅', 13, c['mute']))
    # 右：PC が回した時間 6時間40分
    cx2 = 690
    s.append(track(cx2, cy, R1) + arc(cx2, cy, R1, 0, 6.667, c['hl']))
    s.append(T(cx2, cy + 8, '6:40', 34, c['ink'], 'middle', 500, True, '-1') + T(cx2, cy + 32, '時間:分', 13, c['ink'], 'middle', 700))
    tx2 = 850
    s.append(T(tx2, 150, '私が見ていない間に', 16, c['ink'], w=700) + T(tx2, 174, 'PC が回した時間', 16, c['ink'], w=700) + T(tx2, 198, '3Dの店舗のシミュレーション18回', 13, c['mute']))
    s.append('</svg>'); return ''.join(s)

# ---------- 07：失敗の年表 ----------
def g_lessons(t='b'):
    c = TH[t]; W, H = 1080, 120; cw = W / 11; ya = 56
    s = [svg(W, H, '任せすぎた失敗が起きた月。2026年3月、7月、10月')]
    s.append(L(0, ya, W, ya, c['axis'], 1.5))
    for i, lab in enumerate(ML):
        s.append(L(cw * i + cw / 2, ya - 5, cw * i + cw / 2, ya + 5, c['mute']) + T(cw * i + cw / 2, ya + 28, lab, 12, c['mute'], 'middle', num=True))
    for k, i in enumerate((3, 7, 10)):
        x = cw * i + cw / 2
        s.append(R(x - 12, ya - 12, 24, 24, c['hl']) + T(x, ya + 5, k + 1, 14, '#1a3fcc' if t == 'b' else '#fff', 'middle', 600, True))
    s.append('</svg>'); return ''.join(s)
LESS = [('1', '2026.03', 'Unity のデータが、作業中の場面ごと消えた', 'ファイルをまとめて消す命令を禁止。区切りごとに必ず保存'),
        ('2', '2026.07', '原因を AI と7回取り違えた。AI は「安全の制限は緩めない」にこだわって止まった', '決めるのは私。AI には判断の材料と安全な試し方を出させる'),
        ('3', '2026.10', '直近20本のうち、研究の記録は7本だけだった', '週に1回、研究の記録が半分を下回っていないかを見る')]

# ---------- 08：GPU と止まった回数 ----------
def g_gpu(t='w'):
    # 何の費用かを図の頭で言い切る（初めて見る人が主語を探さないように）
    c = TH[t]; W, H = 540, 262; sc = lambda v: 400 * v / 750000; o = 42
    s = [svg(W, H, 'ロボットのアームを動かす AI の学習に使った計算機の費用。学習用の PC を買う見積もりは約75万円（買わなかった）。クラウドの GPU を時間で借りた7回の合計は4,677円')]
    s.append(T(0, 18, 'ロボットの AI の学習に使った計算機の費用', 16, c['ink'], w=700))
    s.append(T(0, o + 18, '学習用の PC を買う見積もり（買わなかった）', 13, c['mute'], w=700))
    s.append(R(0, o + 30, sc(750000), 44, c['ctx']) + T(sc(750000) + 12, o + 62, '75万円', 20, c['mute'], num=True))
    s.append(T(0, o + 120, 'クラウドの GPU を時間で借りた（7回の合計）', 13, c['ink'], w=700))
    s.append(R(0, o + 132, max(4, sc(4677)), 44, c['hl']) + T(16, o + 168, '4,677', 44, c['hl'], w=500, num=True) + T(132, o + 168, '円', 14, c['hl'], w=700))
    s.append('</svg>'); return ''.join(s)
def g_stops(t='w'):
    c = TH[t]; W, H = 540, 262; sc = lambda v: 400 * v / 60; o = 42
    s = [svg(W, H, '3Dのファミレスで、配膳ロボット2台が60秒以上止まった回数。地図を渡す前は57回、地図を渡しドリンクバーの位置を動かした後は20回')]
    s.append(T(0, 18, '3Dのファミレスで、配膳ロボットが60秒以上止まった回数', 16, c['ink'], w=700))
    s.append(T(0, o + 18, 'ロボットに店の地図を渡す前', 13, c['mute'], w=700))
    s.append(R(0, o + 30, sc(57), 44, c['ctx']) + T(sc(57) + 12, o + 62, '57回', 20, c['mute'], num=True))
    s.append(T(0, o + 120, '地図を渡し、ドリンクバーの位置を動かした後', 13, c['ink'], w=700))
    s.append(R(0, o + 132, sc(20), 44, c['hl']) + T(sc(20) + 14, o + 168, '20', 44, c['hl'], w=500, num=True) + T(sc(20) + 70, o + 168, '回', 14, c['hl'], w=700))
    s.append('</svg>'); return ''.join(s)

def stat(n, u, lab, pre=''):
    return f'<div class="st"><span class="sn">{f"<small>{pre}</small>" if pre else ""}{n}<small>{u}</small></span><span class="sl">{lab}</span></div>'

H1 = '<span class="ph">画面の中で&nbsp;</span><span class="ph">AI を</span><span class="ph">使いこなした</span><span class="ph">土台が、</span><span class="ph">ロボットを</span><span class="ph">動かす AI への</span><span class="ph">道になる。</span>'

# ---------- 作業ログへのリンク ----------
R_ = {
    'LOG083': 'day83', 'LOG123': 'day123', 'LOG149': 'day149', 'LOG190': 'mecharm-descend-tip-shift-two-stage-tracking',
    'LOG200': 'pivot-to-probe-control-and-external-review', 'LOG275': 'why-arm-stopped-grasping-root-cause-apick',
    'LOG335': 'youtube-script-curation', 'LOG340': 'studio-to-astro-cloudflare', 'LOG342': 'pixelstomotors-com-domain',
    'LOG346': 'issues-map-weekly-update', 'LOG350': 'measure-before-selling', 'LOG352': 'teach-replay-19-demos',
    'LOG357': 'unity-family-restaurant-serving-robot', 'LOG358': 'pi05-learned-the-pauses', 'LOG361': 'pi05-first-block-in-box',
    'LOG363': 'pi05-all-three-positions', 'LOG368': 'from-research-log-to-lp', 'LOG369': 'how-to-finish-a-design',
    'LOG374': 'serving-robot-map-planner', 'LOG376': 'sns-autopost-x-linkedin', 'LOG377': 'manga-shorts-speech-bubbles',
}
def src_(*logs):
    return '<p class="src">出典：' + '・'.join(f'<a href="/record/{R_[n]}">{n}</a>' for n in logs) + '</p>'

# ---------- 最初の画面：前とあと＋土台 ----------
ROWS = [  # AI の効果が伝わりやすい順：公開までの時間 → 文字数 → 本数 → 種類 → その土台で動いたロボット
    ('記事を送ってから公開まで', ('30〜45', '分', '手作業・本人の目安', 100), ('約1', '分', 'AI と仕組みが公開', 3), 'LOG342'),
    ('記事1本の文字数', ('2,794', '字', '', 6), ('49,746', '字', 'コードとログを全部残す', 100), 'LOG149'),
    ('月に公開した作業ログ', ('23', '本', '', 39), ('59', '本', '10月は25日まで', 100), ''),
    ('公開するものの種類', ('2', '種類', '作業ログ・4コママンガ', 25), ('8', '種類', '動画・試算・LP なども', 100), ''),
    ('動かしたロボット', ('0', '台', '画面の中の絵だけ', 0), ('1', '台', '机の上のロボットアーム', 100), 'LOG123'),
    ('自分で学習させた AI がブロックを箱に入れた場所', ('0', 'か所', '', 0), ('3', 'か所', 'お手本を取った3か所すべて', 100), 'LOG363'),
]
def row(k, lab, b, a, lg):
    def bar(v, cls):
        n, u, note, w = v
        # 0 は棒を出さず、起点の縦線だけにする（ストライプの切れ端に見えないように）
        i = '<i class="z"></i>' if w == 0 else f'<i style="width:{w}%"></i>'
        return f'<div class="bar {cls}">{i}<span class="v"><b>{n}</b><small>{u}</small>{f"<em>{note}</em>" if note else ""}</span></div>'
    link = f'<a class="lk" href="/record/{R_[lg]}">{lg}</a>' if lg else ''
    return f'<div class="row" style="--d:{2.6 + k * 0.12:.2f}s"><div class="lab">{lab}{link}</div><div class="bars">{bar(b, "b")}{bar(a, "a")}</div></div>'

# 土台の帯：毎日の本数を縦線で並べる（1本の縦線が1日・高さがその日に出した本数）。本数は作業ログの公開日（日本時間）で数える
def daily_counts(start=datetime.date(2025, 12, 10), end=datetime.date(2026, 10, 25), last=380):
    # LP の数字は LOG380 までの時点（379本・LOG379 は欠番）。それより後の記事は数えない
    J = datetime.timezone(datetime.timedelta(hours=9)); items = {}
    for c in json.load(open(SITE / 'src/data/cards.json')):
        if re.match(r'^LOG\d+$', c['day']): items[c['day']] = c['published']
    for f in glob.glob(str(SITE / 'src/content/records/*.md')):
        t = open(f).read(); d = re.search(r'^day:\s*"?(\d+)', t, re.M); dt = re.search(r'^date:\s*(\S+)', t, re.M)
        if d: items['LOG' + d.group(1)] = dt.group(1)
    items = {k: v for k, v in items.items() if int(k[3:]) <= last}
    daily = collections.Counter()
    for v in items.values():
        v = v.replace('Z', '+00:00')
        t = datetime.datetime.fromisoformat(v) if 'T' in v else datetime.datetime.fromisoformat(v[:10]).replace(tzinfo=J)
        t = t.astimezone(J).date()
        if start <= t <= end: daily[t] += 1
    return start, end, daily

def beat():
    start, end, daily = daily_counts(); n_days = (end - start).days + 1; W, H = 1000, 120; mx = max(daily.values())
    assert sum(daily.values()) == 379, sum(daily.values())
    bars = ''.join(f'<rect x="{W * k / n_days:.2f}" y="{H - H * n / mx:.1f}" width="{W / n_days * 0.7:.2f}" height="{H * n / mx:.1f}" fill="#fff"/>'
                   for k in range(n_days) for n in [daily.get(start + datetime.timedelta(days=k), 0)] if n)
    labs = []; d = datetime.date(2026, 1, 1)
    while d <= end:
        labs.append(f'<span{" class=j" if d.month == 1 else ""} style="left:{100 * (d - start).days / n_days:.2f}%">{d.month}</span>'); d = (d.replace(day=28) + datetime.timedelta(days=4)).replace(day=1)
    return (f'<div class="beat" aria-hidden="true"><svg viewBox="0 0 {W} {H}" preserveAspectRatio="none" width="100%" height="{H}">{bars}</svg>'
            f'<div class="bax"><span style="left:0">2025.12</span>{"".join(labs)}</div></div>')

FV = f"""<section class="w fv">
<h1>{H1}</h1>
<div class="base"><div class="btop"><div class="bx"><div class="bl">FOUNDATION ／ 土台<span class="bd">2025.12 — 2026.10</span></div><div class="bt">AI に書かせ、確かめさせ、記録させる</div><div class="bs">コードと実行ログを省かずに残した作業ログ。縦線1本が1日で、高さがその日に出した本数</div></div><div class="bn"><b class="cnt" aria-label="379"></b><small>本</small></div></div>{beat()}</div>
<div class="lg"><p class="lead">AI に任せた作業は速く、多く、広くなり、その上でロボットは自分で学習させた AI が動かすものになった。</p><div class="legend"><span><i></i>2025.12</span><span class="a"><i></i>2026.10</span></div></div>
{''.join(row(k, *r) for k, r in enumerate(ROWS))}
</section>"""

def PH(t):
    # 文節で折り返す：「|」で区切った塊を inline-block にし、塊の途中では折り返さない（iPhone の Safari は word-break: auto-phrase を無視するため）
    return ''.join(f'<span class="ph">{x}</span>' for x in t.split('|'))

def sec(cls, n, en, h2, chart, note='', extra='', srcs=()):
    # 見出しの改行は「\n」（広い画面だけ）。狭い画面では「|」で区切った文節の間でだけ折り返す
    h2br = '<br class="pc">'.join(PH(line) for line in h2.split('\n'))
    return f"""<section class="sec {cls}"><div class="w"><div class="k"><b>{n}</b>{en}</div><h2>{h2br}</h2>{f'<p class="note sub">{note}</p>' if note else ''}<div class="chart">{chart}</div>{extra}{src_(*srcs) if srcs else ''}</div></section>"""

DEFS = ('<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false"><defs>'
        '<pattern id="oy-st-w" width="4" height="4" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="2" height="4" fill="#1a3fcc"/></pattern>'
        '<pattern id="oy-st-b" width="7" height="7" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="2" height="7" fill="#ffffff"/></pattern>'
        '</defs></svg>')
BODY = DEFS + FV + f"""
{sec('blue', '01', 'LOGS', '本業が増えて|月6本まで落ちた|作業ログが、\n公開まで&nbsp;|AI に任せた|10月は|月59本に戻った', g_monthly('b'), '月ごとの本数（10月は25日まで）。1日に何本も出すようになり記事の分け方も変わったので、生産性の倍率ではない。')}
{sec('', '02', 'OUTPUTS', '公開するものは、|2種類から|8種類に', g_units('w'), '', '', ('LOG346', 'LOG357', 'LOG368', 'LOG376', 'LOG377'))}
{sec('blue', '03', 'RECORDS', '誰でも|同じ手順で|再現できるよう、|コードとログを|省かず載せた。\n記事1本は|約18倍の長さに', g_chars('b'), '記事1本の文字数の中央値（コードとログを含む）。手で書けば省いていた部分も、AI に書かせて全部残した。研究の深さではなく、記録の形の違い。', '', ('LOG149', 'LOG190'))}
{sec('', '04', 'SPEED', '記事の公開は|手作業の|30分以上から|約1分に、\n3Dの店舗の|営業135分は&nbsp;|PC で|約8分に', g_speed('w'), '前の時間を枠、いまの時間を塗りで、同じ物差しに並べた。記事の公開の前の値は本人の目安。', '<div class="stats">' + stat('10', '分', 'サイトに機能を足す（頼んでから確かめるまで）', '約') + stat('65', '回', '5時間でのデザインの直しと公開') + stat('20', '本', '4コママンガからひと晩で作ったショート') + '</div>', ('LOG340', 'LOG342', 'LOG350', 'LOG357', 'LOG369', 'LOG377'))}
{sec('blue', '05', 'REVIEW', 'アームを動かす AI を|学習し直す前に、|別の AI が|お手本のデータの|崩れを見つけた。\n直したデータで|学び直すと、|アームは|8回とも|途中で|止まらなくなった', g_runs('b'), 'ブロックを箱へ入れる AI（π0.5）を、机の上のアームで動かした1日（LOG358）。お手本の待ち時間を削ったデータを、学習の前に codex（コードやデータを確かめる AI）が確かめ、崩れを直してから学習し直した。', '<div class="stats">' + stat('4', '件', 'アームのコードの致命的なバグ') + stat('29', 'か所', '解説動画の台本の事実の直し') + stat('18', '件', '判断のページの数字と言い切りの直し') + '</div>', ('LOG358', 'LOG200', 'LOG335', 'LOG368'))}
{sec('', '06', 'TIME', '作業ログの記事を|サイトに公開する|手作業は、\n41本で|約21〜31時間ぶん|減った計算', g_time('w'), '左は見込みで、実際に減った時間はまだ測っていない。右は通しで回した時間で、人の手が同じだけ空いたという意味ではない。', '', ('LOG374', 'LOG376'))}
{sec('blue', '07', 'LESSONS', 'AI に作業を|任せすぎて|起きた失敗が、\nいまの&nbsp;|AI との|決まりになった', g_lessons('b'), '', '<div class="less">' + ''.join(f'<div><span class="ln">{n}</span><span class="ld">{d}</span><p class="lw">{w}</p><p class="lr"><span class="arw">→</span> {r}</p></div>' for n, d, w, r in LESS) + '</div>', ('LOG083', 'LOG275'))}
<section class="sec"><div class="w"><div class="k"><b>08</b>PIXELS → MOTORS</div>
<h2>{PH('画面の中の&nbsp;|AI の土台が、|机の上の|ロボットでも|働いた')}</h2>
<div class="two"><div>{g_gpu('w')}</div><div>{g_stops('w')}</div></div>
<div class="media">
<figure><video class="photo" src="/site/oy/arm-box.mp4" poster="/site/oy/arm-box.webp" muted autoplay loop playsinline aria-label="アームが赤いブロックをつかみ、白い箱に入れるところ（6倍速）"></video>
<figcaption><b>机の上のロボット</b>2026.10.22　人のお手本から学んだ AI（Physical Intelligence の π0.5）が、初めてブロックを箱に入れた（6倍速）。お手本を取った3か所とも入った。<a href="/series6">フィジカルAI研究室を見る</a></figcaption></figure>
<figure><video class="photo" src="/site/srl/top-busy.mp4" poster="/site/srl/top-busy.webp" muted autoplay loop playsinline aria-label="3Dのファミレスを真上から見た画面。スタッフと配膳ロボットが通路を行き来する"></video>
<figcaption><b>3Dの店舗</b>Unity で作ったファミレスで、スタッフと配膳ロボットを走らせる導入シミュレーション（真上から見た画面）。上の「止まった回数」は、ここで数えた。<a href="/series8">導入シミュレーションを見る</a></figcaption></figure>
</div>
<p class="note" style="margin-top:72px">画面の中で身につけた AI の使い方が、ロボットのどの出来事につながったか。ロボットで起きた順に並べた。</p>
<div class="chart" style="margin-top:24px">{g_bridge('w')}</div>
{src_('LOG368', 'LOG374', 'LOG361', 'LOG363', 'LOG352', 'LOG200', 'LOG123')}
</div></section>
<section class="sec blue cta-sec"><div class="w"><div class="cta-in"><h2 class="cta-h">{PH("画面の中で|積み上げた|土台を、|次は|ロボットが働く|店舗や病院へ。")}</h2>
<p class="note" style="margin-top:20px">{PH("このページの数字の出どころは、|公開している|379本の作業ログ。")}<br>{PH("コードも実行ログも失敗も|省かず載せているので、|同じ手順で|たどれる。")}</p>
<a class="cta" href="/#record" data-ev="1年LP:作業ログへ">作業ログを読む<span class="arw">→</span></a></div></div></section>"""

if __name__ == '__main__':  # 別のスクリプト（one_year_lab.py）から部品だけ読むときは書き出さない
    out = SITE / 'src/data/one-year/body.html'
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(BODY)
    print('書き出し', out)
```

### コード：LP のページ src/pages/one-year.astro

書き出した本文を読み込み、文字・色・動きを決めるページです。

```astro
---
// Pixels to Motors の1年（LP。LOG の記事ではない）
// 伝えること：画面の中で AI を使いこなした土台（AI に書かせ・確かめさせ・記録させる進め方）が、ロボットを動かす AI への道になる
// グラフに特化した作り（2026-10-25 エイイチと詰めた）。各セクションの主役はグラフ1枚、文は補助。色は白・青（#1a3fcc）・黒だけ
// 本文（グラフの SVG を含む）は tools/one_year_lp.py が src/data/one-year/body.html に書き出す。見た目の決まりはこのファイルの style
// 数字は 2026-10-25 の時点（10月は25日まで）。入口は TOP（OneYearBanner）
import Base from '../layouts/Base.astro';
import { SITE, AUTHOR, PUBLISHER } from '../lib/seo';
import body from '../data/one-year/body.html?raw';

const path = '/one-year';
const url = SITE + path;
const title = '画面の中で AI を使いこなした土台が、ロボットを動かす AI への道になる ― Pixels to Motors の1年';
const seoTitle = 'Pixels to Motors の1年｜画面の中の AI から、ロボットを動かす AI へ';
const description = '1年で、ロボットは画面の中の絵から、自分で学習させた AI が動かす机の上の実物になった。土台は、AI に書かせ・確かめさせ・記録させる進め方と、省かずに残した作業ログ379本。速さ・見落としの直し・空いた時間をグラフで示した。';
const published = '2026-10-25';
const jsonLd = [
  {
    '@context': 'https://schema.org', '@type': 'Article', headline: title, description, url, mainEntityOfPage: url,
    image: [SITE + '/og/one-year.jpg'], inLanguage: 'ja', datePublished: published, dateModified: published,
    author: AUTHOR, publisher: PUBLISHER,
    about: [{ '@type': 'Thing', name: 'フィジカルAI' }, { '@type': 'Thing', name: 'AIエージェント' }, { '@type': 'Thing', name: 'ロボットアーム' }],
  },
  {
    '@context': 'https://schema.org', '@type': 'BreadcrumbList',
    itemListElement: [
      { '@type': 'ListItem', position: 1, name: 'Pixels to Motors', item: SITE + '/' },
      { '@type': 'ListItem', position: 2, name: 'Pixels to Motors の1年', item: url },
    ],
  },
];
---
<Base title={`${seoTitle} | Pixels to Motors`} description={description} path={path} ogImage="/og/one-year.jpg" ogType="article" published={published} modified={published} jsonLd={jsonLd}>
  <div class="oy" set:html={body} />
  <script is:inline>
    // 図が画面に入ったら .in を付ける（線が伸びる動きの合図）。JS が動かないときは最初から全部見える
    (function () {
      var root = document.querySelector('.oy'); if (!root || !('IntersectionObserver' in window)) return;
      root.classList.add('js');
      var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.35 });
      root.querySelectorAll('.chart').forEach(function (el) { io.observe(el); });
    })();
  </script>
</Base>

<style is:global>
  /* 前・比べる相手は斜めのストライプ（SVG の oy-st-w と同じ：線2px・4px おき・45°） */
  .oy { --oy-st: repeating-linear-gradient(-45deg, #1a3fcc 0 2px, transparent 2px 4px); background: #fff; color: #0a0a0a; font-family: 'Noto Sans JP', sans-serif; font-feature-settings: "palt" 1; line-height: 1.8; overflow-x: clip; }
  .oy a { color: inherit; }
  .oy .w { max-width: 1080px; margin: 0 auto; padding: 0 40px; box-sizing: content-box; }
  .oy .ph { display: inline-block; max-width: 100%; }
  /* 見出しの改行（\n）は広い画面だけ。狭い画面では文節（.ph）の間で折り返す */
  @media (max-width: 760px) { .oy br.pc { display: none; } }
  .oy .sec { padding: 96px 0; }
  .oy .blue { background: #1a3fcc; color: #fff; }
  /* 矢印はすべて Inter の「→」（細い線＋開いた矢じり）にそろえる */
  .oy .arw { font-family: Inter, sans-serif; font-weight: 400; }
  .oy .cta .arw { font-size: 24px; line-height: 1; }
  .oy .k { display: flex; gap: 16px; align-items: baseline; font: 500 12px/1 Inter, sans-serif; letter-spacing: .08em; color: #6b6f78; }
  .oy .blue .k { color: #c9d6ff; }
  .oy .k b { font: 500 26px/1 Inter, sans-serif; letter-spacing: 0; color: #1a3fcc; }
  .oy .blue .k b { color: #fff; }
  .oy h1 { margin: 0; font-size: 52px; line-height: 1.3; font-weight: 700; max-width: 960px; word-break: auto-phrase; }
  .oy h2 { margin: 16px 0 0; font-size: 30px; line-height: 1.45; font-weight: 700; max-width: 900px; word-break: auto-phrase; text-wrap: pretty; }
  .oy h2.cta-h { font-size: 38px; }
  /* 最後の CTA：青の面に、配膳ロボットの店と搬送ロボットの病院の画像（左右に並べ、白黒・黒を持ち上げて文字が読める濃さ。public/site/sim の2枚から作る）を乗算で重ねる。写真の白い所は青のまま、暗い所だけ濃くなる */
  .oy .cta-sec { background: #1a3fcc url(/site/oy/cta-bg.jpg?v=2) center / cover no-repeat; background-blend-mode: multiply; padding: 140px 0; }
  /* 文字の塊だけを中央に置き、塊の中は左ぞろえのまま */
  .oy .cta-in { width: fit-content; max-width: 860px; margin: 0 auto; }
  .oy .note { margin: 0; font-size: 13px; line-height: 1.8; color: #6b6f78; max-width: 720px; }
  .oy .blue .note { color: #c9d6ff; }
  .oy .note.sub { margin-top: 10px; }
  .oy .chart { margin: 40px 0 0; }
  .oy .src { margin: 24px 0 0; font: 500 12px Inter, 'Noto Sans JP', sans-serif; color: #6b6f78; }
  .oy .blue .src { color: #c9d6ff; }
  .oy .src a, .oy .lk { text-decoration: underline; text-underline-offset: 3px; }
  .oy .src a + a { margin-left: 2px; }

  /* 最初の画面：土台の帯と、前とあと */
  .oy .fv { padding-top: 88px; padding-bottom: 96px; }
  .oy .base { margin-top: 40px; background: #1a3fcc; color: #fff; padding: 32px 40px 36px; }
  .oy .btop { display: flex; justify-content: space-between; align-items: center; gap: 32px; }
  .oy .bx { display: flex; flex-direction: column; gap: 6px; }
  .oy .bl { font: 600 12px Inter, sans-serif; letter-spacing: .12em; color: #c9d6ff; }
  .oy .bl .bd { margin-left: 20px; letter-spacing: .06em; color: #fff; }
  .oy .bt { font-size: 26px; font-weight: 700; line-height: 1.4; }
  .oy .bs { font-size: 13px; color: #dbe4ff; }
  .oy .bn { font: 600 72px/1 Inter, sans-serif; letter-spacing: -.02em; white-space: nowrap; }
  .oy .bn small { font: 700 16px 'Noto Sans JP', sans-serif; margin-left: 6px; }
  @property --n { syntax: '<integer>'; inherits: false; initial-value: 0; }
  .oy .cnt { --n: 379; counter-reset: n var(--n); animation: oy-count 2.27s linear .3s both; }
  .oy .cnt::after { content: counter(n); }
  @keyframes oy-count { from { --n: 0; } to { --n: 379; } }
  /* 土台の帯：毎日の本数の縦線。数字（0→379）と同じ速さで左から現れる */
  .oy .beat { margin-top: 28px; }
  .oy .beat svg { display: block; animation: oy-wipe 2.27s linear .3s both; }
  @keyframes oy-wipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
  .oy .bax { position: relative; height: 24px; margin-top: 8px; border-top: 2px solid #fff; font: 500 12px Inter, sans-serif; color: #c9d6ff; }
  .oy .bax span { position: absolute; top: 6px; white-space: nowrap; }
  @keyframes oy-pop { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
  .oy .lg { display: flex; justify-content: space-between; align-items: baseline; gap: 24px; margin: 48px 0 12px; }
  .oy .lead { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.5; }
  .oy .legend { display: flex; gap: 24px; flex: none; font: 500 13px Inter, sans-serif; color: #6b6f78; }
  .oy .legend span { display: inline-flex; align-items: center; gap: 8px; }
  .oy .legend i { display: inline-block; width: 12px; height: 12px; background: var(--oy-st); }
  .oy .legend span.a i { background: #1a3fcc; }
  .oy .legend span.a { color: #0a0a0a; font-weight: 600; }
  .oy .row { display: grid; grid-template-columns: 300px 1fr; gap: 32px; align-items: center; padding: 18px 0; border-top: 1px solid #e9ebf0; }
  .oy .lg + .row { border-top: 1px solid #0a0a0a; }
  .oy .lab { font-size: 15px; font-weight: 700; line-height: 1.6; }
  .oy .lk { display: block; margin-top: 2px; font: 500 11px Inter, sans-serif; color: #6b6f78; }
  .oy .bars { display: flex; flex-direction: column; gap: 6px; }
  .oy .bar { display: flex; align-items: center; gap: 14px; }
  .oy .bar i { display: block; height: 14px; background: var(--oy-st); flex: none; max-width: 62%; transform-origin: left center; animation: oy-grow .7s cubic-bezier(.2,.7,.2,1) both; animation-delay: var(--d); }
  .oy .bar.a i { height: 22px; background: #1a3fcc; }
  .oy .bar i.z { width: 2px; background: #1a3fcc; }
  @keyframes oy-grow { from { transform: scaleX(0); } to { transform: none; } }
  .oy .v { white-space: nowrap; display: flex; align-items: baseline; gap: 2px; }
  .oy .v b { font: 500 15px Inter, sans-serif; color: #6b6f78; }
  .oy .bar.a .v b { font: 600 30px/1 Inter, sans-serif; color: #1a3fcc; letter-spacing: -.01em; }
  .oy .v small { font-size: 12px; font-weight: 700; color: #6b6f78; margin-right: 12px; }
  .oy .bar.a .v small { color: #1a3fcc; font-size: 14px; }
  .oy .v em { font-style: normal; font-size: 12px; color: #6b6f78; }
  .oy .row { --d: 2.6s; }
  /* 03：線が左から伸び、点と数字が線の到着に合わせて出る */
  .oy.js .chart .ln { stroke-dasharray: 1; stroke-dashoffset: 1; }
  .oy.js .chart.in .ln { animation: oy-draw 1.8s cubic-bezier(.4,0,.2,1) .2s forwards; }
  @keyframes oy-draw { to { stroke-dashoffset: 0; } }
  .oy.js .chart .pt { opacity: 0; }
  .oy.js .chart.in .pt { animation: oy-fade .4s ease-out forwards; animation-delay: var(--t); }
  @keyframes oy-fade { to { opacity: 1; } }
  /* 04：塗りの棒が前の長さ（枠いっぱい）から、いまの長さまで縮む */
  .oy .shr { transform-box: fill-box; transform-origin: left center; }
  .oy.js .chart .shr { transform: scaleX(var(--s)); }
  .oy.js .chart.in .shr { animation: oy-shrink 1.4s cubic-bezier(.6,0,.2,1) .4s forwards; }
  @keyframes oy-shrink { from { transform: scaleX(var(--s)); } to { transform: scaleX(1); } }
  @media (prefers-reduced-motion: reduce) { .oy.js .chart .ln { stroke-dashoffset: 0; animation: none !important; } .oy.js .chart .pt { opacity: 1; animation: none !important; } .oy.js .chart .shr { transform: none; animation: none !important; } }
  @media (prefers-reduced-motion: reduce) { .oy .beat svg, .oy .bar i, .oy .cnt { animation: none !important; opacity: 1; } }

  /* 数字の列・失敗の3列・写真 */
  .oy .stats { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid #0a0a0a; margin-top: 40px; }
  .oy .blue .stats { border-top-color: #fff; }
  .oy .st { padding: 24px 24px 0 0; display: flex; flex-direction: column; gap: 8px; }
  .oy .st + .st { padding-left: 24px; border-left: 1px solid #e9ebf0; }
  .oy .blue .st + .st { border-left-color: #ffffff30; }
  .oy .sn { font: 500 60px/1 Inter, sans-serif; letter-spacing: -.03em; }
  .oy .sn small { font: 700 15px 'Noto Sans JP', sans-serif; letter-spacing: 0; margin: 0 3px; }
  .oy .sl { font-size: 13px; font-weight: 700; }
  .oy .two { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-top: 40px; }
  /* 08 の動画2本：机の上のロボットと、3Dの店舗の導入シミュレーションを並べる */
  .oy .media { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 56px; }
  .oy .media figure { margin: 0; }
  .oy .photo { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; background: #e9ebf0; }
  .oy .media figcaption { margin-top: 14px; font-size: 13px; line-height: 1.8; color: #6b6f78; }
  .oy .media figcaption b { display: block; margin-bottom: 4px; font-size: 14px; color: #0a0a0a; }
  .oy .media figcaption a { display: inline-block; margin-left: 6px; color: #1a3fcc; font-weight: 700; }
  .oy .media figcaption a::after { content: ' →'; font-family: Inter, sans-serif; font-weight: 400; }
  @media (max-width: 760px) { .oy .media { grid-template-columns: 1fr; gap: 36px; } }
  .oy .less { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid #fff; margin-top: 8px; }
  .oy .less > div { padding: 28px 28px 0 0; }
  .oy .less > div + div { padding-left: 28px; border-left: 1px solid #ffffff30; }
  .oy .ln { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; background: #fff; color: #1a3fcc; font: 600 14px Inter, sans-serif; margin-right: 12px; }
  .oy .ld { font: 500 13px Inter, sans-serif; color: #c9d6ff; }
  .oy .lw { margin: 16px 0 0; font-size: 14px; color: #e3eaff; }
  .oy .lr { margin: 12px 0 0; font-size: 16px; font-weight: 700; }
  .oy .cta { display: inline-flex; align-items: center; gap: 24px; height: 64px; padding: 0 40px; background: #fff; color: #1a3fcc; font: 700 17px 'Noto Sans JP', sans-serif; text-decoration: none; margin-top: 48px; transition: background .15s; }
  .oy .cta:hover { background: #e3eaff; }

  /* 幅の狭い画面：グラフは横にずらして見る（字が小さくなりすぎないよう、720px より縮めない） */
  @media (max-width: 760px) {
    .oy .w { padding: 0 20px; }
    .oy .sec { padding: 64px 0; }
    .oy .fv { padding-top: 72px; padding-bottom: 64px; }
    .oy h1 { font-size: 30px; }
    .oy h2 { font-size: 22px; }
    .oy h2.cta-h { font-size: 24px; }
    /* 横にずらして見る。上下は図の外にはみ出す数字（01 の 59 など）が切れないよう 24px ずつ空ける */
    .oy .chart { overflow-x: auto; margin: 16px -20px -24px; padding: 24px 20px; }
    .oy .chart svg { min-width: 720px; }
    .oy .base { padding: 24px 20px 24px; }
    .oy .btop { flex-direction: column; align-items: flex-start; gap: 12px; }
    .oy .bt { font-size: 19px; }
    .oy .bn { font-size: 56px; }
    .oy .beat svg { height: 80px; }
    .oy .bax .j { display: none; } /* 狭い画面では「1」が「2025.12」に重なるので出さない */
    .oy .lg { flex-direction: column; gap: 12px; }
    .oy .lead { font-size: 17px; }
    .oy .row { grid-template-columns: 1fr; gap: 10px; }
    .oy .bar i { max-width: 48%; }
    .oy .bar.a .v b { font-size: 24px; }
    .oy .v em { display: none; }
    .oy .stats, .oy .less, .oy .two { grid-template-columns: 1fr; }
    .oy .st + .st, .oy .less > div + div { padding-left: 0; border-left: 0; border-top: 1px solid #e9ebf0; margin-top: 20px; padding-top: 20px; }
    .oy .blue .st + .st, .oy .less > div + div { border-top-color: #ffffff30; }
    .oy .sn { font-size: 48px; }
  }
</style>
```

### コード：トップの入口 src/components/OneYearBanner.astro

```astro
---
// 1年の LP（/one-year）への入口。TOP に置く
// LP と同じ青（#1a3fcc）の面に白い文字。右には LP の最後の CTA と同じ写真（配膳ロボットの店と搬送ロボットの病院・白黒を青に乗算）を端まで敷く（2026-10-25）
// 題名は「|」で文節に区切り、区切りの間でだけ折り返す（GuideCards と同じ。iPhone の Safari は word-break: auto-phrase を無視するため）
const title = '画面の中で\u00a0|AI を|使いこなした|土台が、|ロボットを|動かす AI への|道になる。';
---
<a class="oyb" href="/one-year" data-ev="TOP:1年のLP">
  <span class="oyb-text">
    <span class="oyb-label">2025.12 — 2026.10</span>
    <span class="oyb-title">{title.split('|').map((p) => <span class="ph">{p}</span>)}</span>
    <span class="oyb-lead">1年で、ロボットは画面の中の絵から、自分で学習させた AI が動かす机の上の実物になった。その土台になった「AI に書かせ、確かめさせ、記録させる」進め方を、グラフで見せます。</span>
    <span class="oyb-go">読む<i class="ms">arrow_forward</i></span>
  </span>
  <span class="oyb-ph" aria-hidden="true"></span>
</a>
<style>
  .oyb { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 46%); align-items: stretch; border-radius: 8px; box-shadow: 0 8px 16px rgba(0,0,0,.08); overflow: hidden;
    background: #1a3fcc; color: #fff; text-decoration: none; font-family: 'Noto Sans JP', sans-serif; }
  .oyb:focus-visible { outline: 2px solid #121418; outline-offset: 3px; }
  .oyb-text { display: grid; gap: 14px; padding: 48px; }
  .oyb-label { font: 600 12px/18px Inter, sans-serif; letter-spacing: .08em; color: #c9d6ff; }
  .oyb-title { font-size: 26px; line-height: 1.5; font-weight: 700; word-break: auto-phrase; text-wrap: pretty; }
  .ph { display: inline-block; max-width: 100%; }
  .oyb-lead { font-size: 14px; line-height: 1.8; color: #e3eaff; text-wrap: pretty; }
  .oyb-go { justify-self: start; display: inline-flex; align-items: center; gap: 6px; height: 44px; margin-top: 4px; padding: 0 20px; border-radius: 4px; background: #fff; color: #1a3fcc;
    font: 700 14px/1 var(--sans); transition: background .15s; }
  .oyb:hover .oyb-go { background: #e3eaff; }
  /* 矢印は TOP のほかのボタンと同じ（Material Symbols の arrow_forward） */
  .oyb-go .ms { font-size: 18px; transition: transform .2s; }
  .oyb:hover .oyb-go .ms { transform: translateX(3px); }
  /* 写真：白黒の画像を青に乗算。写真の白い所は青のまま、暗い所だけ濃くなる */
  .oyb-ph { display: block; min-height: 320px; background: #1a3fcc url(/site/oy/cta-bg.jpg?v=2) center / cover no-repeat; background-blend-mode: multiply; }
  @media (max-width: 899px) {
    .oyb { grid-template-columns: 1fr; }
    .oyb-ph { order: -1; min-height: 180px; }
    .oyb-text { padding: 28px 20px; }
    .oyb-title { font-size: 21px; }
  }
</style>
```

### コード：グラフの中身が図の枠の外に出ていないかを確かめるスクリプト

ページの中の SVG の図を1つずつ見て、中身の範囲（`getBBox()`）が図の枠（`viewBox`）の外に出ているものだけを書き出します。Playwright で動かします。

```javascript
// 各グラフの svg で、中身が viewBox の外にはみ出していないかを調べる
const { chromium } = require('playwright');
(async () => { const b = await chromium.launch(); const p = await b.newPage({ viewport: { width: 1440, height: 900 } });
 await p.goto('http://localhost:4321/one-year?p2m-notrack=1', { waitUntil: 'networkidle' });
 const r = await p.evaluate(() => [...document.querySelectorAll('.oy svg[viewBox]')].map((s, i) => { const vb = s.viewBox.baseVal; const bb = s.getBBox();
   return { i, label: (s.getAttribute('aria-label') || '').slice(0, 20), vb: [vb.width, vb.height], bottom: +(bb.y + bb.height).toFixed(1), right: +(bb.x + bb.width).toFixed(1), top: +bb.y.toFixed(1), left: +bb.x.toFixed(1) }; })
   .filter(x => x.bottom > x.vb[1] + 0.5 || x.right > x.vb[0] + 0.5 || x.top < -0.5 || x.left < -0.5));
 console.log(JSON.stringify(r, null, 1)); await b.close(); })();
```

14時20分の時点の結果です。01 の「59」は図の上に約18px、04 の数字と枠は数px 出ています。広い画面では図の外にもそのまま描かれ（`overflow: visible`）、スマホでは枠の上下に足した 24px の余白に収まります。08 の上の2つの図（i=8・9）は、枠の内側に収まっていて、はみ出しているのは線の太さの分（1px 未満）だけです。

```
[
 {
  "i": 1,
  "label": "月ごとに公開した作業ログの本数。2025",
  "vb": [
   1080,
   490
  ],
  "bottom": 477,
  "right": 1080,
  "top": -17.7,
  "left": -1
 },
 {
  "i": 4,
  "label": "かかる時間。記事の公開は30〜45分から",
  "vb": [
   1080,
   376
  ],
  "bottom": 381,
  "right": 1080.5,
  "top": -3,
  "left": 0
 },
 {
  "i": 8,
  "label": "ロボットのアームを動かす AI の学習に",
  "vb": [
   540,
   262
  ],
  "bottom": 220.4,
  "right": 475,
  "top": -0.7,
  "left": -0.4
 },
 {
  "i": 9,
  "label": "3Dのファミレスで、配膳ロボット2台が6",
  "vb": [
   540,
   262
  ],
  "bottom": 220.4,
  "right": 435.2,
  "top": -0.7,
  "left": 0
 }
]
```

### コード：CTA の背景画像を作ったスクリプト

`public/site/sim/` の配膳ロボットの店（serving.jpg）と搬送ロボットの病院（hospital.jpg）を、縦長に切って左右に並べ、白黒にして黒を持ち上げ、`public/site/oy/cta-bg.jpg`（1600×900）に書き出しました。

```python
from PIL import Image, ImageOps
def part(f):
    im=Image.open(f'public/site/sim/{f}.jpg').convert('L'); w,h=im.size; cw=int(h*800/900)
    im=im.crop(((w-cw)//2,0,(w-cw)//2+cw,h)).resize((800,900),Image.LANCZOS)
    im=ImageOps.autocontrast(im,cutoff=1); return im.point(lambda v:int(105+v*150/255))
c=Image.new('L',(1600,900),255); c.paste(part('serving'),(0,0)); c.paste(part('hospital'),(800,0))
c.save('public/site/oy/cta-bg.jpg',quality=80,optimize=True)
```

### コード：08 の動画の色を整えた ffmpeg の設定

動画（LOG361 の、アームがブロックを箱に入れる回を6倍速にした16秒）と、表紙の画像に同じ補正をかけました。

```bash
F="colortemperature=temperature=8000:mix=0.6,eq=contrast=1.08:brightness=0.045:saturation=1.10:gamma=1.05,unsharp=5:5:0.4"
ffmpeg -v error -y -i public/site/oy/arm-box.mp4 -vf "$F" -c:v libx264 -preset veryfast -crf 22 -pix_fmt yuv420p -movflags +faststart -an arm-box.mp4
ffmpeg -v error -y -i public/site/oy/arm-box.webp -vf "$F" arm-box.webp
```

ファイルの大きさは、動画が 681,720 バイトから 931,751 バイトに、表紙の画像は 32,320 バイトになりました。

