Lecture 1プロジェクト設計 — ポートフォリオの構成を考える

12:00

プロジェクト設計 — ポートフォリオの構成を考える

ポートフォリオサイトを作り始める前に、最も重要なステップがあります。それは「設計」です。いきなりコードを書き始めるのではなく、どんなサイトにしたいのか、どんなセクションが必要なのか、全体の構成を考えることから始めましょう。この講義では、Claude Code を使ってプロジェクトの設計段階を効率化する方法を学びます。

ポートフォリオサイトに必要な要素

プロフェッショナルなポートフォリオサイトには、以下の要素が不可欠です。

ヘッダー・ナビゲーション: サイト名やロゴ、主要ページへのリンクを配置します。訪問者が最初に目にする部分であり、サイト全体の印象を決定づけます。

ヒーローセクション: ファーストビューに表示される大きなエリアです。あなたの名前、肩書き、キャッチコピーを配置して、訪問者に「この人は何をしている人なのか」を一瞬で伝えます。

プロフィール(About)セクション: 自己紹介、経歴、スキルセットを詳しく紹介します。写真を含めると信頼感が増します。

作品ギャラリー(Works)セクション: これまでの制作物やプロジェクトをカード形式で一覧表示します。ポートフォリオの核心となる部分です。

お問い合わせ(Contact)セクション: メールアドレスや問い合わせフォーム、SNSリンクなど、連絡手段を提供します。

フッター: コピーライト表記やSNSリンク、サイトマップなどを配置します。

Claude Code でプロジェクト構成を設計する

Claude Code を使えば、プロジェクトの設計を対話的に進められます。まず、ターミナルを開いて作業ディレクトリを作成しましょう。

> ポートフォリオサイトのプロジェクトフォルダ構成を作って。
  以下のファイルが必要です:
  - index.html(メインページ)
  - css/style.css(スタイルシート)
  - css/reset.css(リセットCSS)
  - js/main.js(JavaScript)
  - images/(画像フォルダ)

Claude Code は以下のようなフォルダ構成を生成してくれます。

portfolio/
├── index.html
├── css/
│   ├── style.css
│   └── reset.css
├── js/
│   └── main.js
└── images/
    └── .gitkeep

実際にフォルダとファイルを作成するコマンドも Claude Code に聞いてみましょう。

> 上記のフォルダ構成を作成するbashコマンドを書いて

Claude Code が生成するコマンドは次の通りです。

mkdir -p portfolio/{css,js,images}
touch portfolio/index.html
touch portfolio/css/{style.css,reset.css}
touch portfolio/js/main.js
touch portfolio/images/.gitkeep

ワイヤーフレームの作成

サイトの構成が決まったら、各セクションのレイアウトを大まかに決めるワイヤーフレームを作成します。Claude Code にテキストベースのワイヤーフレームを作ってもらいましょう。

> ポートフォリオサイトのワイヤーフレームをASCIIアートで作って。
  セクションは: ヘッダー、ヒーロー、About、Works、Contact、フッター
  デスクトップ版(幅1200px想定)で

Claude Code は以下のようなワイヤーフレームを生成します。

┌──────────────────────────────────────┐
  Logo          Home About Works Contact     Header / Nav
├──────────────────────────────────────┤
                                          
         こんにちは、○○です                  Hero Section
      Webエンジニア / デザイナー          
         [ 作品を見る ]                   
                                          
├──────────────────────────────────────┤
  [Photo]    About Me                    
             自己紹介テキスト ...            About Section
             Skills: HTML CSS JS ...     
├──────────────────────────────────────┤
  ┌──────┐ ┌──────┐ ┌──────┐            
  Work 1 Work 2 Work 3               Works Section
  └──────┘ └──────┘ └──────┘            
  ┌──────┐ ┌──────┐ ┌──────┐            
  Work 4 Work 5 Work 6            
  └──────┘ └──────┘ └──────┘            
├──────────────────────────────────────┤
  お問い合わせ                            
  [ 名前     ] [ メール   ]                 Contact Section
  [ メッセージ                ]           
  [ 送信 ]                               
├──────────────────────────────────────┤
  © 2026 Your Name  |  SNS Links            Footer
└──────────────────────────────────────┘

このワイヤーフレームをガイドとして使いながら、次の講義からHTML/CSSのコーディングに入っていきます。

カラースキームとデザイン方針を決める

最後に、デザインの方向性を Claude Code と一緒に決めましょう。

> ポートフォリオサイトのカラースキームを提案して。
  条件:
  - プロフェッショナルで落ち着いた印象
  - ダークモード風
  - アクセントカラーは青系
  CSS変数として定義して

Claude Code が提案するカラースキームの例です。

:root {
  /* メインカラー */
  --color-bg: #0f172a;
  --color-bg-secondary: #1e293b;
  --color-text: #e2e8f0;
  --color-text-muted: #94a3b8;

  /* アクセントカラー */
  --color-accent: #3b82f6;
  --color-accent-hover: #60a5fa;

  /* ボーダー・シャドウ */
  --color-border: #334155;
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5);

  /* フォント */
  --font-main: 'Noto Sans JP', sans-serif;
  --font-heading: 'Noto Sans JP', sans-serif;
}

このように、Claude Code を活用すれば設計段階から効率的に進められます。カラースキーム、フォント選び、レイアウト構成など、通常は時間のかかる意思決定を AI と対話しながら素早く行えます。

演習問題

  1. プロジェクトフォルダの作成: 上記のコマンドを実行して、ポートフォリオサイトのプロジェクトフォルダを実際に作成してください。

  2. オリジナルのワイヤーフレーム作成: Claude Code に自分の職種や目的に合わせたワイヤーフレームを作ってもらいましょう。例えば「フリーランスのイラストレーター向け」「学生のインターン応募用」などの条件を加えてみてください。

  3. カラースキームのカスタマイズ: Claude Code に3種類の異なるカラースキーム(ライトモード、ダークモード、カラフル)を提案してもらい、気に入ったものを style.css に記述してください。

  4. 競合サイト分析: 気に入ったポートフォリオサイトのURLを Claude Code に伝えて、その構成要素を分析してもらいましょう。

参考資料

Lecture 2HTML構造 — セマンティックなマークアップを作る

12:00

HTML構造 — セマンティックなマークアップを作る

前回の講義でプロジェクトの設計とフォルダ構成を整えました。この講義では、いよいよ HTML のコーディングに入ります。Claude Code を使って、セマンティック(意味的)な HTML5 マークアップを効率的に生成する方法を学びましょう。セマンティック HTML を正しく使うことで、アクセシビリティ、SEO、コードの保守性が大幅に向上します。

セマンティック HTML5 の基本要素

HTML5 で導入されたセマンティック要素は、ページの構造に「意味」を与えます。従来の <div> だけで構成するのではなく、適切なタグを使い分けることが重要です。

  • <header> — ページやセクションのヘッダー(ロゴ、ナビゲーション)
  • <nav> — ナビゲーションリンクのグループ
  • <main> — ページのメインコンテンツ(1ページに1つだけ)
  • <section> — テーマごとのコンテンツブロック
  • <article> — 独立した完結するコンテンツ
  • <aside> — 補足的なコンテンツ
  • <footer> — ページやセクションのフッター

これらを正しく使うことで、スクリーンリーダーがページ構造を正確に読み取れるようになり、検索エンジンもコンテンツの階層を理解しやすくなります。

Claude Code で index.html を生成する

Claude Code に、ポートフォリオサイトの HTML 構造全体を生成してもらいましょう。

> ポートフォリオサイトのindex.htmlを作って。
  以下のセクションを含むセマンティックHTML5で:
  - header: ロゴ(テキスト)+ ナビゲーション(Home, About, Works, Contact)
  - hero: 名前、肩書き、CTAボタン
  - about: プロフィール写真 + 自己紹介 + スキル一覧
  - works: プロジェクトカード6枚(グリッドレイアウト用)
  - contact: お問い合わせフォーム
  - footer: コピーライト + SNSリンク
  CSSは外部ファイル(css/style.css)で読み込んで
  Google Fonts(Noto Sans JP)も読み込んで

Claude Code が生成する HTML は以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>山田太郎 | ポートフォリオ</title>
  <meta name="description" content="山田太郎のポートフォリオサイト。Web開発の実績を紹介しています。">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="css/reset.css">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <!-- ヘッダー -->
  <header class="header">
    <div class="container">
      <a href="#" class="logo">Portfolio</a>
      <nav class="nav">
        <button class="nav-toggle" aria-label="メニューを開く">
          <span class="hamburger"></span>
        </button>
        <ul class="nav-list">
          <li><a href="#hero" class="nav-link">Home</a></li>
          <li><a href="#about" class="nav-link">About</a></li>
          <li><a href="#works" class="nav-link">Works</a></li>
          <li><a href="#contact" class="nav-link">Contact</a></li>
        </ul>
      </nav>
    </div>
  </header>

  <main>
    <!-- ヒーローセクション -->
    <section id="hero" class="hero">
      <div class="container">
        <h1 class="hero-title">山田 太郎</h1>
        <p class="hero-subtitle">Web Developer / Designer</p>
        <a href="#works" class="btn btn-primary">作品を見る</a>
      </div>
    </section>

    <!-- Aboutセクション -->
    <section id="about" class="about">
      <div class="container">
        <h2 class="section-title">About</h2>
        <div class="about-content">
          <div class="about-image">
            <img src="images/profile.jpg" alt="山田太郎のプロフィール写真" width="300" height="300">
          </div>
          <div class="about-text">
            <p>こんにちは、山田太郎です。Webエンジニアとして3年間の経験があります。</p>
            <p>フロントエンドからバックエンドまで幅広く対応できます。</p>
            <div class="skills">
              <h3>スキル</h3>
              <ul class="skill-list">
                <li class="skill-item">HTML5 / CSS3</li>
                <li class="skill-item">JavaScript</li>
                <li class="skill-item">React</li>
                <li class="skill-item">Node.js</li>
                <li class="skill-item">Python</li>
                <li class="skill-item">Figma</li>
              </ul>
            </div>
          </div>
        </div>
      </div>
    </section>

    <!-- Worksセクション -->
    <section id="works" class="works">
      <div class="container">
        <h2 class="section-title">Works</h2>
        <div class="works-grid">
          <article class="work-card">
            <img src="images/work-01.jpg" alt="プロジェクト1" class="work-image">
            <div class="work-info">
              <h3 class="work-title">ECサイト</h3>
              <p class="work-desc">React + Node.jsで構築したECサイト</p>
              <div class="work-tags">
                <span class="tag">React</span>
                <span class="tag">Node.js</span>
              </div>
            </div>
          </article>
          <!-- work-card を6つ繰り返す -->
        </div>
      </div>
    </section>

    <!-- Contactセクション -->
    <section id="contact" class="contact">
      <div class="container">
        <h2 class="section-title">Contact</h2>
        <form class="contact-form" action="https://formspree.io/f/your-id" method="POST">
          <div class="form-group">
            <label for="name">お名前</label>
            <input type="text" id="name" name="name" required>
          </div>
          <div class="form-group">
            <label for="email">メールアドレス</label>
            <input type="email" id="email" name="email" required>
          </div>
          <div class="form-group">
            <label for="message">メッセージ</label>
            <textarea id="message" name="message" rows="5" required></textarea>
          </div>
          <button type="submit" class="btn btn-primary">送信する</button>
        </form>
      </div>
    </section>
  </main>

  <!-- フッター -->
  <footer class="footer">
    <div class="container">
      <div class="footer-links">
        <a href="https://github.com/" target="_blank" rel="noopener noreferrer">GitHub</a>
        <a href="https://twitter.com/" target="_blank" rel="noopener noreferrer">X (Twitter)</a>
        <a href="https://linkedin.com/" target="_blank" rel="noopener noreferrer">LinkedIn</a>
      </div>
      <p class="copyright">&copy; 2026 山田太郎. All rights reserved.</p>
    </div>
  </footer>

  <script src="js/main.js"></script>
