Communitygithub.com

Jav1es/dsh-skills

ゼロから構築/リファクタリングする純静的マルチページポートフォリオ(フレームワーク・ビルドツール不使用)。データ駆動レンダリング、SVG 模式図、3D とアニメーション、パフォーマンス最適化、リリース前検証をカバー。ユーザーが「ポートフォリオサイト/マルチページサイト/作品カード追加/ページ美化/リリース前チェック/ページのカクつき最適化/GitHub Pages へのデプロイ」を依頼した際に使用。実戦で遭遇したハマりどころと検証方法も含む。

dsh-skills とは?

このスキルは、純粋な HTML5 + CSS3 + ネイティブ JavaScript のみを使用し、フレームワークやビルドツールに依存しないマルチページポートフォリオサイトを構築するための包括的なガイドです。主な特徴として、JSON データ駆動によるコンテンツ管理(作品データを一元管理し、各ページで自動レンダリング)、外部画像を一切使わないインライン SVG 模式図(幾何学モチーフを使い分け)、パフォーマンスに配慮した 3D 視差効果とアニメーション(`filter: blur` を避け `transform` / `opacity` のみ使用)、そして安全なエントリーアニメーション(初期非表示を JS で制御し、白画面を防止)が含まれます。また、`<script>` タグのペアチェックやスクリーンショット検証、GitHub Pages へのデプロイ手順など、実運用で役立つ検証・デプロイチェックリストも提供します。このスキルは、Claude Code、Coder、Codex、GitHub Copilot などの AI エージェントが、ユーザーのポートフォリオサイト構築・改善要求に応じて活用できます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Jav1es/dsh-skills/tree/HEAD/static-portfolio-build

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

static-portfolio-build — 静的マルチページポートフォリオ

技術的制約:HTML5 + CSS3 + ネイティブ JS のみ。Bootstrap/Tailwind などの UI フレームワーク、外部 CDN フォントは禁止。 メインカラー ≤3 色、フォント ≤2 種類、画像には必ず alt 属性を付与(SVG は role="img" + aria-label)。 ファーストビューの 3G 想定読み込み時間 ≤3 秒(1.6Mbps / RTT 400ms で試算)。

一、アーキテクチャ:データ駆動、一元管理

各 HTML に作品内容を手書きしない。 JSON データとレンダリング層に分離:

works-data.json    データソース(スクリプト自動生成、手動編集禁止)
works-data.js      同データのインライン版(window.__WORKS_DATA__)—— file:// ダブルクリック時の CORS 回避用
works.js           レンダリング層:ROLES / cardHTML() / tileHTML() / bindFilter() / bindExpand()
extract-works.mjs  旧単一ページから作品データを抽出
patch-works.mjs    番号再割り当て/モジュール補完/モチーフ割り当て(べき等必須!)

なぜ JS インラインデータが必須か:fetch('./works-data.json') は file:// プロトコルで CORS にブロックされる。ユーザーがダブルクリックで開くと真っ白に。

べき等パッチスクリプト:patch-works.mjs は何度でも安全に実行できるようにする。EXTRA_TITLES を使って既に補完済みのエントリをフィルタリングしないと、2 回目の実行で「マッピングテーブルにありません」エラーが発生。

モジュール番号と実ディレクトリの一致:作品番号はカテゴリ順ではなく、ローカルディレクトリ/GitHub リポジトリの実際の配置に合わせる。「ディレクトリがあるものはディレクトリ番号に合わせ、ないものは空き番を埋める」方式で連続性を確保。番号変更時は全ページの参照(トップカード、職種ページバナー、アバウトページのカテゴリ)を同期させる——トップカードはデータから自動生成させるのがベスト、手書きによる不整合を防ぐ:

document.querySelectorAll('[data-owns]').forEach(function (el) {
  var r = WORKS.ROLES[el.getAttribute('data-owns')];
  if (r) el.textContent = '担当 ' + r.owns.length + ' モジュール(' + r.owns.join(' / ') + ')';
});

二、スクリーンショットがない場合:インライン SVG 模式図

作品にスクリーンショットがないケースが多い。カテゴリごとに幾何学モチーフを割り当て、インライン SVG を生成(外部画像ゼロ、ファーストビューでの画像リクエストゼロ):

