cursor-animated-sites について
フォルダーツリー上をカーソルが動く、自己完結型 HTML ウォークスルーを作るスキルです。ライフサイクルや手順、データフローを説明でき、`/docs-site-macos` を基盤に手動送り、自動再生、キャプション、役割別のノード色を備えます。
主な機能
- 01単一ファイルの HTML ウォークスルー
- 02カーソルとフォルダーツリーのアニメーション
- 03手動送りと自動再生モード
- 04説明文に合わせたキャプションとファイル表示
- 05役割別のノード色と順序バッジ
- 06視差効果を抑える設定への対応
活用シーン
- 01インストールから実行までの流れを説明する
- 02操作が読み書きするファイルを示す
- 03処理の手順を一つずつ提示する
- 04最終状態を表示したウォークスルーを作る
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rheinmir/ setup --skill cursor-animated-sitesインストール後、Claude Code で/cursor-animated-sitesを入力して読み込まれたか確認します。
詳細
スキル:cursor-animated-sites
フォルダーツリー上でカーソルが段階的に動くアニメーションを備えた、単一ファイルで完結するHTMLのウォークスルーを作成します。これは /docs-site-macos に追加するレイヤーです。そのスキルのガラスデザインシステム(背景プレーン、ガラスの階層、スクロールバー、フォント、リップル、出力パス
llmwiki/html/DDMMYY-<slug>.html、自動ホスティング)を再利用してください。このスキルでは、インタラクティブなウォークスルーを追加します。
docs-site-macos の アクセシビリティとドキュメントヘッドのセクションも継承してください。
<meta viewport>+<title>/description、インラインファビコン、⏮/⏭/▶コントロールの:focus-visibleリング、そしてグローバルなprefers-reduced-motionガードを含みます(ルール11ですでにウォークスルーのアニメーションはスキップされますが、このガードによってカーソルとタイプライターの動きも止めます)。viewportメタなしでページを公開しないでください。モバイルでレスポンシブグリッドが崩れます。
内容
目的とコンテキスト
- 目的:
/docs-site-macosに追加するレイヤーとして、単一ファイルで完結するHTMLのウォークスルーを作成します。ガターのカーソルがコントローラーからファイルへ段階的に移動し、ファイルの作成時に内容が入力され、ノードは役割ごとに色分けされます。手動ステップと自動再生モード、およびフレームごとのキャプションを備えます。 - 使用するタイミング:
- ライフサイクル(インストール → 設定 → 実行)、シーケンス、またはデータフローを説明するとき。
- 視聴者が、各アクションでどのファイルが読み書きされるのか、またその順序を確認する必要がある、「Xは段階的にどう動作するのか」という説明全般。
- トリガーとなる語句:「アニメーション」「カーソルが動く」「ライフサイクル」「段階的」「トランスクリプト」「フォルダーツリーの図解」「/cursor-animated-sites」。
- 対象外: ガラスデザインシステム(背景プレーン、ガラスの階層、スクロールバー、フォント、リップル、出力パス、自動ホスティング)は再定義しません。
/docs-site-macosを再利用してください。このスキルで追加するのは、インタラクティブなウォークスルーのみです。
メンタルモデル
STEPS[] (ph · sp · h · d · flow · chain · role · marks · leak · verdict) + ツリー行(data-born) → 準備(ステップごとのアクセント、bornによる表示) → アニメーション(キャプションのトランスクリプト = マスタークロック、born行に内容を入力、chainの順にカーソルが移動、役割ごとの色 + seqバッジ) → 停止 = 最終フレーム全体を表示
入力と出力の契約
| フィールド | 必須? | 意味 | |
|---|---|---|---|
| 入力 | 説明するライフサイクル/シーケンス | はい | 実際のステップ順序と、各ステップで読み書きするファイル |
| 入力 | フォルダーツリー(ファイル + アクター) | はい | 各ファイルには作成ステップがある |
| 出力 | llmwiki/html/DDMMYY-<slug>.html | はい | docs-site-macosをベースにした単一ファイル、自動ホスティング |
| 出力 | ドラフト出力レポート + インデックス + ログ | はい | OKF YAMLフロントマター |
ルールと機能
苦労して得たルール(要点をまとめたものです。再学習は不要です)。実際の使用から得られた修正点です。違反すると、ページがわかりにくくなります。
- RULE-01 (MUST): カーソルは左のガターに配置し、コンテンツ上には絶対に置きません。
x = rowLeft − ~22pxに置く、小さな右向きの矢印です。主に縦方向に動きます。マウスポインターのグリフを行の上に置くとテキストを覆い、邪魔になります。→ ガターに▶のような矢印を使います。滑らかな動き:leftとtopは同じ時間とイージングを使う必要があります(例:.5s cubic-bezier(.45,.05,.2,1))。タイミングが異なると(例:left が .32s、top が .5s)、x が y より先に到達するため、斜め移動がぎくしゃくします。will-change:left,topを追加します。 - RULE-02 (MUST): 訪問したノードすべてに番号を付けます(インラインの連番バッジ)。 一時停止中も、見る人が順序 ①②③ を読み取れる必要があります。停止時に動くカーソルだけでは、順序がわかりません。
- RULE-03 (MUST): 初期表示は、空の最初のフレームではなく、ステップの最終フレーム全体にします。 読み込み時に、完全なフレーム(すべてのノードを表示し、キャプションも全文表示)を見せます。何も表示されない空の「frame 0」は表示しません。
- RULE-04 (MUST): ファイルは作成されるまで存在しません。 ファイルの「誕生」ステップより前は
display:noneにし、ツリーから完全に除外します(フェードやゴースト表示にはしません)。作成するステップで、タイプ入力のように表示されます。ツリーは時間とともに埋まっていきます。「インストール前」の空のステップを明示的に用意し、空の状態を実際に見られるようにします。 - RULE-05 (MUST): コピーペーストのように一度に出すのではなく、書き起こしのように表示します。 新しいファイルの行はクリップ付きのタイプライター表示にし、キャプションは1文字ずつ表示します。書き起こしを全体の時間基準とし、フレームは固定タイマーではなく、書き起こしの速度に合わせて進めます。
- RULE-06 (MUST): ゆっくり進めます。 動きより読む速度を優先します。1文字あたり約30msにし、フレームの最後には長めの待機時間を設けます。速すぎると、見る人は読むのを諦めてしまいます。
- RULE-07 (MUST): ステップ内では、すべてを同じ色にせず、役割ごとに色を付けます。 特に混乱を招くのは、コピー元(source)とコピー先(dest)が同じ色だと、方向を判別できないことです。source = 青の「読み取り」、dest = ティールの「書き込み/作成」、pass = グレー、deny = 赤、ok = 緑、leak = アンバーとします。行にはテキストタグも付けます。
- RULE-08 (MUST): ステップごとのアクセントカラーは左側のリストとキャプションのチップだけに使います(リストでステップを区別できるようにします)。右側のノードには役割ごとの色を使います。色の体系は2つあり、用途も異なります。同じ要素で混在させないでください。
- RULE-09 (MUST): フレームの下のキャプションは、左のバーの内容を重複させてはいけません。 左側はコンパクトなナビゲーション(ラベルとタイトルのみ)にします。右側のキャプションには、現在のフレームの詳細な説明とフローを表示します。内容を完全に重複させると、どちらも不要だと思われてしまいます。
- RULE-10 (MUST): 左カラムは狭くし、アニメーションに広いスペースを割り当てます。
grid-template-columns: minmax(190px,.5fr) 1.5fr。 - RULE-11 (MUST): モードは2つです。 「1ステップずつ」(⏭ 1フレーム分の書き起こしを再生して停止)と「自動再生」(ゆっくり自動で進める)です。デフォルトは一時停止/手動にします。
prefers-reduced-motionに従い(アニメーションを省略して、フレーム全体を表示します)。 - RULE-12 (MUST): キャプションは平易な言葉で書き、専門用語や極端に省略した表現は使いません。 各フレームのキャプション(
d)は、プロジェクトの外部にいる人にも通じる完全な文にします。各用語は初出時に説明します。「hook」なら「各アクションの前にランタイムが行う処理」、「R1/R2」なら実際の内容を使ったルール(「raw/ への書き込みを禁止するルール」)、「exit 2 / exit 0」なら「コード 2 = BLOCK / コード 0 = PASS」、「assertion」なら「結果が X でなければならないとするチェック」、「layer=repo」なら「変更されたファイルに対して、コミット/PR 時に実行されるチェック」とします。何が読み取られ、何が書き込まれるのか、その理由も説明します。「PreToolUse → LÕI → R1 → exit 2」のように記号だけの簡潔なキャプションは意味がわかりにくく、見てもらえません。文で説明し、短いflow行で省略表記を使います。(dが長い場合は、表示が長引かないよう、1文字あたりのタイプ速度を約18msに下げます。) - 機能:
/docs-site-macosスキルを読み、HTML ファイルと draft/index/log を1つずつ作成し、ローカルで HTML のパースチェックを実行して、自動ホストサーバー経由でページを開きます。
失敗条件
- viewport meta のないページ → モバイルでレスポンシブグリッドが崩れます:失敗。公開しないでください。
- HTML パースチェックでエラーが発生 → 失敗。ホスティング前に修正してください。
- 一時停止時にフレームが読み取れない(連番バッジがない、フレーム 0 が空、キャプションが左側のバーと重複している)→ RULE-02/03/09 に照らして 失敗。修正して、もう一度クリックして確認してください。
手順
メインワークフロー
| ステップ | 種別 | 入力 | アクション | 出力/終了条件 | 失敗時/次の手順 |
|---|---|---|---|---|---|
| W01 | 効果 | docs-site-macos の規約 | ベースページを生成 | llmwiki/html/DDMMYY-<slug>.html にガラス風のベースページ | — |
| W02 | 効果 | ベースページ | 本文をヒーロー + コントロール + ライフサイクル + 凡例に置き換える | 構造 | — |
| W03 | 判断 | 実際のファイル一覧 | data-born + seqslot を含むツリー行と、アクターチップを作成 | ツリー | — |
| W04 | 判断 | ライフサイクル | 空の「before」ステップを最初に含め、役割、判定/漏えい/マークを設定した STEPS[] を作成 | データ | — |
| W05 | 効果 | リファレンスエンジン | エンジン JS + CSS の差分を追加。一時停止/静止状態をデフォルトに設定 | 動作するページ | — |
| W06 | 決定的 | ページ | 検証:HTML パース + 一時停止状態で ⏭ をクリックし、すべてのステップを確認 | すべてのフレームが読み取り可能 | 読み取れない → 修正し、W06 を繰り返す |
各ステップの詳細(W01–W06 の基準となる手順):
/docs-site-macosの規約(ガラス風 CSS、フォント、スクロールバー、背景レイヤー、llmwiki/html/DDMMYY-<slug>.htmlへの出力、自動ホスト)に従ってベースページを生成します。- 本文を上記の
hero + controls + .lifecycle(left steps / right tree-pane) + legend構造に置き換えます。 - フォルダーツリーを作成します。ファイルごとに
.trow id="f-*" data-born="…"を 1 つ置き、それぞれ<span class="seqslot"></span>から始めます。アクターチップも同様に.controllersに配置します。 STEPS[]を作成します。実際のライフサイクルの順序に並べ、明示的な空の「before」ステップを最初に含めます。各ノードのrole(src/dst)を設定し、該当する場合はverdict/leak/marksを追加します。- エンジン JS + CSS の差分を追加します。デフォルトは一時停止/静止状態にします。⏭ はステップ送り、▶ はゆっくり自動再生します。
- 検証します:
python3 -c "import html.parser; html.parser.HTMLParser().feed(open(F).read())"を実行し、自動ホストサーバー経由で開きます。⏭ をクリックしてすべてのステップを確認し、一時停止時に各フレームを明瞭に読み取れることを確認します。
分岐
| ID | 種類 | 条件 | 動作 | スキップ/失敗 | 合流先 |
|---|---|---|---|---|---|
| B01 | 必須条件 | prefers-reduced-motion: reduce | アニメーションをスキップし、すべてのフレームを表示します(RULE-11) | — | W06 |
| B02 | ユーザー任意 | ビューアーが ▶「自動再生」を押す | ゆっくり自動で進みます。⏭ は 1 フレーム再生して停止します | デフォルト = 一時停止/手動 | W06 |
検証と終了条件
決定的な検証:HTML パースチェック。目視確認が必要です:⏭ をクリックしてすべてのステップを確認し、一時停止時に各フレームを明瞭に読み取れることを確認します。すべてのフレームが合格したら終了し、出力レポートを作成します。
例
- 成功例:「インストールのライフサイクルをアニメーション化するページを作成し、ハーネスを設定」→ STEPS は空の「インストール前」から始まり、ステップ 1 ではカーソルが
c-install → f-policy → f-cliを移動し、f-policyには青緑色で「書き込み/作成」と入力されます。一時停止したフレームには ①②③ のバッジと、全文が表示されたキャプションがあります。 - **境界/失敗例:**ソース行と宛先行の両方がステップのアクセント色で色付けされる → ビューアーがコピーの方向を判別できません → RULE-07/08 に違反します。右側のペインのノードを ROLE の色(src は青の「読み取り」、dst は青緑色の「書き込み/作成」)にし、テキストタグを付けます。
参考 — ページ構造
hero (title + 1-line how-to-read)
controls: ⏮ ⏭ Bước tiếp ▶ Tự chạy <hint> <phase chip> <n / N>
.lifecycle (grid: narrow left | wide right)
├─ .steps (LEFT) — compact cards: <span class=sp>label</span><h3>title</h3> (NO paragraph)
└─ .tree-pane (RIGHT, sticky glass)
├─ chrome dots + title
├─ .controllers — actor chips (installer / agent / git / CI), each with a .seqslot
├─ .tree — rows with id=f-*, data-born="<step|−1 pre-exist|99 never>", leading .seqslot
├─ .frame-cap — fcNum / fcSp / fcH / fcD(streams) / fcFlow (the per-frame caption)
├─ .cursor — gutter arrow (fill=currentColor)
└─ .verdict + .leak — floating tags on the RIGHT side near the active row
legend: đọc(blue) · ghi(teal) · cho qua(green) · chặn(red) · lọt(amber)
参考 — STEP データモデル
const STEPS = [
{ ph:'install'|'run', // phase (left chip text)
sp:'CÀI · B0', // short label
h:'Đặt lõi vào repo', // title (left + caption)
d:'... mô tả đầy đủ ...', // detailed narration → ONLY in right caption, streams
flow:'installer → policy.yaml', // 1-line data-flow summary
chain:['c-install','f-policy','f-cli'], // node ids the cursor visits IN ORDER
role:{'f-policy':'dst','f-cli':'dst'}, // per-node role: 'src'|'dst' (pass/actor inferred)
marks:{'f-x':'warn','f-y':'deny'}, // optional: override role on specific nodes
leak:'f-wiki', // optional: node that "leaked" (amber tag)
verdict:{kind:'deny'|'ok'|'neutral', text:'exit 2 · CHẶN'} }, // optional: end badge
];
各ファイル行の data-born:そのファイルが作成されるステップのインデックス(-1 = 既存のベンダーファイル、99 = 作成されないファイル。たとえば書き込みがブロックされた場合)。born === cur のファイルはこのステップで入力されます。born < cur のファイルはすでに存在し、それ以外のファイルは非表示です。
参考 — コア JS(エンジン — 正確に再現すること)
const PAL=[{c:'#0a84ff',bg:'rgba(10,132,255,.12)'},{c:'#30b0c7',bg:'rgba(48,176,199,.13)'},
{c:'#5856d6',bg:'rgba(88,86,214,.12)'},{c:'#34c759',bg:'rgba(52,199,89,.14)'},
{c:'#ff9500',bg:'rgba(255,149,0,.15)'},{c:'#ff2d55',bg:'rgba(255,45,85,.12)'}]; // màu mỗi step
const ROLEINFO={src:{c:'#0a84ff',t:'đọc',cls:'r-src'}, dst:{c:'#30b0c7',t:'ghi/tạo',cls:'r-dst'},
pass:{c:'#9aa3b2',t:'',cls:'r-pass'}, actor:{c:'#9aa3b2',t:'',cls:'r-pass'},
neutral:{c:'#0a84ff',t:'',cls:'r-pass'}, deny:{c:'#e0264b',t:'chặn',cls:'deny'},
ok:{c:'#34c759',t:'cho qua',cls:'ok'}, warn:{c:'#f0902a',t:'lọt',cls:'warn'},
attempt:{c:'#e0264b',t:'',cls:'deny'}};
let cur=0, auto=false, timers=[];
const clear=()=>{timers.forEach(clearTimeout);timers=[]};
// con trỏ ở GUTTER trái (x = rowLeft − 22), y = giữa dòng
const at=id=>{const el=document.getElementById(id),pr=pane.getBoundingClientRect(),r=el.getBoundingClientRect();
return {gx:Math.max(2,r.left-pr.left-22), y:r.top-pr.top+r.height/2, el};};
// hiện nếu pre-existing(-1) hoặc đã tạo ở bước TRƯỚC (b<i); born===i sẽ "gõ" ra trong bước này
function applyVis(i){document.querySelectorAll('.trow[data-born]').forEach(e=>{
const b=+e.dataset.born; e.classList.toggle('notyet', !((b===-1)||(b!==99&&b<i)));});}
function prep(){ const s=STEPS[cur];
/* left active card, prog, phase, caption (fcNum/fcSp/fcH/fcD=full/fcFlow) */
const a=PAL[cur%PAL.length], rs=document.documentElement.style;
rs.setProperty('--ac',a.c); rs.setProperty('--acbg',a.bg); // accent PER-STEP (trái + chip)
document.querySelectorAll('.trow,.chip').forEach(e=>e.classList.remove('hit','deny','ok','warn','attempt','born-now','r-src','r-dst','r-pass'));
document.querySelectorAll('.rtag').forEach(t=>t.remove());
document.querySelectorAll('.seqslot').forEach(s2=>{s2.textContent='';s2.className='seqslot';});
applyVis(cur); return s; }
function paint(idx,s){ const id=s.chain[idx], last=idx===s.chain.length-1, vk=s.verdict?.kind||'';
const el=document.getElementById(id); el.classList.remove('notyet'); const p=at(id);
let role = id==='f-raw' ? 'attempt'
: (s.marks&&s.marks[id]) || (last&&vk) || (s.role&&s.role[id]) || (id.indexOf('c-')===0?'actor':'pass');
const ri=ROLEINFO[role]||ROLEINFO.pass;
el.classList.add('hit'); if(ri.cls)el.classList.add(ri.cls); if(id==='f-raw')el.classList.add('attempt');
const slot=el.querySelector('.seqslot'); if(slot){slot.textContent=idx+1; slot.className='seqslot on '+(ri.cls||'');}
if(ri.t && id.indexOf('c-')!==0){const tag=document.createElement('span');tag.className='rtag '+ri.cls;tag.textContent=ri.t;el.appendChild(tag);}
cursor.style.left=p.gx+'px'; cursor.style.top=(p.y-9)+'px'; cursor.style.color=ri.c; // cursor đổi màu theo vai trò
/* leak tag + verdict tag positioned on the RIGHT side near p.y */ }
function revealBorn(i){document.querySelectorAll('.trow[data-born]').forEach(e=>{if(+e.dataset.born===i)e.classList.remove('notyet');});}
function typeRow(e,dur){e.classList.remove('notyet'); if(dur<=0)return; e.style.setProperty('--td',dur+'ms');
e.classList.add('typing'); timers.push(setTimeout(()=>e.classList.remove('typing'),dur+40));}
function typeText(el,txt,per){el.textContent='';el.classList.add('streaming');let i=0;
const tick=()=>{el.textContent=txt.slice(0,i); i++<txt.length?timers.push(setTimeout(tick,per)):el.classList.remove('streaming');};tick();}
function full(){const s=prep(); revealBorn(cur); s.chain.forEach((id,idx)=>paint(idx,s));} // TĨNH = frame đầy đủ
function animate(){const s=prep();
if(reduce){revealBorn(cur); s.chain.forEach((id,idx)=>paint(idx,s)); return;}
const per=30, capDur=Math.max(1600, s.d.length*per); // transcript = đồng hồ; CHẬM
typeText(document.getElementById('fcD'), s.d, per);
const born=[...document.querySelectorAll('.trow[data-born]')].filter(e=>+e.dataset.born===cur);
let bt=400; const slot=born.length?(capDur*0.55)/born.length:0;
born.forEach(e=>{timers.push(setTimeout(()=>typeRow(e,Math.min(slot*0.8,700)),bt)); bt+=slot;});
const n=Math.max(1,s.chain.length);
s.chain.forEach((id,idx)=>timers.push(setTimeout(()=>paint(idx,s), 300+Math.round(idx*(capDur-300)/n))));
if(auto) timers.push(setTimeout(()=>step(cur+1), capDur+2200)); // CHỈ tự sang khi auto
}
function step(i){clear(); cur=(i+STEPS.length)%STEPS.length; animate();} // Tự bước: gõ 1 frame rồi dừng
// prev/next: auto=false; step(±1). play: auto=!auto; auto?step(cur):full().
// LOAD: requestAnimationFrame(()=>setTimeout(()=>{cur=0;full();},200)); // mặc định TĨNH frame đầy đủ
参考 — 必須 CSS 差分(docs-site-macos に追加)
.lifecycle{display:grid;grid-template-columns:minmax(190px,.5fr) 1.5fr;gap:22px;align-items:start}
.steps{max-height:74vh;overflow-y:auto} .step{opacity:.62} .step.active{opacity:1;border-left:3px solid var(--ac)}
.tree-pane{position:sticky;top:18px} .trow{display:flex;align-items:center;white-space:pre;border-radius:8px;padding:5px 8px}
.seqslot{display:inline-flex;align-items:center;justify-content:center;width:17px;height:17px;margin-right:8px;border-radius:50%;font-size:10px;font-weight:800;background:transparent;color:transparent;border:1px solid rgba(30,90,170,.1)}
.seqslot.r-src{background:#0a84ff;color:#fff;border-color:transparent}
.seqslot.r-dst{background:#30b0c7;color:#fff;border-color:transparent}
.seqslot.r-pass{background:#9aa3b2;color:#fff;border-color:transparent}
.trow.hit.r-src{background:rgba(10,132,255,.13);box-shadow:inset 0 0 0 1px #0a84ff}
.trow.hit.r-dst{background:rgba(48,176,199,.17);box-shadow:inset 0 0 0 1px #30b0c7}
.trow.hit.r-pass{background:rgba(140,150,165,.12);box-shadow:inset 0 0 0 1px rgba(120,130,145,.5)}
.rtag{margin-left:9px;font-size:10px;font-weight:700;padding:1px 8px;border-radius:11px}
.rtag.r-src{background:rgba(10,132,255,.14);color:#0a84ff} .rtag.r-dst{background:rgba(48,176,199,.2);color:#1d7d8c}
.trow.notyet{display:none} /* CHƯA tạo → ẩn hẳn (không mờ) */
.trow.typing{overflow:hidden;animation:typeline var(--td,500ms) steps(30) both}
@keyframes typeline{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}
.cursor{position:absolute;z-index:7;will-change:left,top;color:var(--ac);
transition:left .5s cubic-bezier(.45,.05,.2,1),top .5s cubic-bezier(.45,.05,.2,1),color .3s ease} /* MƯỢT: left+top CÙNG duration+easing, đừng lệch */
.cursor svg path{fill:currentColor} /* arrow color = currentColor (set per role) */
.fc-sp{background:var(--acbg);color:var(--ac)} /* caption chip = per-step accent */
納品 — 出力レポート
llmwiki/wiki/sources/draft/DDMMYY-<ten>.md に propose の下書きを作成し、wiki/index.md + wiki/log.md に追記します。
YAML frontmatter を使用します(OKF v0.1 準拠 — 太字の **Type:** は使用しないでください。OKF に失敗します):
---
type: draft
title: DDMMYY-<ten>
status: proposed
tags: [cursor-animated-sites, output-report]
timestamp: YYYY-MM-DD
---
# DDMMYY-<ten>
## What
<một câu — trang này dựng/quyết định cái gì>
## Files
| File | Action |
|------|--------|
| `llmwiki/html/DDMMYY-<ten>.html` | created |
## Origin
- **Draft:** `wiki/sources/draft/DDMMYY-<ten>.md`
- **Commit:** _(verify-before-commit điền)_
起源
llmwiki/html/250626-harness-lifecycle.htmlを構築したセッションから、/orca-evalによって抽出されました。- ベーステーマ:
/docs-site-macos(~/.claude/skills/docs-site-macos/SKILL.md)。