</body>
</html>

リセット CSS の設定

ブラウザごとのデフォルトスタイルの違いをなくすため、リセット CSS を用意します。Claude Code に生成してもらいましょう。

> モダンなCSS Resetを作って。
  box-sizing: border-box をデフォルトに、
  margin/paddingリセット、
  img の max-width: 100%、
  リスト装飾の除去を含めて。
  css/reset.css に書いて

Claude Code が生成するリセット CSS はこのようになります。

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100vh;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}

input,
button,
textarea,
select {
  font: inherit;
}

ul,
ol {
  list-style: none;
}

a {
  color: inherit;
  text-decoration: none;
}

h1, h2, h3, h4, h5, h6 {
  overflow-wrap: break-word;
}

HTML の構造を確認・修正する

コードが生成されたら、Claude Code に構造の確認もお願いしましょう。

> index.htmlのアクセシビリティをチェックして。
  aria属性の不足、alt属性の確認、
  見出し階層の正しさを確認して

Claude Code はコードを分析し、以下のような改善点を指摘してくれます。

  • <nav>aria-label="メインナビゲーション" を追加すべき
  • ハンバーガーメニューボタンに aria-expanded="false" を追加すべき
  • 見出しレベルが h1h2h3 の順で正しく階層化されているか
  • すべての <img> に適切な alt テキストがあるか

このように Claude Code を使えば、コードの生成だけでなく、品質チェックにも活用できます。HTMLの基礎を理解しながら、AIの力で効率的にコーディングを進めていきましょう。

演習問題

  1. HTML の生成と確認: 上記の Claude Code プロンプトを実行して、実際に index.htmlreset.css を生成してください。ブラウザで開いて、構造が正しく表示されることを確認しましょう。

  2. セクションの追加: Claude Code に「ブログセクション」または「タイムライン(経歴)セクション」を追加するよう依頼してみてください。セマンティックなHTMLで生成されているか確認しましょう。

  3. アクセシビリティの改善: Claude Code に index.html のアクセシビリティチェックを依頼し、指摘された点を修正してください。

  4. 独自の内容に書き換え: 山田太郎の部分を自分の名前や肩書きに変更し、スキル一覧も自分のスキルに合わせてカスタマイズしてください。

参考資料

Lecture 3CSSデザイン — モダンなスタイリングを適用する

12:00

CSSデザイン — モダンなスタイリングを適用する

HTML の構造が完成したら、次はビジュアルデザインです。この講義では、Claude Code を活用して CSS Grid、Flexbox、カスタムプロパティ(CSS変数)を使ったモダンなスタイリングをポートフォリオサイトに適用していきます。ゼロからデザインを考える必要はありません。Claude Code に方向性を伝えれば、プロフェッショナルなスタイルが生成されます。

CSS カスタムプロパティとベーススタイル

まず、サイト全体で使う基本的なスタイルを定義しましょう。CSS カスタムプロパティ(CSS変数)を使うと、色やフォントサイズなどを一箇所で管理でき、後からの変更が容易になります。

> ポートフォリオサイトのベーススタイルをCSSで作って。
  条件:
  - ダークモード(背景: 濃紺系、文字: 明るいグレー)
  - CSS変数で色・フォントサイズ・スペーシングを管理
  - containerクラス(max-width: 1200px, 中央揃え)
  - セクション共通のpadding
  - Noto Sans JP をベースフォントに
  css/style.css に書いて

Claude Code が生成するベーススタイルです。

/* ========================================
   CSS Custom Properties
   ======================================== */
:root {
  /* Colors */
  --color-bg: #0f172a;
  --color-bg-secondary: #1e293b;
  --color-bg-card: #1e293b;
  --color-text: #e2e8f0;
  --color-text-muted: #94a3b8;
  --color-accent: #3b82f6;
  --color-accent-hover: #60a5fa;
  --color-border: #334155;

  /* Shadows */
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5);

  /* Spacing */
  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 2rem;
  --space-lg: 4rem;
  --space-xl: 6rem;

  /* Font sizes */
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.25rem;
  --text-xl: 1.5rem;
  --text-2xl: 2rem;
  --text-3xl: 3rem;

  /* Border radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;

  /* Transitions */
  --transition-fast: 0.2s ease;
  --transition-normal: 0.3s ease;
}

/* ========================================
   Base Styles
   ======================================== */
body {
  font-family: 'Noto Sans JP', sans-serif;
  font-size: var(--text-base);
  color: var(--color-text);
  background-color: var(--color-bg);
  line-height: 1.8;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--space-md);
}

.section-title {
  font-size: var(--text-2xl);
  font-weight: 700;
  text-align: center;
  margin-bottom: var(--space-lg);
  position: relative;
}

.section-title::after {
  content: '';
  display: block;
  width: 60px;
  height: 3px;
  background: var(--color-accent);
  margin: var(--space-sm) auto 0;
  border-radius: 2px;
}

section {
  padding: var(--space-xl) 0;
}

ヘッダーとナビゲーションのスタイル

ヘッダーは固定配置にして、スクロールしても常に表示されるようにします。

> ヘッダーのCSSを書いて。
  - position: fixedで上部固定
  - 背景は半透明ダーク + backdrop-filter: blur
  - ロゴは左寄せ、ナビは右寄せ(Flexbox)
  - ナビリンクにホバーエフェクト
  - z-indexを高くして他要素の上に表示
/* ========================================
   Header
   ======================================== */
.header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1000;
  background: rgba(15, 23, 42, 0.9);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border);
  padding: var(--space-sm) 0;
}

.header .container {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.logo {
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--color-accent);
  letter-spacing: 0.05em;
}

.nav-list {
  display: flex;
  gap: var(--space-md);
}

.nav-link {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-muted);
  transition: color var(--transition-fast);
  position: relative;
}

.nav-link:hover,
.nav-link.active {
  color: var(--color-accent);
}

.nav-link::after {
  content: '';
  position: absolute;
  bottom: -4px;
  left: 0;
  width: 0;
  height: 2px;
  background: var(--color-accent);
  transition: width var(--transition-normal);
}

.nav-link:hover::after {
  width: 100%;
}

.nav-toggle {
  display: none;
}

ヒーローセクションのスタイル

ファーストビューで強いインパクトを与えるヒーローセクションを作ります。

> ヒーローセクションのCSSを書いて。
  - 画面全体の高さ(100vh)
  - コンテンツを中央揃え(Flexbox)
  - タイトルは大きく太字
  - サブタイトルはグレー
  - CTAボタンにホバーアニメーション
  - 背景にグラデーション or パーティクル効果
/* ========================================
   Hero Section
   ======================================== */
.hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  position: relative;
  background: linear-gradient(135deg, var(--color-bg) 0%, #1a1a3e 100%);
  padding-top: 80px;
}

.hero::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: radial-gradient(circle at 30% 50%, rgba(59, 130, 246, 0.1) 0%, transparent 50%),
              radial-gradient(circle at 70% 50%, rgba(139, 92, 246, 0.08) 0%, transparent 50%);
  pointer-events: none;
}

.hero-title {
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  font-weight: 700;
  margin-bottom: var(--space-sm);
  letter-spacing: 0.02em;
}

.hero-subtitle {
  font-size: var(--text-xl);
  color: var(--color-text-muted);
  margin-bottom: var(--space-lg);
}

.btn {
  display: inline-block;
  padding: 0.875rem 2.5rem;
  border-radius: var(--radius-md);
  font-weight: 700;
  font-size: var(--text-base);
  transition: all var(--transition-normal);
  cursor: pointer;
  border: none;
}

.btn-primary {
  background: var(--color-accent);
  color: white;
  box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}

.btn-primary:hover {
  background: var(--color-accent-hover);
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
}

Flexbox と Grid の使い分け

モダン CSS では、Flexbox と Grid を場面に応じて使い分けます。Claude Code にその判断を聞いてみましょう。

> FlexboxとCSS Gridをいつ使い分けるか教えて。
  ポートフォリオサイトの具体例で説明して

Claude Code は次のような指針を示してくれます。

Flexbox を使う場面(1次元レイアウト): - ヘッダー内のロゴとナビゲーションの配置 - ボタンやアイコンの横並び - カード内のコンテンツ配置 - フッターのリンク群

CSS Grid を使う場面(2次元レイアウト): - 作品ギャラリー(3列 × 2行など) - About セクションの画像 + テキストレイアウト - ページ全体のレイアウト構造 - レスポンシブで列数を変えたいとき

About セクションで Grid を使った例を見てみましょう。

/* ========================================
   About Section
   ======================================== */
.about {
  background: var(--color-bg-secondary);
}

.about-content {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: var(--space-lg);
  align-items: start;
}

.about-image img {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  aspect-ratio: 1;
  object-fit: cover;
}

.about-text p {
  color: var(--color-text-muted);
  margin-bottom: var(--space-sm);
  line-height: 2;
}

.skill-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-sm);
}

.skill-item {
  background: var(--color-bg);
  color: var(--color-accent);
  padding: 0.375rem 1rem;
  border-radius: 9999px;
  font-size: var(--text-sm);
  border: 1px solid var(--color-border);
}

このように、Claude Code とやり取りしながら CSS を組み立てていくと、一貫性のある美しいデザインを効率的に作れます。次の講義では、このデザインをスマホ対応(レスポンシブ)にしていきます。

演習問題

  1. ベーススタイルの適用: 上記の CSS を css/style.css に記述して、ブラウザで表示を確認してください。ヘッダーが固定され、ヒーローセクションが画面全体に表示されることを確認しましょう。

  2. カラースキームの変更: CSS変数の値を変更して、ライトモード版を作ってみてください。Claude Code に「ライトモードに変換して」と依頼するのも有効です。

  3. フッタースタイルの作成: Claude Code に「フッターのCSSをダークモードで、SNSリンクを横並び、ホバーでアクセントカラーに変化」と指示してCSSを生成してもらいましょう。

  4. ボタンバリエーション: .btn-secondary(アウトラインスタイル)と .btn-ghost(透明背景)のスタイルを Claude Code に生成してもらい、HTML に追加してください。