カテゴリモチーフ要素
企画roadmapノード + 破線接続 + マイルストーン
ワークフローpipeline入力→処理→審査→出力 + 矢印マーカー
エージェントneural階層ノード + 全結合エッジ
データmetrics棒グラフ + 折れ線トレンド
オープンソースterminalダークウィンドウ + カラーコード行 + カーソル
業界gridブループリントグリッド

すべての SVG に role="img" + 意味のある aria-label を必須。

ハマりどころ:

  • SVG 内の <text> がカードのグラデーションに隠れる → テキストは SVG 外に出し、HTML オーバーレイ .w-cover-tag で対応(position:absolute)
  • preserveAspectRatio は meet を使用、slice は不可(切り抜き防止)
  • 配列インデックスのずれ:モチーフデータを [x, y, テキスト, 色] で定義したが、レンダリング側が [x, y, width, color] として l[2] を参照 → 矩形がすべて無効になりカーソルだけ表示。モチーフ定義時はインデックスの意味を事前確認。

三、3D と動き(見た目は良いがカクつかないこと)

背景:SpaceX 風透視グリッド

.bg-grid-floor {
  position: absolute; left: -30%; right: -30%; top: 16%; height: 90%;
  background-image:
    repeating-linear-gradient(90deg,  rgba(10,10,10,.16) 0 1px, transparent 1px 74px),
    repeating-linear-gradient(180deg, rgba(10,10,10,.15) 0 1px, transparent 1px 40px);
  transform: perspective(520px) rotateX(72deg) translateZ(0);
  transform-origin: 50% 0%;
  mask-image: linear-gradient(to bottom, transparent 0%, #000 11%, #000 52%, transparent 90%);
  animation: floorDrift 30s linear infinite;   /* background-position を変更、transform は使わない */
}

さらに地平線グロウ線 + 3 つの浮遊ライトスポット(各 24/29/34 秒のブレス)。

パフォーマンスの鉄則(カクつきの分岐点)

filter: blur() と backdrop-filter は絶対に使わない。 これらは毎フレーム背後コンテンツを再ラスタライズ/再サンプリングし、スクロール時のカクつき最大原因:

  • ソフトなライトスポットには radial-gradient(circle, rgba(...).10, transparent 68%) —— グラデーション自体がソフトなので blur 不要
  • すりガラス風ナビゲーションはほぼ不透明な背景 rgba(255,255,255,.96) で代用
  • セクション背景は半透明の白ベタ、backdrop-filter は使わない

アニメーションは transform / opacity のみ(コンポジターで処理、リフロー・再描画をトリガーしない)。スクロールパララックスは requestAnimationFrame でスロットリング + translate3d。

カードの 3D 傾き

pointermove で CSS 変数を書き込み、DOM には触れない:

.tilt { transform: perspective(1000px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)); }
.tilt.is-tilting { transition: transform .06s linear; }  /* 追従時は transition を短縮 */

.tilt .w-body { transform: translateZ(18px); } でパララックス浮遊効果;::before の放射状ハイライトが --mx/--my に追従。

ホバー移動とエントリーアニメーションが transform を奪い合う → ホバー時は box-shadow/border-color のみ変更、移動はすべて .tilt に任せる。

四、エントリーアニメーション:純 CSS の opacity:0 は絶対に避ける

これが最大のハマりどころ。 純 CSS で初期非表示+JS で .is-revealed 追加方式は、IntersectionObserver がコールバックしない場合(ヘッドレスブラウザ、JS エラー、異常パス)ページ全体が永久に非表示になる。

正しい方法:JS で必要な要素だけに非表示クラスを付与、未付与の要素はデフォルトで表示:

.w-card.reveal-armed { opacity: 0; transform: translateY(18px); }
.is-revealed { opacity: 1 !important; transform: none !important; }
list.forEach(function (el) {
  var r = el.getBoundingClientRect();
  var inView = r.top < innerHeight + 150 && r.bottom > -150;
  if (!inView) el.classList.add('reveal-armed');   // ビューポート外のみ非表示化
  io.observe(el);
});
// フォールバック:3 秒後も非表示の要素を強制表示
setTimeout(function () { /* force reveal */ }, 3000);