参考資料

Lecture 4レスポンシブ対応 — スマホでも美しく表示する

12:00

レスポンシブ対応 — スマホでも美しく表示する

現在のWeb閲覧の約60%以上がスマートフォンからと言われています。デスクトップでだけ美しいサイトでは不十分です。この講義では、Claude Code を使ってポートフォリオサイトをレスポンシブ化し、どんなデバイスでも最適に表示されるようにします。メディアクエリ、モバイルファースト設計、そしてハンバーガーメニューの実装を学びましょう。

モバイルファースト設計とは

レスポンシブ対応には「モバイルファースト」と「デスクトップファースト」の2つのアプローチがあります。現代のベストプラクティスはモバイルファーストです。

モバイルファーストでは、まずスマートフォン向けのスタイルをベースとして書き、画面が大きくなるにつれてスタイルを追加していきます。メディアクエリでは min-width を使います。

/* モバイル(ベース): 何も指定しなければスマホ用 */
.container { padding: 0 1rem; }

/* タブレット以上 */
@media (min-width: 768px) {
  .container { padding: 0 2rem; }
}

/* デスクトップ以上 */
@media (min-width: 1024px) {
  .container { max-width: 1200px; margin: 0 auto; }
}

前回の講義ではデスクトップのスタイルを先に書いたので、Claude Code にモバイル対応を追加してもらいます。

Claude Code でレスポンシブ CSS を生成する

ブレークポイントの定義と基本的なレスポンシブルールを追加しましょう。

> ポートフォリオサイトにレスポンシブ対応のCSSを追加して。
  ブレークポイント:
  - 768px以下: タブレット・スマホ
  - 480px以下: 小さいスマホ
  対応が必要なセクション:
  - ヘッダー: ナビを非表示にしてハンバーガーメニューに
  - ヒーロー: フォントサイズ縮小
  - About: 2カラム → 1カラム(縦積み)
  - Works: 3列グリッド → 2列 → 1列
  - Contact: フォーム幅100%

Claude Code が生成するレスポンシブ CSS です。

/* ========================================
   Responsive Design
   ======================================== */

/* タブレット以下 (768px) */
@media (max-width: 768px) {
  :root {
    --space-lg: 3rem;
    --space-xl: 4rem;
  }

  /* ナビゲーション: ハンバーガーメニュー */
  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 40px;
    height: 40px;
    background: none;
    border: none;
    cursor: pointer;
    z-index: 1001;
  }

  .hamburger,
  .hamburger::before,
  .hamburger::after {
    display: block;
    width: 24px;
    height: 2px;
    background: var(--color-text);
    transition: all var(--transition-normal);
  }

  .hamburger {
    position: relative;
  }

  .hamburger::before,
  .hamburger::after {
    content: '';
    position: absolute;
    left: 0;
  }

  .hamburger::before { top: -7px; }
  .hamburger::after { top: 7px; }

  /* メニュー開閉時のアニメーション */
  .nav-toggle.active .hamburger {
    background: transparent;
  }

  .nav-toggle.active .hamburger::before {
    top: 0;
    transform: rotate(45deg);
  }

  .nav-toggle.active .hamburger::after {
    top: 0;
    transform: rotate(-45deg);
  }

  /* ナビリスト: フルスクリーンオーバーレイ */
  .nav-list {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100vh;
    background: rgba(15, 23, 42, 0.98);
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: var(--space-md);
    opacity: 0;
    visibility: hidden;
    transition: all var(--transition-normal);
  }

  .nav-list.open {
    opacity: 1;
    visibility: visible;
  }

  .nav-list .nav-link {
    font-size: var(--text-xl);
  }

  /* About: 1カラム */
  .about-content {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .about-image {
    display: flex;
    justify-content: center;
  }

  .about-image img {
    width: 200px;
    height: 200px;
  }

  /* Works: 2列 */
  .works-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  /* Hero */
  .hero-title {
    font-size: 2.5rem;
  }

  .hero-subtitle {
    font-size: var(--text-lg);
  }
}

/* スマホ (480px以下) */
@media (max-width: 480px) {
  :root {
    --space-lg: 2rem;
    --space-xl: 3rem;
  }

  /* Works: 1列 */
  .works-grid {
    grid-template-columns: 1fr;
  }

  .hero-title {
    font-size: 2rem;
  }

  .section-title {
    font-size: var(--text-xl);
  }

  .btn {
    width: 100%;
    text-align: center;
  }
}

ハンバーガーメニューの JavaScript

ハンバーガーメニューの開閉を制御する JavaScript が必要です。Claude Code に書いてもらいましょう。

> ハンバーガーメニューのJavaScriptを書いて。
  要件:
  - .nav-toggleクリックでメニュー開閉
  - メニューリンクをクリックしたら自動で閉じる
  - メニュー外をクリックしても閉じる
  - body のスクロールを無効化(メニュー表示中)
  js/main.js に書いて

Claude Code が生成する JavaScript です。

// ========================================
// Hamburger Menu
// ========================================
const navToggle = document.querySelector('.nav-toggle');
const navList = document.querySelector('.nav-list');
const navLinks = document.querySelectorAll('.nav-link');

function toggleMenu() {
  navToggle.classList.toggle('active');
  navList.classList.toggle('open');
  document.body.style.overflow = navList.classList.contains('open') ? 'hidden' : '';

  // aria-expanded の更新
  const isOpen = navList.classList.contains('open');
  navToggle.setAttribute('aria-expanded', isOpen);
}

navToggle.addEventListener('click', toggleMenu);

// リンクをクリックしたらメニューを閉じる
navLinks.forEach(link => {
  link.addEventListener('click', () => {
    if (navList.classList.contains('open')) {
      toggleMenu();
    }
  });
});

// メニュー外クリックで閉じる
document.addEventListener('click', (e) => {
  if (navList.classList.contains('open') &&
      !navList.contains(e.target) &&
      !navToggle.contains(e.target)) {
    toggleMenu();
  }
});

レスポンシブ画像の最適化

レスポンシブ対応では画像のサイズも重要です。大きな画像をスマホで読み込むと通信量が無駄になります。

> レスポンシブ画像の実装方法を教えて。
  picture要素とsrcset属性の使い方を
  ポートフォリオのプロフィール画像の例で

Claude Code が教えてくれるレスポンシブ画像の実装例です。

<!-- srcset による画像の出し分け -->
<img
  src="images/profile-400.jpg"
  srcset="
    images/profile-400.jpg 400w,
    images/profile-800.jpg 800w"
  sizes="(max-width: 768px) 200px, 300px"
  alt="プロフィール写真"
  width="300"
  height="300"
  loading="lazy"
>

<!-- picture要素でフォーマットの出し分け -->
<picture>
  <source srcset="images/profile.webp" type="image/webp">
  <source srcset="images/profile.jpg" type="image/jpeg">
  <img src="images/profile.jpg" alt="プロフィール写真" width="300" height="300">
</picture>

loading="lazy" 属性を追加すると、画像がビューポートに近づいたときに初めて読み込まれるため、初期表示速度が向上します。ポートフォリオのような画像が多いサイトでは特に有効な手法です。

デバイスでの確認方法

レスポンシブ対応したら、実際にさまざまなデバイスサイズで確認する必要があります。Chrome DevTools の Device Mode が便利です。

  1. Chrome でサイトを開く
  2. F12 で DevTools を開く
  3. 左上のデバイスアイコン(またはCtrl + Shift + M)をクリック
  4. 上部のドロップダウンからデバイスを選択(iPhone SE、iPad、Galaxy S など)

Claude Code に確認ポイントを聞くこともできます。

> レスポンシブ対応のチェックリストを作って。
  テストすべきブレークポイントとチェック項目を

演習問題

  1. レスポンシブ CSS の適用: 上記のメディアクエリを style.css に追加し、ブラウザの幅を変えて各ブレークポイントで表示が変わることを確認してください。

  2. ハンバーガーメニューの実装: JavaScript コードを main.js に追加し、モバイル表示でハンバーガーメニューが正しく動作することを確認してください。開閉アニメーション、リンククリック時の自動閉鎖、スクロール無効化の3点をテストしましょう。

  3. 追加のブレークポイント: 1440px以上の大画面向けのスタイルを Claude Code に生成してもらい、追加してください(例: コンテナ幅を広げる、フォントサイズを大きくするなど)。

  4. Chrome DevTools で検証: DevTools の Device Mode で iPhone SE、iPad、Galaxy S20 の3デバイスで表示を確認し、崩れている箇所があれば Claude Code に修正を依頼してください。

参考資料

Lecture 5プロフィールセクション — 自己紹介ページを作る

12:00

プロフィールセクション — 自己紹介ページを作る

ポートフォリオサイトで最も重要なセクションの一つが「プロフィール(About)セクション」です。訪問者はあなたの作品だけでなく、「この人はどんな人なのか」を知りたがっています。この講義では、Claude Code を使ってプロフィール写真、自己紹介文、スキルセット、ソーシャルリンクを含む魅力的なプロフィールセクションを作り上げます。

プロフィールセクションに含める情報

効果的なプロフィールセクションには、以下の要素を含めましょう。

プロフィール写真: 顔がはっきりわかる写真。丸くトリミングするとモダンな印象になります。写真がない場合はアバターイラストでも構いません。

自己紹介文: 2〜3段落で簡潔にまとめます。現在の職種・役割、得意分野、キャリアの方向性を含めます。堅すぎず、かつプロフェッショナルなトーンが理想です。

スキルバー / スキルタグ: 持っているスキルを視覚的に表示します。プログレスバーで習熟度を示す方法と、タグでスキル名を並べる方法があります。

ソーシャルリンク: GitHub、X(Twitter)、LinkedIn、ブログなどへのリンク。アイコンで表示するのが一般的です。

プロフィール HTML の強化

前回までに作った About セクションの HTML を、より充実した内容に拡張しましょう。

> About セクションのHTMLを強化して。
  以下の要素を追加:
  - プロフィール写真(丸くトリミング)
  - 3段落の自己紹介文
  - スキルバー(HTML/CSS 90%, JavaScript 80%, React 70%, Python 60%)
  - SNSリンク(GitHub, X, LinkedIn)をアイコン付きで
  - 経歴のタイムライン(3項目)
  SVGアイコンはインラインで

Claude Code が生成する強化版 HTML です。

<section id="about" class="about">
  <div class="container">
    <h2 class="section-title">About</h2>
    <div class="about-content">
      <div class="about-image">
        <img src="images/profile.jpg" alt="山田太郎のプロフィール写真"
             width="300" height="300" loading="lazy">
        <div class="social-links">
          <a href="https://github.com/" target="_blank" rel="noopener noreferrer"
             aria-label="GitHub">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205
              11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555
              -3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02
              -.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305
              3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925
              0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315
              3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23
              3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0
              4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015
              2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63
              -5.37-12-12-12z"/>
            </svg>
          </a>
          <a href="https://twitter.com/" target="_blank" rel="noopener noreferrer"
             aria-label="X (Twitter)">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
              <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214
              -6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm
              -1.161 17.52h1.833L7.084 4.126H5.117z"/>
            </svg>
          </a>
          <a href="https://linkedin.com/" target="_blank" rel="noopener noreferrer"
             aria-label="LinkedIn">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
              <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852
              -3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h
              .046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v
              6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063
              2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0
              .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24
              23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
            </svg>
          </a>
        </div>
      </div>
      <div class="about-text">
        <h3 class="about-heading">こんにちは、山田太郎です</h3>
        <p>東京を拠点にフロントエンド開発を専門とするWebエンジニアです。
        3年間のWeb開発経験を通じて、ユーザーにとって使いやすく、
        美しいインターフェースを作ることに情熱を持っています。</p>
        <p>HTML/CSS、JavaScript を基盤に、React や Next.js を使った
        モダンなWebアプリケーション開発を得意としています。
        バックエンドにも理解があり、フルスタックな開発が可能です。</p>
        <p>新しい技術を学ぶことが好きで、個人プロジェクトやOSS活動を
        通じて常にスキルアップを心がけています。</p>

        <div class="skills">
          <h3>Skills</h3>
          <div class="skill-bars">
            <div class="skill-bar-item">
              <div class="skill-bar-header">
                <span class="skill-name">HTML / CSS</span>
                <span class="skill-percent">90%</span>
              </div>
              <div class="skill-bar-bg">
                <div class="skill-bar-fill" style="width: 90%"></div>
              </div>
            </div>
            <div class="skill-bar-item">
              <div class="skill-bar-header">
                <span class="skill-name">JavaScript</span>
                <span class="skill-percent">80%</span>
              </div>
              <div class="skill-bar-bg">
                <div class="skill-bar-fill" style="width: 80%"></div>
              </div>
            </div>
            <div class="skill-bar-item">
              <div class="skill-bar-header">
                <span class="skill-name">React</span>
                <span class="skill-percent">70%</span>
              </div>
              <div class="skill-bar-bg">
                <div class="skill-bar-fill" style="width: 70%"></div>
              </div>
            </div>
            <div class="skill-bar-item">
              <div class="skill-bar-header">
                <span class="skill-name">Python</span>
                <span class="skill-percent">60%</span>
              </div>
              <div class="skill-bar-bg">
                <div class="skill-bar-fill" style="width: 60%"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>

スキルバーとソーシャルリンクの CSS

次に、スキルバーとソーシャルリンクのスタイルを Claude Code に書いてもらいます。

> スキルバーのCSSを書いて。
  - バーの背景はダークグレー、塗りはアクセントカラーのグラデーション
  - アニメーション付き(左からスライドイン)
  - パーセント表示は右端に
  SNSリンクのCSSも:
  - 横並び、丸い背景、ホバーでアクセントカラーに
/* ========================================
   Skill Bars
   ======================================== */
.skill-bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.skill-bar-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 4px;
}

.skill-name {
  font-size: var(--text-sm);
  font-weight: 500;
}

.skill-percent {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.skill-bar-bg {
  width: 100%;
  height: 8px;
  background: var(--color-bg);
  border-radius: 4px;
  overflow: hidden;
}

.skill-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--color-accent), #8b5cf6);
  border-radius: 4px;
  width: 0;
  transition: width 1.2s ease;
}

.skill-bar-fill.animate {
  /* width は inline style で設定済み */
}

/* ========================================
   Social Links
   ======================================== */
.social-links {
  display: flex;
  gap: var(--space-sm);
  justify-content: center;
  margin-top: var(--space-md);
}

.social-links a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  transition: all var(--transition-normal);
}

.social-links a:hover {
  background: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
  transform: translateY(-3px);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}

/* ========================================
   About Heading
   ======================================== */
.about-heading {
  font-size: var(--text-xl);
  margin-bottom: var(--space-sm);
  color: var(--color-text);
}

スキルバーのアニメーション JavaScript

スキルバーが画面に表示されたタイミングでアニメーションを開始する JavaScript を追加しましょう。

> スキルバーが画面に表示されたら
  width: 0 から指定した幅までアニメーションするJavaScriptを書いて。
  Intersection Observer APIを使って
// ========================================
// Skill Bar Animation
// ========================================
const skillBars = document.querySelectorAll('.skill-bar-fill');

const skillObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // inline style の width をアニメーションのターゲットにする
      const targetWidth = entry.target.style.width;
      entry.target.style.width = '0';

      // 少し遅延してからアニメーション開始
      setTimeout(() => {
        entry.target.style.width = targetWidth;
      }, 200);

      skillObserver.unobserve(entry.target);
    }
  });
}, { threshold: 0.5 });

skillBars.forEach(bar => skillObserver.observe(bar));

Intersection Observer API は、要素がビューポート内に入ったかどうかを検知できる仕組みです。スクロールイベントをリスニングするよりもパフォーマンスに優れており、モダンなブラウザでは標準的に使われます。threshold: 0.5 は要素の50%が表示されたらコールバックを実行する設定です。

プロフィール写真の効果的な表示

プロフィール写真をさらに印象的にするCSSテクニックを追加しましょう。

> プロフィール写真にホバーエフェクトを追加して。
  - 通常: 丸くトリミング + ボーダー
  - ホバー: 少し拡大 + シャドウ強化 + ボーダー色変化
.about-image img {
  border-radius: 50%;
  border: 4px solid var(--color-border);
  box-shadow: var(--shadow-lg);
  aspect-ratio: 1;
  object-fit: cover;
  transition: all var(--transition-normal);
}

.about-image img:hover {
  border-color: var(--color-accent);
  transform: scale(1.05);
  box-shadow: 0 0 30px rgba(59, 130, 246, 0.2);
}

プロフィール写真はポートフォリオサイトで非常に重要な要素です。顔が見えることで訪問者に親近感を与え、信頼感を醸成します。写真がまだない場合は、プレースホルダーとしてアバター画像を使い、後から差し替えましょう。

演習問題

  1. プロフィールセクションの完成: 上記の HTML と CSS をプロジェクトに追加し、ブラウザで確認してください。プロフィール写真は仮画像(placeholder.com など)を使って構いません。

  2. 自分の情報に書き換え: 名前、自己紹介文、スキルセットを自分の実際の情報に変更してください。Claude Code に「フリーランスのデザイナー向けの自己紹介文を書いて」などと頼んでもよいでしょう。

  3. スキル表示の切り替え: Claude Code に「スキルバーの代わりにタグ形式のスキル表示も作って。ボタンでバーとタグを切り替えられるようにして」と依頼し、実装してみてください。

  4. 経歴タイムラインの追加: Claude Code に「縦型のタイムライン(経歴)を作って。左に年号、右に内容。線でつないで」と依頼し、About セクションの下に追加してください。

参考資料

Lecture 6作品ギャラリー — プロジェクト一覧を作る

12:00

作品ギャラリー — プロジェクト一覧を作る

ポートフォリオサイトの中心となるのが「作品ギャラリー(Works)セクション」です。あなたの実力を示す最大のアピールポイントであり、採用担当者やクライアントが最も注目する部分です。この講義では、Claude Code を使って CSS Grid ベースの美しいプロジェクトカード、ホバーエフェクト、フィルタリング機能を備えた作品ギャラリーを作ります。

作品カードに含める情報

効果的な作品カードには、以下の情報を含めましょう。

  • サムネイル画像: プロジェクトのスクリーンショットやキービジュアル
  • プロジェクト名: 何を作ったのかが一目でわかるタイトル
  • 概要説明: 1〜2文の簡潔な説明
  • 使用技術タグ: どの技術スタックで作ったか
  • リンク: ライブデモ URL と GitHub リポジトリへのリンク

Claude Code で作品カードの HTML を生成する

6枚のプロジェクトカードを一括で生成してもらいましょう。

> Works セクションの作品カードを6枚分のHTMLを書いて。
  各カードに含める要素:
  - サムネイル画像(仮画像でOK)
  - プロジェクト名
  - 概要(1-2文)
  - 技術タグ(2-4個)
  - Demo と GitHub の2つのリンクボタン
  プロジェクト例: ECサイト, タスク管理アプリ, 天気予報アプリ,
  ブログCMS, チャットアプリ, ポートフォリオサイト

Claude Code が生成する HTML です。