ビューポート内は即座に表示、ビューポート外のみアニメーション対象 — ファーストビューがアニメーションに邪魔されず、異常パスでも白画面にならない。

五、スクリプト注入:<script> の開始・終了タグペアを検証

複数ページに一括でコードを注入する際、<script> と </script> の数が一致していることを必ずチェックする。

実際の事故:注入時に <script> 開始タグが一つ多い → それ以降のインラインスクリプトブロックが JS パーサーに飲み込まれる → 4 つの職種ページレンダリングコードがすべて無効に。ページは一見正常(タイトル、ナビ、責務リストは表示)で、作品エリアだけ空白、目視ではほぼ発見不可。

正規表現注入時の注意:LastIndexOf('<script>') は <script src=...> にもマッチするため、定義がタグの外側に差し込まれ、表示テキストとして可視化される。明示的なアンカー文字列を使用する。

六、検証方法(再利用可能)

スクリーンショット検証(重要:同名ファイルはキャッシュされる)

vision_html_screenshot は同名ファイルに対してキャッシュを使用する。CSS 変更後は Copy-Item で一時的に .verify/ 内の別名ファイルにコピーしてからスクリーンショットを撮り、検証後サンドボックスを削除する。

同期プローブ(load + setTimeout は使わない)

ヘッドレス環境では window.load + setTimeout は信頼できない。プローブをレンダリングコールバック内部に直接記述(同期的に実行)、position:fixed;bottom:0 の <pre> に出力する:

WORKS.loadData(function (data) {
  root.innerHTML = WORKS.listByRole(data.items, 'fde');
  var d = document.createElement('pre');
  d.style.cssText='position:fixed;bottom:0;left:0;right:0;z-index:99999;background:#000;color:#0f0;font:11px monospace;padding:4px;margin:0';
  d.textContent = 'CARDS=' + root.querySelectorAll('.w-card').length;
  document.body.appendChild(d);
});

⚠️ プローブの位置指定に top を使ってはならない — カードの 3D 傾き transform が fixed 要素を押しずらす。

一括検証(ページごとにファイルを読むより効率的)

  • デッドリンク:正規表現で ./xx/yy.md を抽出 → Test-Path で個別検証
  • 番号の連続性:Compare-Object で期待シーケンス vs 実際
  • ディレクトリ対応:PowerShell は '01' を int の 1 に変換するため、[string] で明示的にキャスト必須
  • 高負荷機能の残存チェック:[regex]::Matches($css,'filter:\s*blur|backdrop-filter')、コメント内の記述に注意

七、GitHub Pages へのデプロイ

gh repo create <user>/<repo> --public --description "..."
git remote add origin https://github.com/<user>/<repo>.git
git push -u origin main

デプロイ前の必須チェック:

  1. git ls-files で依存ディレクトリが実際に追跡されているか確認(ローカルにファイルがあっても git 追跡とは別)
  2. リモートに既存コンテンツがある場合は先に git ls-tree --name-only origin/main で比較、上書きで失わないように
  3. ローカルの相対パスで依存している兄弟プロジェクトディレクトリ:一緒にプッシュするか、GitHub リポジトリ URL に変更する
  4. GitHub Pages は Settings → Pages → Source でブランチを選択
  5. サブアプリ(例:hitech-self-assessment/)がある場合、メインサイトの置き換えで上書きされないよう注意

パフォーマンス推定:総バイト数 / 8 / 1.6e6 + 4 * 0.4(秒)

八、納品前自己チェック(web-delivery-checklist 準拠)

  • 全 4〜5 ページのスクリーンショット検証済み(トップページのみではない)
  • デッドリンク 0、alt 漏れ 0
  • <script> 開始・終了タグペア一致、CSS 中括弧のバランス
  • filter: blur / backdrop-filter の実際の使用数 = 0
  • 3G 推定読み込み時間 < 3 秒
  • メインカラー ≤3、フォント ≤2、外部 CDN リソース 0
  • モバイル(390px)でハンバーガーメニューの開閉が可能、aria-expanded が同期
  • prefers-reduced-motion 設定時は全アニメーションをオフ

関連スキル