<section id="works" class="works">
  <div class="container">
    <h2 class="section-title">Works</h2>

    <!-- フィルターボタン -->
    <div class="works-filter">
      <button class="filter-btn active" data-filter="all">All</button>
      <button class="filter-btn" data-filter="frontend">Frontend</button>
      <button class="filter-btn" data-filter="fullstack">Fullstack</button>
      <button class="filter-btn" data-filter="design">Design</button>
    </div>

    <div class="works-grid">
      <!-- Card 1 -->
      <article class="work-card" data-category="fullstack">
        <div class="work-image-wrapper">
          <img src="images/work-01.jpg" alt="ECサイトのスクリーンショット"
               class="work-image" loading="lazy">
          <div class="work-overlay">
            <a href="#" class="btn btn-sm" target="_blank">Demo</a>
            <a href="#" class="btn btn-sm btn-outline" target="_blank">GitHub</a>
          </div>
        </div>
        <div class="work-info">
          <h3 class="work-title">ECサイト</h3>
          <p class="work-desc">React と Node.js で構築したフルスタック EC サイト。
          商品検索、カート機能、決済連携を実装。</p>
          <div class="work-tags">
            <span class="tag">React</span>
            <span class="tag">Node.js</span>
            <span class="tag">MongoDB</span>
            <span class="tag">Stripe</span>
          </div>
        </div>
      </article>

      <!-- Card 2 -->
      <article class="work-card" data-category="frontend">
        <div class="work-image-wrapper">
          <img src="images/work-02.jpg" alt="タスク管理アプリのスクリーンショット"
               class="work-image" loading="lazy">
          <div class="work-overlay">
            <a href="#" class="btn btn-sm" target="_blank">Demo</a>
            <a href="#" class="btn btn-sm btn-outline" target="_blank">GitHub</a>
          </div>
        </div>
        <div class="work-info">
          <h3 class="work-title">タスク管理アプリ</h3>
          <p class="work-desc">ドラッグ&ドロップでタスクを管理できる
          カンバンボードアプリ。</p>
          <div class="work-tags">
            <span class="tag">JavaScript</span>
            <span class="tag">CSS Grid</span>
            <span class="tag">LocalStorage</span>
          </div>
        </div>
      </article>

      <!-- Card 3 -->
      <article class="work-card" data-category="frontend">
        <div class="work-image-wrapper">
          <img src="images/work-03.jpg" alt="天気予報アプリのスクリーンショット"
               class="work-image" loading="lazy">
          <div class="work-overlay">
            <a href="#" class="btn btn-sm" target="_blank">Demo</a>
            <a href="#" class="btn btn-sm btn-outline" target="_blank">GitHub</a>
          </div>
        </div>
        <div class="work-info">
          <h3 class="work-title">天気予報アプリ</h3>
          <p class="work-desc">OpenWeatherMap API を使った天気予報アプリ。
          位置情報取得と5日間予報に対応。</p>
          <div class="work-tags">
            <span class="tag">JavaScript</span>
            <span class="tag">API</span>
            <span class="tag">CSS</span>
          </div>
        </div>
      </article>

      <!-- Card 4 -->
      <article class="work-card" data-category="fullstack">
        <div class="work-image-wrapper">
          <img src="images/work-04.jpg" alt="ブログCMSのスクリーンショット"
               class="work-image" loading="lazy">
          <div class="work-overlay">
            <a href="#" class="btn btn-sm" target="_blank">Demo</a>
            <a href="#" class="btn btn-sm btn-outline" target="_blank">GitHub</a>
          </div>
        </div>
        <div class="work-info">
          <h3 class="work-title">ブログCMS</h3>
          <p class="work-desc">Markdown 対応のブログ管理システム。
          記事の作成・編集・公開を管理画面から操作。</p>
          <div class="work-tags">
            <span class="tag">Next.js</span>
            <span class="tag">Supabase</span>
            <span class="tag">MDX</span>
          </div>
        </div>
      </article>

      <!-- Card 5 -->
      <article class="work-card" data-category="fullstack">
        <div class="work-image-wrapper">
          <img src="images/work-05.jpg" alt="チャットアプリのスクリーンショット"
               class="work-image" loading="lazy">
          <div class="work-overlay">
            <a href="#" class="btn btn-sm" target="_blank">Demo</a>
            <a href="#" class="btn btn-sm btn-outline" target="_blank">GitHub</a>
          </div>
        </div>
        <div class="work-info">
          <h3 class="work-title">チャットアプリ</h3>
          <p class="work-desc">WebSocket を使ったリアルタイムチャット。
          グループチャットと画像送信に対応。</p>
          <div class="work-tags">
            <span class="tag">React</span>
            <span class="tag">Socket.io</span>
            <span class="tag">Express</span>
          </div>
        </div>
      </article>

      <!-- Card 6 -->
      <article class="work-card" data-category="design">
        <div class="work-image-wrapper">
          <img src="images/work-06.jpg" alt="ポートフォリオサイトのスクリーンショット"
               class="work-image" loading="lazy">
          <div class="work-overlay">
            <a href="#" class="btn btn-sm" target="_blank">Demo</a>
            <a href="#" class="btn btn-sm btn-outline" target="_blank">GitHub</a>
          </div>
        </div>
        <div class="work-info">
          <h3 class="work-title">ポートフォリオサイト</h3>
          <p class="work-desc">このサイト自体もポートフォリオ作品。
          レスポンシブ対応のモダンなデザイン。</p>
          <div class="work-tags">
            <span class="tag">HTML</span>
            <span class="tag">CSS</span>
            <span class="tag">JavaScript</span>
          </div>
        </div>
      </article>
    </div>
  </div>
</section>

CSS Grid によるギャラリーレイアウト

作品カードを美しく配置するためのCSSを生成しましょう。

> Works ギャラリーのCSSを書いて。
  - CSS Grid: 3列、カード間のgap: 2rem
  - カードにホバーエフェクト(浮き上がる + シャドウ)
  - 画像にオーバーレイ(半透明の黒 + ボタン表示)
  - フィルターボタンのスタイル
  - 角丸、ダークモードに合うデザイン
/* ========================================
   Works Section
   ======================================== */
.works {
  background: var(--color-bg);
}

.works-filter {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-lg);
  flex-wrap: wrap;
}

.filter-btn {
  padding: 0.5rem 1.5rem;
  border: 1px solid var(--color-border);
  border-radius: 9999px;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: all var(--transition-fast);
  font-size: var(--text-sm);
}

.filter-btn:hover,
.filter-btn.active {
  background: var(--color-accent);
  color: white;
  border-color: var(--color-accent);
}

.works-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
}

.work-card {
  background: var(--color-bg-card);
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--color-border);
  transition: all var(--transition-normal);
}

.work-card:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-lg);
  border-color: var(--color-accent);
}

/* 画像オーバーレイ */
.work-image-wrapper {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
}

.work-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-normal);
}

.work-card:hover .work-image {
  transform: scale(1.05);
}

.work-overlay {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  opacity: 0;
  transition: opacity var(--transition-normal);
}

.work-card:hover .work-overlay {
  opacity: 1;
}

.btn-sm {
  padding: 0.5rem 1.25rem;
  font-size: var(--text-sm);
}

.btn-outline {
  background: transparent;
  border: 1px solid white;
  color: white;
}

.btn-outline:hover {
  background: white;
  color: var(--color-bg);
}

/* カード情報 */
.work-info {
  padding: var(--space-sm) var(--space-md) var(--space-md);
}

.work-title {
  font-size: var(--text-lg);
  font-weight: 700;
  margin-bottom: var(--space-xs);
}

.work-desc {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--space-sm);
  line-height: 1.7;
}

.work-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.tag {
  font-size: 0.75rem;
  padding: 0.25rem 0.75rem;
  background: rgba(59, 130, 246, 0.1);
  color: var(--color-accent);
  border-radius: 9999px;
  border: 1px solid rgba(59, 130, 246, 0.2);
}

フィルタリング機能の JavaScript

フィルターボタンでカテゴリ別に作品を絞り込む機能を追加しましょう。

> 作品カードのフィルタリング機能をJavaScriptで実装して。
  - フィルターボタンクリックでdata-category属性でフィルタリング
  - "All"は全て表示
  - フェードイン/フェードアウトのアニメーション付き
// ========================================
// Works Filter
// ========================================
const filterButtons = document.querySelectorAll('.filter-btn');
const workCards = document.querySelectorAll('.work-card');

filterButtons.forEach(btn => {
  btn.addEventListener('click', () => {
    // アクティブ状態の更新
    filterButtons.forEach(b => b.classList.remove('active'));
    btn.classList.add('active');

    const filter = btn.dataset.filter;

    workCards.forEach(card => {
      const category = card.dataset.category;

      if (filter === 'all' || category === filter) {
        card.style.opacity = '0';
        card.style.display = '';
        setTimeout(() => {
          card.style.opacity = '1';
        }, 50);
      } else {
        card.style.opacity = '0';
        setTimeout(() => {
          card.style.display = 'none';
        }, 300);
      }
    });
  });
});

フィルタリング機能を追加する際、CSS にもトランジションを設定しておきましょう。

.work-card {
  transition: all var(--transition-normal), opacity var(--transition-normal);
}

この機能により、訪問者は興味のあるカテゴリだけを表示できるようになります。プロジェクト数が多くなった場合に特に有効です。

演習問題

  1. ギャラリーの実装: 上記の HTML、CSS、JavaScript をプロジェクトに追加し、6枚のカードが CSS Grid で正しく配置されることを確認してください。サムネイル画像は placeholder.com から仮画像を使いましょう(例: https://placehold.co/600x375)。

  2. ホバーエフェクトの確認: カードにマウスを乗せたとき、浮き上がりエフェクトと画像オーバーレイが正しく表示されることを確認してください。

  3. フィルタリングの動作テスト: フィルターボタンをクリックして、カテゴリ別にカードが正しく表示・非表示になることを確認してください。

  4. カードの追加: Claude Code に「7枚目と8枚目の作品カードを追加して。AIチャットボットとレシピアプリで」と依頼して、ギャラリーを拡張してみましょう。

参考資料

Lecture 7アニメーション — CSSで動きをつける

12:00

アニメーション — CSSで動きをつける

静的なサイトに動きを加えると、ユーザー体験が大きく向上します。ただし、過剰なアニメーションは逆効果になるため、適切な場面で控えめに使うことがポイントです。この講義では、Claude Code を活用して CSS アニメーション、トランジション、スクロールトリガーのアニメーションをポートフォリオサイトに追加していきます。

CSS Transition と Animation の違い

まず、CSS で動きを作る2つの方法を理解しましょう。

Transition(トランジション): ある状態から別の状態への変化をスムーズにする機能です。:hover や JavaScript によるクラス付与などをトリガーにします。「AからBへの変化」を表現するのに適しています。

Animation(アニメーション): @keyframes を使って複数の中間状態を定義できます。ページ読み込み時に自動的に実行したり、複雑な動きを表現できます。ループ再生も可能です。

> CSSのTransitionとAnimationの違いを、
  ポートフォリオサイトの具体例で説明して。
  どの場面でどちらを使うべきか

Claude Code は以下のような使い分けを教えてくれます。

場面 適切な手法 理由
ボタンのホバー Transition 2状態間の単純な変化
ナビリンクの下線 Transition ホバーで幅が変わるだけ
ヒーローテキスト登場 Animation ページ読み込み時に自動再生
スクロール時のフェードイン Animation + JS 複数要素に段階的に適用
ローディングスピナー Animation 無限ループ

ヒーローセクションの登場アニメーション

ページを開いたときに、ヒーローセクションの要素がふわっと表示されるアニメーションを追加しましょう。

> ヒーローセクションの登場アニメーションを作って
  - タイトル: 下からフェードイン0.6s
  - サブタイトル: 下からフェードイン0.8s, 0.2s遅延
  - ボタン: 下からフェードイン1.0s, 0.4s遅延
  @keyframesで定義して

Claude Code が生成するアニメーション CSS です。

/* ========================================
   Keyframes
   ======================================== */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes slideInLeft {
  from {
    opacity: 0;
    transform: translateX(-30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* ========================================
   Hero Animations
   ======================================== */
.hero-title {
  animation: fadeInUp 0.6s ease forwards;
}

.hero-subtitle {
  opacity: 0;
  animation: fadeInUp 0.6s ease 0.2s forwards;
}

.hero .btn {
  opacity: 0;
  animation: fadeInUp 0.6s ease 0.4s forwards;
}

forwards を指定することで、アニメーション終了後に最後のフレームの状態が保持されます。これを忘れると、アニメーション完了後に要素が初期状態に戻ってしまいます。animation-delay で各要素の登場タイミングをずらすことで、順番に現れる自然な演出になります。

スクロールトリガーのアニメーション

ページをスクロールしたときに、各セクションの要素がふわっと現れるアニメーションを実装します。Intersection Observer API を使います。

> スクロールでセクションがビューポートに入ったら
  フェードインするアニメーションを実装して。
  - CSSクラス: .scroll-animate(初期状態: 非表示)
  - .scroll-animate.visible(表示状態)
  - 4つのバリエーション: fade-up, fade-left, fade-right, fade-in
  - JavaScript: Intersection Observerで監視
  - 要素ごとに遅延を設定できるようにdata-delayを使う

まず CSS でアニメーションの初期状態と表示状態を定義します。

/* ========================================
   Scroll Animations
   ======================================== */
.scroll-animate {
  opacity: 0;
  transition: all 0.6s ease;
}

.scroll-animate.fade-up {
  transform: translateY(40px);
}

.scroll-animate.fade-left {
  transform: translateX(-40px);
}

.scroll-animate.fade-right {
  transform: translateX(40px);
}

.scroll-animate.fade-in {
  transform: none;
}

/* 表示状態 */
.scroll-animate.visible {
  opacity: 1;
  transform: translateY(0) translateX(0);
}

次に JavaScript で Intersection Observer を設定します。

// ========================================
// Scroll Animation
// ========================================
const scrollElements = document.querySelectorAll('.scroll-animate');

const scrollObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const delay = entry.target.dataset.delay || 0;
      setTimeout(() => {
        entry.target.classList.add('visible');
      }, delay);
      scrollObserver.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.15,
  rootMargin: '0px 0px -50px 0px'
});

scrollElements.forEach(el => scrollObserver.observe(el));

HTML での使い方はこのようになります。

<h2 class="section-title scroll-animate fade-up">About</h2>
<div class="about-content scroll-animate fade-up" data-delay="200">
  <!-- コンテンツ -->
</div>

<h2 class="section-title scroll-animate fade-up">Works</h2>
<article class="work-card scroll-animate fade-up" data-delay="0">...</article>
<article class="work-card scroll-animate fade-up" data-delay="100">...</article>
<article class="work-card scroll-animate fade-up" data-delay="200">...</article>

data-delay 属性で各要素の遅延時間(ミリ秒)を指定できます。作品カードに段階的な遅延を設定すると、左から右へ順番に現れるような演出になります。rootMargin: '0px 0px -50px 0px' は、ビューポートの下端から50px手前で発火する設定で、要素が少し見え始めてからアニメーションを開始するようにしています。

ホバーエフェクトの強化

既存のホバーエフェクトをさらに印象的にしましょう。

> ナビリンクとボタンのホバーエフェクトを強化して。
  ナビリンク: 下線が左から右にスライドイン
  ボタン: 背景色がグラデーションで変化 + rippleエフェクト
/* ========================================
   Enhanced Hover Effects
   ======================================== */

/* ナビリンク: 下線スライドイン */
.nav-link {
  position: relative;
  overflow: hidden;
}

.nav-link::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 100%;
  height: 2px;
  background: var(--color-accent);
  transform: translateX(-100%);
  transition: transform var(--transition-normal);
}

.nav-link:hover::after {
  transform: translateX(0);
}

/* ボタン: シマーエフェクト */
.btn-primary {
  position: relative;
  overflow: hidden;
}

.btn-primary::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.15),
    transparent
  );
  transition: left 0.5s ease;
}

.btn-primary:hover::before {
  left: 100%;
}

/* スキルタグ: ホバーで輝く */
.skill-item {
  transition: all var(--transition-fast);
}

.skill-item:hover {
  background: var(--color-accent);
  color: white;
  transform: scale(1.05);
  box-shadow: 0 2px 10px rgba(59, 130, 246, 0.3);
}

アクセシビリティへの配慮

アニメーションは一部のユーザーにとって不快な場合があります。prefers-reduced-motion メディアクエリを使って、アニメーションを無効化するオプションを提供しましょう。

> prefers-reduced-motion に対応するCSSを書いて。
  アニメーションが苦手なユーザー向けに
  すべてのアニメーションとトランジションを無効化
/* ========================================
   Reduced Motion
   ======================================== */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .scroll-animate {
    opacity: 1;
    transform: none;
  }
}

この設定は、OS の設定で「視差効果を減らす」を有効にしているユーザーに自動的に適用されます。アクセシビリティに配慮したサイトは、より多くの人に快適に利用してもらえます。これはプロフェッショナルなポートフォリオサイトとして重要なポイントです。

演習問題

  1. ヒーローアニメーションの実装: @keyframes fadeInUp とヒーローセクションのアニメーション CSS を追加し、ページ読み込み時にテキストが順番にフェードインすることを確認してください。

  2. スクロールアニメーションの実装: .scroll-animate クラスを各セクションの要素に追加し、Intersection Observer の JavaScript を main.js に追加して、スクロール時のフェードインを確認してください。

  3. カスタムアニメーション: Claude Code に「セクションタイトルが表示されたとき、下線が左から右にスライドする @keyframes アニメーション」を作ってもらい、実装してください。

  4. prefers-reduced-motion の確認: Chrome DevTools の Rendering タブで prefers-reduced-motion: reduce をシミュレートし、アニメーションが無効になることを確認してください。

参考資料

Lecture 8お問い合わせフォーム — 連絡先ページを作る

12:00

お問い合わせフォーム — 連絡先ページを作る

ポートフォリオサイトにおいて、お問い合わせフォームは訪問者があなたに連絡を取るための重要な手段です。美しいフォームデザイン、適切なバリデーション、そして実際にメールが届く仕組みを Claude Code を使って実装していきましょう。バックエンドサーバーを用意しなくても、Formspree などの外部サービスを使えば無料でフォーム送信機能を実現できます。

フォームの HTML 構造

まず、アクセシブルで使いやすいフォームの HTML を生成しましょう。

> お問い合わせフォームのHTMLを書いて。
  フィールド:
  - お名前(テキスト、必須)
  - メールアドレス(email、必須)
  - 件名(テキスト)
  - メッセージ(textarea、必須、5行)
  - 送信ボタン
  条件:
  - label  input を正しく紐付け(for/id  - 各フィールドに placeholder
  - required 属性
  - aria 属性でアクセシビリティ対応
  - 送信先は Formspree

Claude Code が生成するフォーム HTML です。

<section id="contact" class="contact">
  <div class="container">
    <h2 class="section-title scroll-animate fade-up">Contact</h2>
    <p class="section-subtitle scroll-animate fade-up" data-delay="100">
      お仕事のご依頼やご質問など、お気軽にお問い合わせください。
    </p>

    <div class="contact-wrapper scroll-animate fade-up" data-delay="200">
      <!-- 左側: 連絡先情報 -->
      <div class="contact-info">
        <div class="contact-info-item">
          <div class="contact-icon">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" stroke-width="2">
              <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2
              2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
              <polyline points="22,6 12,13 2,6"/>
            </svg>
          </div>
          <div>
            <h3>メール</h3>
            <p>hello@example.com</p>
          </div>
        </div>
        <div class="contact-info-item">
          <div class="contact-icon">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" stroke-width="2">
              <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/>
              <circle cx="12" cy="10" r="3"/>
            </svg>
          </div>
          <div>
            <h3>所在地</h3>
            <p>東京都渋谷区</p>
          </div>
        </div>
        <div class="contact-info-item">
          <div class="contact-icon">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" stroke-width="2">
              <circle cx="12" cy="12" r="10"/>
              <polyline points="12 6 12 12 16 14"/>
            </svg>
          </div>
          <div>
            <h3>対応時間</h3>
            <p>平日 9:00 - 18:00</p>
          </div>
        </div>
      </div>

      <!-- 右側: フォーム -->
      <form class="contact-form" id="contactForm"
            action="https://formspree.io/f/your-form-id" method="POST"
            novalidate>
        <div class="form-group">
          <label for="name">お名前 <span class="required">*</span></label>
          <input type="text" id="name" name="name"
                 placeholder="山田 太郎"
                 required
                 aria-required="true"
                 autocomplete="name">
          <span class="error-message" id="nameError"></span>
        </div>

        <div class="form-group">
          <label for="email">メールアドレス <span class="required">*</span></label>
          <input type="email" id="email" name="email"
                 placeholder="taro@example.com"
                 required
                 aria-required="true"
                 autocomplete="email">
          <span class="error-message" id="emailError"></span>
        </div>

        <div class="form-group">
          <label for="subject">件名</label>
          <input type="text" id="subject" name="subject"
                 placeholder="お仕事のご依頼について">
        </div>

        <div class="form-group">
          <label for="message">メッセージ <span class="required">*</span></label>
          <textarea id="message" name="message" rows="5"
                    placeholder="お問い合わせ内容をご記入ください"
                    required
                    aria-required="true"></textarea>
          <span class="error-message" id="messageError"></span>
        </div>

        <button type="submit" class="btn btn-primary btn-submit">
          <span class="btn-text">送信する</span>
          <span class="btn-loading" hidden>送信中...</span>
        </button>
        <p class="form-status" id="formStatus" role="alert"></p>
      </form>
    </div>
  </div>
</section>

フォームの CSS スタイリング

フォームをダークモードに合う美しいデザインにします。

> お問い合わせフォームのCSSを書いて。
  - 2カラム: 左に連絡先情報、右にフォーム
  - ダークモード: input背景は濃いグレー、ボーダーは暗めの線
  - フォーカス時にアクセントカラーのボーダー + グロー
  - エラー表示は赤色
  - 送信ボタンは幅100%
/* ========================================
   Contact Section
   ======================================== */
.contact {
  background: var(--color-bg-secondary);
}

.section-subtitle {
  text-align: center;
  color: var(--color-text-muted);
  margin-top: calc(var(--space-lg) * -1 + var(--space-sm));
  margin-bottom: var(--space-lg);
}

.contact-wrapper {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--space-lg);
  align-items: start;
}

/* 連絡先情報 */
.contact-info {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.contact-info-item {
  display: flex;
  gap: var(--space-sm);
  align-items: start;
}

.contact-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  min-width: 48px;
  border-radius: var(--radius-md);
  background: rgba(59, 130, 246, 0.1);
  color: var(--color-accent);
}

.contact-info-item h3 {
  font-size: var(--text-base);
  margin-bottom: 2px;
}

.contact-info-item p {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* フォーム */
.contact-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.form-group label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text);
}

.required {
  color: #ef4444;
}

.form-group input,
.form-group textarea {
  padding: 0.75rem 1rem;
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-size: var(--text-base);
  transition: all var(--transition-fast);
}

.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}

.form-group input.error,
.form-group textarea.error {
  border-color: #ef4444;
  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}

.error-message {
  font-size: 0.8rem;
  color: #ef4444;
  min-height: 1.2em;
}

.form-group textarea {
  resize: vertical;
  min-height: 120px;
}

.btn-submit {
  width: 100%;
  margin-top: var(--space-xs);
}

.form-status {
  text-align: center;
  font-size: var(--text-sm);
  margin-top: var(--space-xs);
}

.form-status.success {
  color: #22c55e;
}

.form-status.error {
  color: #ef4444;
}

/* レスポンシブ */
@media (max-width: 768px) {
  .contact-wrapper {
    grid-template-columns: 1fr;
  }
}

JavaScript によるフォームバリデーション

ブラウザの標準バリデーションに加えて、JavaScript でカスタムバリデーションを実装しましょう。

> フォームバリデーションのJavaScriptを書いて。
  - リアルタイムバリデーション(入力中にチェック)
  - 名前: 空でないこと
  - メール: 正しい形式であること
  - メッセージ: 10文字以上
  - エラーメッセージを日本語で表示
  - 送信時にすべてチェック
  - Formspreeへの送信はfetch APIで非同期に
  - 送信中のローディング表示
// ========================================
// Contact Form Validation
// ========================================
const contactForm = document.getElementById('contactForm');
const formStatus = document.getElementById('formStatus');

const validators = {
  name: {
    validate: (value) => value.trim().length > 0,
    message: 'お名前を入力してください'
  },
  email: {
    validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    message: '正しいメールアドレスを入力してください'
  },
  message: {
    validate: (value) => value.trim().length >= 10,
    message: 'メッセージは10文字以上で入力してください'
  }
};

// リアルタイムバリデーション
Object.keys(validators).forEach(fieldName => {
  const input = document.getElementById(fieldName);
  if (!input) return;

  input.addEventListener('blur', () => validateField(fieldName));
  input.addEventListener('input', () => {
    if (input.classList.contains('error')) {
      validateField(fieldName);
    }
  });
});

function validateField(fieldName) {
  const input = document.getElementById(fieldName);
  const errorEl = document.getElementById(fieldName + 'Error');
  const validator = validators[fieldName];

  if (!validator) return true;

  const isValid = validator.validate(input.value);

  if (isValid) {
    input.classList.remove('error');
    if (errorEl) errorEl.textContent = '';
  } else {
    input.classList.add('error');
    if (errorEl) errorEl.textContent = validator.message;
  }

  return isValid;
}

// フォーム送信
contactForm.addEventListener('submit', async (e) => {
  e.preventDefault();

  // 全フィールドのバリデーション
  let isFormValid = true;
  Object.keys(validators).forEach(fieldName => {
    if (!validateField(fieldName)) {
      isFormValid = false;
    }
  });

  if (!isFormValid) return;

  // ローディング表示
  const btnText = contactForm.querySelector('.btn-text');
  const btnLoading = contactForm.querySelector('.btn-loading');
  const submitBtn = contactForm.querySelector('.btn-submit');

  btnText.hidden = true;
  btnLoading.hidden = false;
  submitBtn.disabled = true;

  try {
    const formData = new FormData(contactForm);
    const response = await fetch(contactForm.action, {
      method: 'POST',
      body: formData,
      headers: { 'Accept': 'application/json' }
    });

    if (response.ok) {
      formStatus.textContent = '送信が完了しました。ありがとうございます。';
      formStatus.className = 'form-status success';
      contactForm.reset();
    } else {
      throw new Error('送信に失敗しました');
    }
  } catch (error) {
    formStatus.textContent = '送信に失敗しました。時間をおいて再度お試しください。';
    formStatus.className = 'form-status error';
  } finally {
    btnText.hidden = false;
    btnLoading.hidden = true;
    submitBtn.disabled = false;
  }
});

Formspree の設定方法

Formspree は静的サイトにフォーム送信機能を追加できる無料サービスです。設定手順を Claude Code に確認しましょう。

> Formspreeの登録から設定完了までの手順を教えて。
  HTMLフォームとの連携方法を含めて

設定手順は以下の通りです。

  1. formspree.io にアクセスしてアカウントを作成
  2. ダッシュボードで「New Form」をクリック
  3. フォーム名を入力して作成
  4. 発行されるフォーム ID(例: f/xyzabcde)をコピー
  5. HTML の <form> タグの action 属性にURLを設定
<form action="https://formspree.io/f/xyzabcde" method="POST">

Formspree の無料プランでは月50件まで送信可能です。ポートフォリオサイトであれば十分な件数です。送信されたメッセージは登録したメールアドレスに届きます。スパム対策としてハニーポットフィールドや reCAPTCHA の統合も可能です。

演習問題

  1. フォームの実装: 上記の HTML、CSS、JavaScript をプロジェクトに追加し、フォームが正しく表示されることを確認してください。

  2. バリデーションのテスト: 空のまま送信ボタンを押してエラーメッセージが表示されること、正しく入力するとエラーが消えることを確認してください。

  3. Formspree の設定: Formspree にアカウントを作成し、フォーム ID を取得して HTML に設定してください。テスト送信して、メールが届くことを確認しましょう。

  4. スパム対策: Claude Code に「ハニーポットフィールドを追加するHTMLとCSSを書いて」と依頼し、スパム対策を実装してみてください。ハニーポットは CSS で非表示にしたダミーフィールドで、ボットだけが入力してしまう仕組みです。

参考資料

Lecture 9SEOとパフォーマンス — 検索に強いサイトにする

12:00

SEOとパフォーマンス — 検索に強いサイトにする

素晴らしいポートフォリオサイトを作っても、検索エンジンに見つけてもらえなければ意味がありません。また、表示速度が遅いとユーザーは離脱してしまいます。この講義では、Claude Code を活用して SEO(検索エンジン最適化)対策とパフォーマンスの改善を行い、あなたのポートフォリオを「検索に強く、表示が速い」サイトに仕上げます。

メタタグの最適化

検索エンジンがサイトの内容を正しく理解するためには、適切なメタタグの設定が不可欠です。Claude Code にまとめて生成してもらいましょう。

> ポートフォリオサイトのSEO用メタタグを完全に書いて。
  以下を含めて:
  - 基本メタタグ(description, viewport, charset)
  - Open Graph タグ(Facebook/LINE用)
  - Twitter Card タグ
  - favicon(複数サイズ)
  - canonical URL
  - 構造化データ(JSON-LD、Person型)
  名前: 山田太郎、職業: Webエンジニア
  URL: https://yamada-portfolio.com

Claude Code が生成するメタタグの完全版です。

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- 基本SEOメタタグ -->
  <title>山田太郎 | Webエンジニア ポートフォリオ</title>
  <meta name="description" content="山田太郎のポートフォリオサイト。
  React、Node.js を使ったWeb開発の実績を紹介しています。
  フロントエンドからバックエンドまで幅広く対応。">
  <meta name="keywords" content="山田太郎, Webエンジニア, ポートフォリオ,
  React, JavaScript, フロントエンド">
  <meta name="author" content="山田太郎">
  <link rel="canonical" href="https://yamada-portfolio.com/">

  <!-- Open Graph (Facebook, LINE, etc.) -->
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://yamada-portfolio.com/">
  <meta property="og:title" content="山田太郎 | Webエンジニア ポートフォリオ">
  <meta property="og:description" content="React、Node.js を使ったWeb開発の
  実績を紹介しています。">
  <meta property="og:image" content="https://yamada-portfolio.com/images/ogp.jpg">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:locale" content="ja_JP">
  <meta property="og:site_name" content="山田太郎 ポートフォリオ">

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@yamada_taro">
  <meta name="twitter:title" content="山田太郎 | Webエンジニア ポートフォリオ">
  <meta name="twitter:description" content="React、Node.js を使ったWeb開発の
  実績を紹介しています。">
  <meta name="twitter:image" content="https://yamada-portfolio.com/images/ogp.jpg">

  <!-- Favicon -->
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
  <link rel="icon" type="image/x-icon" href="/favicon.ico">
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">

  <!-- 構造化データ (JSON-LD) -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Person",
    "name": "山田太郎",
    "url": "https://yamada-portfolio.com",
    "jobTitle": "Webエンジニア",
    "sameAs": [
      "https://github.com/yamada-taro",
      "https://twitter.com/yamada_taro",
      "https://linkedin.com/in/yamada-taro"
    ],
    "knowsAbout": ["HTML", "CSS", "JavaScript", "React", "Node.js", "Python"]
  }
  </script>

  <!-- フォント・スタイルシート -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap"
        rel="stylesheet">
  <link rel="stylesheet" href="css/reset.css">
  <link rel="stylesheet" href="css/style.css">
</head>

OGP 画像の作成

SNS でシェアされたときに表示される OGP(Open Graph Protocol)画像は非常に重要です。推奨サイズは 1200x630 ピクセルです。

> OGP画像を作るためのHTMLテンプレートを書いて。
  - サイズ: 1200x630px
  - 背景: ダークグラデーション
  - 名前: 山田太郎
  - 肩書き: Web Engineer
  - URL: yamada-portfolio.com
  - スクリーンショットを撮ってOGP画像として使える形式で

Claude Code はスクリーンショットで使える HTML テンプレートを生成してくれます。この HTML をブラウザで開いてスクリーンショットを撮れば、OGP 画像として利用できます。また、Canva や Figma などのツールでもっと凝ったデザインの OGP 画像を作ることもできます。

画像の最適化

画像はサイトの表示速度に最も影響を与える要素です。Claude Code に最適化の方法を聞きましょう。

> ポートフォリオサイトの画像最適化の方法を教えて。
  - WebP 形式への変換方法
  - 適切なサイズへのリサイズ
  - 遅延読み込み(lazy loading)の実装
  - 画像圧縮のコマンドライン方法

画像最適化のポイントをまとめます。

WebP 形式への変換: WebP は JPEG や PNG より 25〜35% ファイルサイズが小さい画像フォーマットです。

# cwebp コマンドで変換(Google の webp ツール)
cwebp -q 80 images/work-01.jpg -o images/work-01.webp

# 一括変換(bash)
for f in images/*.jpg; do
  cwebp -q 80 "$f" -o "${f%.jpg}.webp"
done

HTML での WebP 対応: <picture> 要素を使って、WebP 対応ブラウザには WebP を、非対応ブラウザには JPEG をフォールバックとして表示します。

<picture>
  <source srcset="images/work-01.webp" type="image/webp">
  <img src="images/work-01.jpg" alt="プロジェクト1"
       loading="lazy" width="600" height="375">
</picture>

遅延読み込み: loading="lazy" 属性を追加するだけで、画面外の画像はスクロールで表示エリアに近づいたときに読み込まれます。ファーストビュー(ヒーロー画像など)には loading="eager" を指定して即座に読み込むようにします。

パフォーマンスの測定と改善

Google の Lighthouse を使ってサイトのパフォーマンスを測定しましょう。

> Lighthouse でパフォーマンスを改善するための
  チェックリストを作って。
  ポートフォリオサイトで特に重要な項目を
  優先度順に並べて

Claude Code が提示する改善チェックリストです。

優先度 高: 1. 画像を WebP に変換し、適切なサイズにリサイズする 2. CSS と JavaScript を minify する 3. loading="lazy" をファーストビュー以外の画像に追加する 4. Google Fonts に display=swap を指定する(既に設定済み) 5. 未使用の CSS を削除する

優先度 中: 6. <link rel="preconnect"> で外部リソースへの接続を事前に開始する 7. CSS を <head> 内、JavaScript を </body> 直前に配置する(既に設定済み) 8. 画像に widthheight 属性を明示して CLS(レイアウトシフト)を防ぐ 9. フォントファイルを preload する

優先度 低: 10. Service Worker でオフラインキャッシュを実装する 11. HTTP/2 に対応したサーバーを使う

CSS の minify は Claude Code にも依頼できます。

> style.css を minify して。
  コメントと空白を除去したバージョンを
  css/style.min.css として出力して

sitemap.xml と robots.txt

検索エンジンのクロールを助けるファイルも用意しましょう。

> sitemap.xmlとrobots.txtを生成して。
  URL: https://yamada-portfolio.com
  ページ: index.html のみ(シングルページ)
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://yamada-portfolio.com/</loc>
    <lastmod>2026-02-16</lastmod>
    <changefreq>monthly</changefreq>
    <priority>1.0</priority>
  </url>
</urlset>
# robots.txt
User-agent: *
Allow: /
Sitemap: https://yamada-portfolio.com/sitemap.xml

これらのファイルをプロジェクトのルートに配置することで、Google や Bing などの検索エンジンがサイトの構造を効率よくクロールできるようになります。

Lighthouse 監査の実行方法

実際に Lighthouse を実行して現在のスコアを確認しましょう。

  1. Chrome でポートフォリオサイトを開く
  2. F12 で DevTools を開く
  3. 「Lighthouse」タブをクリック
  4. 「Performance」「Accessibility」「Best Practices」「SEO」にチェック
  5. 「Analyze page load」をクリック

各カテゴリで 90 点以上を目指しましょう。Claude Code にスコアを伝えると、具体的な改善策を提案してくれます。

> Lighthouseの結果です:
  Performance: 75, Accessibility: 92, Best Practices: 88, SEO: 85
  スコアを改善するための具体的な修正方法を教えて

演習問題

  1. メタタグの追加: 上記のメタタグを index.html<head> に追加してください。自分の情報に書き換えることを忘れずに。

  2. OGP 画像の作成: Claude Code に OGP 画像用の HTML テンプレートを作ってもらい、ブラウザでスクリーンショットを撮って images/ogp.jpg として保存してください。

  3. Lighthouse の実行: Chrome DevTools で Lighthouse を実行し、各スコアを記録してください。90 点未満のカテゴリがあれば、Claude Code に改善方法を聞いて対応しましょう。

  4. 画像の最適化: プロジェクトの画像を WebP 形式に変換し、HTML を <picture> 要素に書き換えてください。Lighthouse のスコアが改善することを確認しましょう。

参考資料

Lecture 10GitHub Pagesデプロイ — サイトを世界に公開する

12:00

GitHub Pagesデプロイ — サイトを世界に公開する

おめでとうございます。ここまでの講義で、プロフェッショナルなポートフォリオサイトが完成しました。最後のステップは、このサイトをインターネットに公開して世界中からアクセスできるようにすることです。この講義では、Claude Code を使って Git の初期設定、GitHub へのプッシュ、GitHub Pages でのデプロイ、さらにカスタムドメインの設定までを行います。

Git の初期設定

まず、プロジェクトを Git で管理できるようにしましょう。

> ポートフォリオプロジェクトのGit初期設定をして。
  - git init
  - .gitignore ファイルの作成(不要ファイルを除外)
  - 初回コミット
  必要なコマンドを順番に教えて

Claude Code が案内する手順です。

# プロジェクトディレクトリに移動
cd portfolio

# Git リポジトリの初期化
git init

# .gitignore の作成
# (Claude Code がファイル内容も生成してくれる)

まず .gitignore ファイルを作成しましょう。

> ポートフォリオサイト用の .gitignore を作って。
  除外すべきもの:
  - OS生成ファイル(.DS_Store, Thumbs.db)
  - エディタ設定(.vscode, .idea)
  - node_modules(もし使う場合)
  - 環境変数ファイル

Claude Code が生成する .gitignore です。

# OS generated files
.DS_Store
.DS_Store?
Thumbs.db
ehthumbs.db
Desktop.ini

# Editor directories and files
.vscode/
.idea/
*.sublime-workspace
*.sublime-project
*.swp
*.swo

# Dependencies
node_modules/

# Environment variables
.env
.env.local

# Build output
dist/
build/

# Misc
*.log
*.tmp

ファイルを作成したら、初回コミットを行います。

# すべてのファイルをステージング
git add .

# 初回コミット
git commit -m "feat: ポートフォリオサイト初期バージョン"

GitHub リポジトリの作成

次に、GitHub にリモートリポジトリを作成してコードをプッシュします。

> GitHubに新しいリポジトリを作成してコードをプッシュする
  手順を教えて。
  - リポジトリ名: portfolio
  - Public リポジトリ
  - GitHub CLI (gh) を使う方法も含めて

方法1: GitHub CLI(gh コマンド)を使う方法

GitHub CLI がインストールされている場合、ターミナルからリポジトリを作成できます。

# GitHub CLI でリポジトリを作成してプッシュ
gh repo create portfolio --public --source=. --push

このコマンド1つで、リポジトリの作成、リモートの設定、プッシュまですべて完了します。Claude Code で gh コマンドを直接実行することもできます。

方法2: GitHub Web UI を使う方法

  1. github.com/new にアクセス
  2. Repository name に portfolio と入力
  3. Public を選択
  4. 「Create repository」をクリック
  5. 表示される手順に従ってリモートを追加
# リモートリポジトリを追加
git remote add origin https://github.com/あなたのユーザー名/portfolio.git

# main ブランチにプッシュ
git branch -M main
git push -u origin main

GitHub Pages の有効化

リポジトリにコードをプッシュしたら、GitHub Pages を有効にします。

> GitHub Pages を有効にする手順を教えて。
  - リポジトリの Settings から設定する方法
  - GitHub CLI で設定する方法
  - デプロイが完了するまでの確認方法

Settings からの設定手順:

  1. GitHub のリポジトリページにアクセス
  2. 「Settings」タブをクリック
  3. 左メニューの「Pages」をクリック
  4. 「Source」で「Deploy from a branch」を選択
  5. Branch で「main」、フォルダで「/ (root)」を選択
  6. 「Save」をクリック

数分後に、以下の URL でサイトが公開されます。

https://あなたのユーザー名.github.io/portfolio/

GitHub CLI での設定:

# GitHub Pages の設定(GitHub CLI)
gh api repos/{owner}/{repo}/pages -X POST \
  -f build_type=legacy \
  -f source='{"branch":"main","path":"/"}'

デプロイの状況は GitHub リポジトリの「Actions」タブで確認できます。緑のチェックマークが表示されればデプロイ完了です。

カスタムドメインの設定

GitHub Pages のデフォルト URL(ユーザー名.github.io/portfolio)をカスタムドメイン(例: yamada-portfolio.com)に変更する方法を学びましょう。

> GitHub Pages にカスタムドメインを設定する手順を教えて。
  ドメイン: yamada-portfolio.com
  - DNS設定(Aレコード、CNAMEレコード)
  - HTTPS対応
  - CNAMEファイルの作成

ステップ1: CNAME ファイルの作成

プロジェクトのルートに CNAME ファイルを作成します。

yamada-portfolio.com
echo "yamada-portfolio.com" > CNAME
git add CNAME
git commit -m "feat: カスタムドメイン設定"
git push

ステップ2: DNS の設定

ドメインレジストラ(お名前.com、ムームードメインなど)の DNS 設定画面で以下を追加します。

# A レコード(GitHub Pages の IP アドレス)
A    @    185.199.108.153
A    @    185.199.109.153
A    @    185.199.110.153
A    @    185.199.111.153

# CNAME レコード(www サブドメイン用)
CNAME    www    あなたのユーザー名.github.io.

ステップ3: HTTPS の有効化

GitHub Pages の Settings → Pages で「Enforce HTTPS」にチェックを入れます。SSL 証明書は GitHub が自動で発行してくれるので、追加の設定は不要です。反映まで最大24時間かかる場合がありますが、通常は数分から1時間程度で完了します。

デプロイ後の確認チェックリスト

サイトが公開されたら、最終確認を行いましょう。Claude Code に確認リストを作ってもらいます。

> ポートフォリオサイトをデプロイした後の
  確認チェックリストを作って。
  表示確認、リンク確認、SEO確認、パフォーマンス確認を含めて

デプロイ後に確認すべき項目は以下の通りです。

表示確認: - デスクトップ(Chrome、Firefox、Safari)で表示が正しいか - スマートフォン(iOS Safari、Android Chrome)で表示が正しいか - フォントが正しく読み込まれているか - 画像がすべて表示されているか

機能確認: - ナビゲーションリンクが正しく動作するか - ハンバーガーメニューが正しく開閉するか - スムーススクロールが動作するか - フォーム送信が正しく動作するか(Formspree にテスト送信) - フィルタリング機能が動作するか

SEO確認: - Google の「URL検査ツール」でインデックス登録をリクエスト - OGP 画像が正しく表示されるか(OGP確認ツールで確認) - robots.txt と sitemap.xml にアクセスできるか

パフォーマンス確認: - Lighthouse で各スコアが 90 以上か - PageSpeed Insights でモバイル・デスクトップ両方をテスト

継続的な更新の仕方

サイト公開後も、作品の追加や情報の更新を継続的に行いましょう。

# 新しい作品を追加した場合の更新手順
git add .
git commit -m "feat: 新しいプロジェクトを追加"
git push origin main

# GitHub Pages は自動的に再デプロイされる

Claude Code を使えば更新作業も効率的に進められます。

> 新しい作品カードを追加して。
  プロジェクト名: AIチャットボット
  技術: Python, OpenAI API, Streamlit
  概要: GPT-4を使った対話型チャットボット
  カテゴリ: fullstack

このように Claude Code に指示するだけで、既存のフォーマットに合わせた HTML が生成されます。コードをコピーして index.html に追加し、コミット・プッシュするだけでサイトが更新されます。

コースのまとめ

全10回の講義を通じて、以下のスキルを身につけました。

  1. プロジェクト設計: フォルダ構成、ワイヤーフレーム、カラースキーム
  2. HTML構造: セマンティックHTML5、アクセシビリティ
  3. CSSデザイン: CSS Grid、Flexbox、カスタムプロパティ
  4. レスポンシブ対応: メディアクエリ、ハンバーガーメニュー
  5. プロフィールセクション: スキルバー、ソーシャルリンク
  6. 作品ギャラリー: プロジェクトカード、フィルタリング
  7. アニメーション: CSS Animation、Intersection Observer
  8. お問い合わせフォーム: バリデーション、Formspree
  9. SEOとパフォーマンス: メタタグ、画像最適化、Lighthouse
  10. デプロイ: Git、GitHub Pages、カスタムドメイン

そしてすべての工程で Claude Code を活用する方法を学びました。Claude Code はコードの生成だけでなく、設計の相談、コードレビュー、バグ修正、ベストプラクティスの提案など、開発のあらゆる場面で頼れるパートナーです。これからも積極的に活用して、ポートフォリオを進化させ続けてください。

演習問題

  1. Git 初期設定とプッシュ: プロジェクトを Git で初期化し、GitHub にリポジトリを作成してプッシュしてください。

  2. GitHub Pages の有効化: リポジトリの Settings から GitHub Pages を有効にし、サイトが公開されることを確認してください。公開 URL をブラウザで開いて正常に表示されることを確認しましょう。

  3. 最終チェック: 上記のデプロイ後チェックリストを実行し、すべての項目がパスすることを確認してください。問題があれば Claude Code に修正を依頼しましょう。

  4. SNS でシェア: 公開したポートフォリオの URL を X(Twitter)や LinkedIn でシェアしてみましょう。OGP 画像が正しく表示されることを確認してください。

参考資料