Lv.01

入門編 湘南のカフェサイト

1ページ・全部手で書く

琥珀

琥珀からの依頼書

海沿いのカフェの1ページサイトを、配布したカンプ通りに作ってください。

今回はコードを1行もAIに書かせないでください。分からないところは、質問だけしてください。

出す前に npx html-validate をかけて、error 0 にしてください。

カンプと配布物

HTMLカンプ1枚/PNG(1440・390)/完成ZIP/公開デモ

手で書くところ

セマンティックHTML/リセットCSS/box-sizing/flexbox/gap/object-fit/picture要素/メディアクエリ768px

AIに任せるところと検品

AIは質問だけに使う(「このCSSが効かない理由」)/npx html-validate で error 0

依頼書を読む

依頼が1本来ています。鵠沼海岸のコーヒースタンド「凪珈琲スタンド」の1ページサイトです。テイクアウトが中心で、店内は8席だけ。来るのは朝の散歩の人、サーフィン帰りの人、在宅ワークの休憩の人です。この人たちが知りたいのは「どんな店で・何が飲めて・いくらで・いつ開いていて・どこにあるか」の5つだけ。ページもこの順に並べます。今回はコードを1行もAIに書かせません。上から下まで、あなたの手で書きます。

完成すると、こうなります。

完成イメージ

スマートフォンの幅(390px)ではこうなります。写真が縦位置に変わり、横に並んでいたものが全部縦に積まれます。

完成イメージ(スマートフォン)

作業の目安は4〜6時間です。1日でやりきらなくてかまいません。セクションは7つあるので、「今日はメニューまで」と区切って進められます。

ここまでで OK:完成の絵を見て、上から「ヘッダー・ヒーロー・このお店・メニュー・店内・アクセス・フッター」の7つに区切れている状態。

作業フォルダと画像をそろえる

準備編で作った coding-practice/01-cafe/ を VSCode で開いてください。その中に index.htmlstyle.css を置きます。このレッスンでは JavaScript を使わないので、script.js は空のままで大丈夫です。

写真は配布ZIPの images/ フォルダに入っています。ZIPを開いて、images/ フォルダごと 01-cafe/ の中にコピーしてください。中身は9枚です。

text
01-cafe/
  index.html
  style.css
  images/
    hero-pc.webp
    hero-sp.webp
    about.webp
    menu-01.webp
    menu-02.webp
    menu-03.webp
    gallery-01.webp
    gallery-02.webp
    gallery-03.webp
    CREDITS.md

.webp は画像の保存形式で、同じ見た目のまま .jpg より軽く保存できます。CREDITS.md には写真家の名前と配布元のURLが書いてあります。ポートフォリオで使うときも、このファイルは一緒に持っておいてください。

これは手で書く

今回は最初から最後まで手で書きます。理由は「速いから」ではありません。自分で1回書いた形が、AIの書いたものを読むときの物差しになるからです。物差しを持たないまま書かせると、出てきたコードの良し悪しを判断できず、そのまま貼るしかなくなります。

ここまでで OK:01-cafe フォルダの中に、空の index.htmlstyle.css、写真9枚が入った images/ がある状態。

HTMLの外側と head を書く

まず、ページの一番外側と <head>(ブラウザに向けた説明書きの場所。画面には出ません)を書きます。index.html の1行目から、そのまま書き写してください。

html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>凪珈琲スタンド | 鵠沼海岸のコーヒースタンド</title>
<meta name="description" content="神奈川県藤沢市鵠沼海岸のコーヒースタンド、凪珈琲スタンド。8:00-17:00営業、水曜定休。テイクアウト中心・イートイン8席。">
<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=Jost:wght@500&family=Noto+Sans+JP:wght@400&family=Zen+Old+Mincho:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>

<html lang="ja"> は「このページは日本語です」という宣言で、読み上げソフトの発音とブラウザの翻訳の判断が変わります。<meta name="viewport"> の1行はスマートフォンで縮小表示にならないための指定で、これが無いと後で書くメディアクエリが一切効きません。<title> はタブと検索結果に出る文字、<meta name="description"> はその下に出る説明文です。

<link rel="preconnect"> の2行は、フォントの配布元へ先に接続だけしておく指定です。次の1行が3種類のフォント(Jost・Noto Sans JP・Zen Old Mincho)を取ってくる行、最後の <link rel="stylesheet" href="style.css"> が同じフォルダの自分のCSSを読み込む行です。

ファイルの一番下には、閉じる2行を先に書いておいてください。これから書くものは、全部この上に入ります。

html
</body>
</html>

ここまでで OK:ブラウザで開くと真っ白だが、タブに「凪珈琲スタンド | 鵠沼海岸のコーヒースタンド」と出ている状態。

ヘッダーを書く

<body> のすぐ下に、ヘッダーを書きます。店名・ナビ・電話番号の3つが入ります。

html
<header>
  <a class="logo" href="#top">
    <span class="logo-en">NAGI</span>
    <span class="logo-jp">凪珈琲スタンド</span>
  </a>
  <nav class="nav">
    <ul>
      <li><a href="#about">このお店</a></li>
      <li><a href="#menu">メニュー</a></li>
      <li><a href="#gallery">店内</a></li>
      <li><a href="#access">アクセス</a></li>
    </ul>
  </nav>
  <a class="tel" href="tel:0466000000"><span class="tel-label">TEL</span>0466&#8209;00&#8209;0000</a>
</header>

<div> ではなく <header><nav> を使うのは、そこが何の場所かをタグ自体に持たせるためです。読み上げソフトは <nav> を見つけると「ナビゲーション」と読み、利用者はそこを飛ばして本文へ移動できます。ナビの4項目を <ul> <li> で囲むのは、順不同の同列な項目の集まりだからです。リンク先の #about は、id="about" を付けた要素へジャンプします。

電話番号は href="tel:0466000000" と、ハイフンを取った数字だけにします。スマートフォンで押すと発信画面が開きます。表示側の &#8209; は「折り返さないハイフン」で、狭い画面で番号が途中で改行されるのを防ぎます。

ここまでで OK:左上に「NAGI 凪珈琲スタンド」、その下にナビ4項目と電話番号が縦に並んでいる状態(横並びにするのはCSSの仕事です)。

ヒーローを書く

ページを開いて最初に目に入る一枚です。<main> の中に入れます。

html
<main>

  <section id="top" class="hero">
    <picture>
      <source media="(max-width: 768px)" srcset="images/hero-sp.webp">
      <img src="images/hero-pc.webp" alt="朝日の差し込む店内カウンターとコーヒーの道具" width="1600" height="900">
    </picture>
    <div class="hero-text">
      <h1>海の音がする、朝のコーヒー</h1>
      <p>鵠沼海岸から歩いて3分。8時に開けて、17時に閉めます。<br>テイクアウトのカップを持って、海まで5分の散歩へ。</p>
      <p class="hero-note">8:00 - 17:00 / 水曜定休</p>
    </div>
  </section>

<main> は「このページの主な中身」を示すタグで、1ページに1つだけ置きます。ヘッダーとフッターは中に入れません。

<picture> は、画面幅によって別々の画像ファイルを読ませる仕組みです。<source media="(max-width: 768px)"> に当てはまるとき(768px以下=スマートフォン)は縦位置の hero-sp.webp、当てはまらないときは <img> の横位置の hero-pc.webp を読みます。CSSで隠すのと違って、読み込むファイル自体が1枚だけで済みます。

alt は、画像が出ないときに代わりに読まれる文です。widthheight を書くのは、写真が届く前にブラウザが場所を確保できるようにするためで、無いと読み込み直後に下の文章が突然ずれます。<h1> はページで一番大きな見出しで、1ページに1つだけにします。

ここまでで OK:写真が1枚出て、その下に見出しとリード文がそのまま縦に並んでいる状態(写真に重ねるのはCSSの仕事です)。

このお店についてを書く

ヒーローの下に続けます。

html
  <section id="about" class="about">
    <div class="about-text">
      <h2>このお店について</h2>
      <p>豆は週に2回、少しずつ焙煎しています。たくさん作って置いておくより、
      その週に飲みきれる量だけを煎ったほうが、おいしいと思っているからです。</p>
      <p>席は8つだけです。混みあう時間は、テイクアウトのカップをお渡しして、
      海のほうへ送り出しています。急がないでください。ここは、そういう店です。</p>
    </div>
    <div class="about-photo">
      <img src="images/about.webp" alt="焙煎したてのコーヒー豆" width="900" height="1100">
    </div>
  </section>

<section> は「見出しが付く1かたまり」を示すタグで、中に必ず見出しを置きます。<h1> の次なので <h2>見出しは数字を飛ばさないのが決まりで、いきなり <h3> を置くと目次の階層が崩れます。

文章と写真をそれぞれ <div> で包むのは、この2つを左右に並べたいからです。意味のある区切りには <section>、並べるためだけの箱には <div>、と使い分けます。<p> の中の改行は表示には出ません(連続した改行や空白は、半角スペース1つとして扱われます)。

ここまでで OK:見出し・文章2つ・写真1枚が、上から順に縦に並んでいる状態。

メニューを書く

3点のカードが並ぶセクションです。

html
  <section id="menu" class="menu">
    <h2>メニュー</h2>
    <p class="menu-lead">価格は税込みです。テイクアウトも店内も同じ価格です。</p>
    <div class="cards">
      <div class="card">
        <img src="images/menu-01.webp" alt="一杯ずつ落とす本日のドリップコーヒー" width="800" height="800">
        <h3>本日のドリップ</h3>
        <p>その週に煎った豆を、一杯ずつ落とします。</p>
        <p class="price">480円</p>
      </div>
      <div class="card">
        <img src="images/menu-02.webp" alt="よく冷やしたミルクを使った潮風ラテ" width="800" height="800">
        <h3>潮風ラテ</h3>
        <p>深めに煎った豆と、よく冷やしたミルクで。</p>
        <p class="price">580円</p>
      </div>
      <div class="card">
        <img src="images/menu-03.webp" alt="朝いちばんに焼いたスコーン" width="800" height="800">
        <h3>焼きたてスコーン</h3>
        <p>朝いちばんに焼きます。売り切れたら終わりです。</p>
        <p class="price">380円</p>
      </div>
    </div>
    <p class="menu-extra">このほかに、水出しコーヒー(600円)と、日替わりの焼き菓子があります。</p>
  </section>

品名が <h3> なのは、<h2> の下にぶら下がる小見出しだからです。同じ形の箱が3つ並ぶときは、中身のタグの並び順も3つとも同じにします。 ここでは「写真 → 品名 → 説明 → 価格」で統一しているので、CSSは3枚まとめて1回書くだけで済みます。価格の <p> にだけ class="price" を付けて、色と大きさを変える目印にしています。

これは手で書く

同じ形のカードを、コピーして貼るのではなく3回とも打ってみてください。3枚目には img h3 p p の並びが手に入っています。ここで体に入る「かたまりの形」が、02以降でAIの出力を読むときにそのまま効いてきます。

ここまでで OK:写真・品名・説明・価格の組が3つ、縦に続いている状態。

店内を書く

写真3枚に、それぞれ説明文が付くセクションです。

html
  <section id="gallery" class="gallery">
    <h2>店内と、まわりのこと</h2>
    <p>店の前の道をまっすぐ行くと、海に出ます。朝は散歩の人、
    昼はしごとの合間の人、夕方はサーフィン帰りの人が来ます。</p>
    <div class="photos">
      <figure>
        <img src="images/gallery-01.webp" alt="8席のカウンター席と窓の外の松林" width="1200" height="800">
        <figcaption>カウンターは8席。窓の向こうは松林です。</figcaption>
      </figure>
      <figure>
        <img src="images/gallery-02.webp" alt="青いひさしが目印の店の外観" width="1200" height="800">
        <figcaption>外観。青いひさしが目印です。</figcaption>
      </figure>
      <figure>
        <img src="images/gallery-03.webp" alt="店の奥にある焙煎機" width="1200" height="800">
        <figcaption>焙煎機は店の奥にあります。</figcaption>
      </figure>
    </div>
  </section>

ここだけ <div> ではなく <figure> を使っています。図とその説明文をひとまとめにするタグで、説明文は <figcaption> に入れます。写真の下に <p> を置くより、「この文はこの写真の説明である」という関係が正確に伝わります。

altfigcaption は役割が違います。alt写真の代わり(何が写っているか)、figcaption写真に添える言葉(読み手に伝えたいこと)。同じ文をコピーしません。

ここまでで OK:写真とキャプションの組が3つ、縦に続いている状態。

アクセスを書く

営業情報を表で書きます。

html
  <section id="access" class="access">
    <h2>アクセス・営業時間</h2>
    <table>
      <tbody>
        <tr><th>住所</th><td>神奈川県藤沢市鵠沼海岸0-0-0</td></tr>
        <tr><th>電話</th><td>0466-00-0000</td></tr>
        <tr><th>営業時間</th><td>8:00 - 17:00(ラストオーダー 16:30)</td></tr>
        <tr><th>定休日</th><td>水曜日</td></tr>
        <tr><th>席数</th><td>8席</td></tr>
        <tr><th>駐車場</th><td>ありません。近くのコインパーキングをご利用ください。</td></tr>
      </tbody>
    </table>
    <p class="access-note">最寄りは小田急江ノ島線 鵠沼海岸駅です。改札を出て海の方向へ、歩いて3分ほどです。</p>
  </section>

</main>

ここは「項目名と値の組」が6つ並ぶ、まさに表のデータです。<table> は見た目を整えるために使うものではありませんが、中身が本当に表であるときは表で書くのが正解です。

<tr> が横1行、<th> が見出しのマス、<td> が値のマスです。左の列を <th> にしているので、読み上げソフトが「住所、神奈川県藤沢市……」と項目名とセットで読んでくれます。最後に </main> を閉じて、ページの主な中身は終わりです。

ここまでで OK:6行の表が出て、その下に最寄り駅の1行がある状態。

フッターを書く

</main> の下に、ページの締めを書きます。

html
<footer>
  <p class="footer-name">凪珈琲スタンド</p>
  <p>神奈川県藤沢市鵠沼海岸0-0-0 / 0466-00-0000 / hello@example.com</p>
  <p>8:00 - 17:00 / 水曜定休</p>
  <p class="footer-note">このサイトは、コーディング練習用に作られた架空のお店のサイトです。</p>
</footer>

<footer> も「ここはページの終わりの案内」という意味をタグ自体が持っています。<main> の外に置くのは、フッターがこのページ固有の本文ではなく、サイト全体に付いてくる情報だからです。一番下の1行は練習用サイトである断り書きで、これがあるとポートフォリオに載せたときに見る人が誤解しません。

ここまでで OK:HTMLがこれで全部です。ブラウザで開くと、写真と文字が上から順に、飾り気なく縦に並んでいる状態。ここで一度 npx -y html-validate index.html を打っておくと、後がラクです。

CSS:リセットと文字の土台を書く

ここからは style.css です。HTMLと同じ順番で上から書いていきます。 ファイルの先頭に、まずこのコメントを置きます。

css
/* 凪珈琲スタンド
   このレッスンではわざと「壊れやすいが分かりやすい書き方」で書いている。
   子孫セレクタでHTMLの階層をなぞり、数値はすべてpxで直書きする。
   CSS変数もBEMも使わない。次のレッスン(02)でBEMとCSS変数に組み替える。 */

/* リセットCSS:ブラウザごとの初期値の差を消す。差が消えないと、余白の計算が毎回ずれる */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

* は「すべての要素」を指します。ブラウザは <p><h1> に最初から余白を持っていて、その値はブラウザごとに違います。ここで一度ゼロにしてから、必要な余白を自分で足します。これがリセットCSS(ブラウザごとの初期表示の差を消すCSS)です。

box-sizing: border-box は、幅の数え方を変える指定です。既定では width: 300pxpadding: 24px を足すと実際の幅は348pxになりますが、border-box なら padding と枠線を含めて300pxに収まります。「300pxと書いたら300px」なので、計算がずれません。

続けて、タグ単位の初期設定を書きます。

css
img {
  display: block;
  max-width: 100%;
}

ul {
  list-style: none;
}

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

imgdisplay: block は、画像の下にできる数pxの隙間を消すためです。画像は既定では文字と同じ扱いなので、行の下に文字の足の分の余白が残ります。max-width: 100% は、置かれた場所より大きくならないようにする保険です。ul の黒丸を消し、a の色と下線を打ち消しています(color: inherit は「親の文字色をそのまま受け継ぐ」)。

最後に、ページ全体の文字を決めます。

css
body {
  font-family: "Noto Sans JP", sans-serif;
  font-size: 16px;
  line-height: 1.9;
  color: #2e2a26;
  background: #f7f3ec;
}

h1, h2, h3, .logo-jp {
  font-family: "Zen Old Mincho", serif;
  font-weight: 500;
}

font-family はカンマ区切りで、左から順に「あれば使う」という指定です。読み込みに失敗すると sans-serif(環境の標準的なゴシック体)に落ちます。line-height: 1.9 は行の高さで、この店の落ち着いた印象はほぼこの数字で決まっています。文字を詰めると、同じ文章でも急がされている感じになります。h1, h2, h3, .logo-jp のようにカンマで並べると、同じ指定を複数のセレクタにまとめて当てられます

これは手で書く

リセットCSSは毎回ほぼ同じ数行です。それでも手で置くのは、「いまゼロにした」と自分が知っているかどうかで、余白がずれたときの探し方が変わるからです。自分で足した余白なのか、ブラウザが元から持っている余白なのか。ここが分かっていれば、DevToolsで迷いません。

ここまでで OK:背景がうすいベージュになり、文字が茶色がかった黒になって、行間が広がっている状態。

CSS:ヘッダーを整える

HTMLの順番どおり、ヘッダーから。

css
/* ヘッダー:上部固定はしない。ロゴ・ナビ・電話を横に並べるだけ */
header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding: 24px 24px;
  background: #ffffff;
  border-bottom: 1px solid #e3dcd0;
}

display: flex は、中の子要素を一列に並べる指定です。ここでは「ロゴ」「ナビ」「電話」の3つが横に並びます。justify-content: space-between は横方向の配り方で、両端に寄せて余りを間に入れます。align-items: center は高さの違う3つの中心を合わせ、gap: 24px は子どうしの最低限の間隔を空けます。padding: 24px 24px は値が2つのとき「上下・左右」の意味になります。

次に、ロゴの2段組みです。

css
.logo {
  display: flex;
  flex-direction: column;
}

.logo-en {
  font-family: "Jost", sans-serif;
  font-weight: 500;
  font-size: 22px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #2e2a26;
}

.logo-jp {
  font-size: 12px;
  letter-spacing: 0.1em;
  color: #8a7f72;
}

ここでも display: flex ですが、flex-direction: column を足しているので縦並びになります。flexbox は「横に並べる道具」ではなく「一列に並べる道具」で、向きは自分で選べる、と覚えると混乱しません。

letter-spacing: 0.24em は字間です。em は「その要素の文字サイズを1とした単位」なので、文字サイズを変えても字間の比率が崩れません。text-transform: uppercase は、大文字で表示する指定です。

最後にナビと電話番号です。

css
/* ナビ:子孫セレクタで .nav > ul > li > a をそのままたどる。
   HTMLの入れ子を変えると、このセレクタは効かなくなる */
.nav ul {
  display: flex;
  gap: 24px;
}

.nav ul li a {
  font-size: 15px;
  color: #2e2a26;
}

.tel {
  font-size: 15px;
  white-space: nowrap;
}

.tel-label {
  font-size: 11px;
  color: #8a7f72;
  margin-right: 4px;
}

.nav ul li a のように半角スペースで区切ったものを子孫セレクタと呼びます。「.nav の中の ul の中の li の中の a」という意味で、HTMLの入れ子をそのままなぞった書き方です。

補足

このレッスンでは、CSSを子孫セレクタと 24px のような数値の直書きで書いています。いまは読みやすさを優先しています。次の回で、直しやすい形に組み替えます。 02では、この .nav ul li a をクラス名ひとつで指す書き方に変え、何十か所にも出てくる 24px#2e2a26 を1か所にまとめます。

white-space: nowrap は「途中で折り返さない」という指定です。電話番号が2行に割れると読み間違いのもとになるので、ここだけ止めています。

ここまでで OK:ヘッダーが白い帯になり、左にロゴ・中央にナビ4項目・右に電話番号が横一列に並んでいる状態。

CSS:ヒーローと、このお店について

写真の上に文字を重ねます。

css
/* ヒーロー:写真の上に文字を重ねる */
.hero {
  position: relative;
}

.hero img {
  width: 100%;
  height: 480px;
  object-fit: cover;
}

.hero-text {
  position: absolute;
  left: 0;
  bottom: 0;
  padding: 24px 24px 40px;
  color: #ffffff;
  background: rgba(46, 42, 38, 0.45);
}

.hero-text h1 {
  font-size: 32px;
  margin-bottom: 16px;
}

.hero-text p {
  font-size: 16px;
  margin-bottom: 8px;
}

.hero-note {
  font-size: 14px;
}

重ね方は2つで1組です。重ねたい相手(.hero)に position: relative、重ねるもの(.hero-text)に position: absolute こうすると left: 0; bottom: 0; の基準が、画面全体ではなく .hero の左下になります。片方だけ書くと、文字が予想外の場所へ飛びます。

object-fit: cover は画像専用の指定で、縦横比を保ったまま、指定した枠いっぱいに切り抜くという意味です。width: 100%height: 480px で枠を決めているので、元の写真が横長でも縦長でも、つぶれずに枠を埋めます。rgba(46, 42, 38, 0.45) の最後の 0.45 は不透明度で、写真の上に濃い色をうっすら敷いて白い文字が読める明るさに落としています。

続けて「このお店について」を左右2カラムにします。

css
/* このお店について:PCは左テキスト・右写真の2カラム */
.about {
  display: flex;
  align-items: center;
  gap: 48px;
  padding: 96px 24px;
  max-width: 1080px;
  margin-inline: auto;
}

.about-text {
  flex: 1;
}

.about-text h2 {
  font-size: 32px;
  margin-bottom: 24px;
}

.about-text p {
  margin-bottom: 16px;
}

.about-photo {
  flex: 1;
}

.about-photo img {
  width: 100%;
  height: 440px;
  object-fit: cover;
}

max-width: 1080pxmargin-inline: auto は、中央寄せの定番の2行です。上限を決めてから左右の余白を自動で等分するので、画面がどれだけ広くても文章が横に伸びきりません。

flex: 1 を2つの子に同じ値で付けると、残り幅を1対1で分け合います。文章側と写真側が半々になるのはこの1行です。片方を flex: 2 にすれば2対1になります。padding: 96px 24px の96pxがセクションどうしの間隔で、「文字より余白のほうが多い」見た目にするために思い切って広く取っています。

ここまでで OK:写真の左下に白い文字が重なり、その下が「左に文章・右に写真」の2カラムになっている状態。

CSS:メニューと店内を並べる

カード3枚を横に並べます。

css
/* メニュー:3枚を横並びのカードで */
.menu {
  padding: 96px 24px;
  max-width: 1080px;
  margin-inline: auto;
  text-align: center;
}

.menu h2 {
  font-size: 32px;
  margin-bottom: 16px;
}

.menu-lead {
  color: #8a7f72;
  margin-bottom: 48px;
}

.menu .cards {
  display: flex;
  justify-content: center;
  gap: 24px;
  text-align: left;
}

.menu .card {
  flex: 1;
  background: #ffffff;
  border: 1px solid #e3dcd0;
}

.menu .card img {
  width: 100%;
  height: 220px;
  object-fit: cover;
}

.menu .card h3 {
  font-size: 20px;
  padding: 24px 24px 8px;
}

.menu .card p {
  padding: 0 24px;
  color: #6b6155;
}

.menu .card .price {
  font-size: 18px;
  color: #b98a56;
  padding: 8px 24px 24px;
}

.menu-extra {
  margin-top: 48px;
  color: #6b6155;
}

.menutext-align: center を付けて見出しと補足を中央寄せにし、.menu .cardstext-align: left に戻しています。文字揃えは親から子へ受け継がれるので、カードの中まで中央に寄らないよう、途中で戻す指定が要ります。

カード3枚が均等な幅になるのは、さきほどと同じ flex: 1 です。.menu .card imgheight: 220pxobject-fit: cover で、元の写真の比率がばらついていても3枚とも同じ高さの帯になります。カード内の padding は、白い面の端まで文字が届かないようにするためのもので、値が3つのときは上・左右・下の順です。

次に、店内の写真3枚です。

css
/* 店内:写真3枚を横並び */
.gallery {
  padding: 96px 24px;
  max-width: 1080px;
  margin-inline: auto;
}

.gallery h2 {
  font-size: 32px;
  margin-bottom: 16px;
}

.gallery > p {
  margin-bottom: 48px;
  max-width: 640px;
}

.gallery .photos {
  display: flex;
  gap: 24px;
}

.gallery .photos figure {
  flex: 1;
}

.gallery .photos img {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.gallery .photos figcaption {
  margin-top: 12px;
  font-size: 14px;
  color: #6b6155;
}

.gallery > p>直接の子だけを指す記号です。スペース区切りの .gallery p だと <figcaption> まで巻き込むので、リード文1つだけに掛けたいときは > で範囲を狭めます。

その max-width: 640px は、1行が長すぎると目が次の行の頭を見失うために付けています。日本語では1行40字前後が読みやすい範囲で、640pxがだいたいそれにあたります。

ここまでで OK:メニューのカード3枚と、店内の写真3枚が、それぞれ横一列に並んでいる状態。

CSS:アクセスとフッターを整える

表とフッターで、CSSの本体は最後です。

css
/* アクセス・営業時間:表で書く */
.access {
  padding: 96px 24px;
  max-width: 1080px;
  margin-inline: auto;
}

.access h2 {
  font-size: 32px;
  margin-bottom: 24px;
}

.access table {
  width: 100%;
  border-collapse: collapse;
  background: #ffffff;
}

.access table th,
.access table td {
  text-align: left;
  padding: 16px 24px;
  border-bottom: 1px solid #e3dcd0;
  font-weight: 400;
}

.access table th {
  width: 160px;
  color: #b98a56;
}

.access-note {
  margin-top: 24px;
  color: #6b6155;
}

border-collapse: collapse は、マスとマスの間の線を1本に重ねる指定です。これが無いと隣り合うマスの枠線が2本並んで罫線が太くなります。表を書いたらほぼ必ず付ける1行です。

<th> は既定で太字・中央揃えなので、font-weight: 400text-align: left で打ち消しています。width: 160px<th> に付けると、1行目の幅がそのまま全部の行に適用されるので、項目名の列が揃います。

最後にフッターです。

css
/* フッター */
footer {
  padding: 48px 24px;
  background: #2e2a26;
  color: #f7f3ec;
  text-align: center;
}

.footer-name {
  font-family: "Zen Old Mincho", serif;
  font-size: 18px;
  margin-bottom: 16px;
}

footer p {
  font-size: 14px;
  margin-bottom: 8px;
}

.footer-note {
  margin-top: 16px;
  font-size: 12px;
  color: #a89f92;
}

背景と文字の色が、ページ本体と入れ替わっています。同じ2色の濃淡をひっくり返すだけで、色を足さずに「ここで終わり」を示せます。この店で使っている色は、文字の #2e2a26・差し色の #b98a56・背景の #f7f3ec と白だけです。色数を増やさないほうが、落ち着いた印象は簡単に作れます。

ここまでで OK:1440px幅のブラウザで、完成イメージとほぼ同じ見た目になっている状態。ここで一度、完成イメージのPNGと自分の画面を並べて見比べてください。

768px以下に落とす

いまのCSSは広い画面を前提に書いています。狭いときの上書きを、ファイルの一番下に足します。上書きなので、必ず一番下に書きます。

css
/* 768px以下:1カラムに落とす */
@media (max-width: 768px) {
  header {
    padding: 16px 16px;
    flex-wrap: wrap;
  }

  .logo-en {
    font-size: 18px;
  }

  .logo-jp {
    font-size: 11px;
  }

  .nav ul {
    gap: 16px;
  }

  .hero img {
    height: 560px;
  }

  .hero-text {
    padding: 16px 16px 32px;
  }

  .hero-text h1 {
    font-size: 24px;
  }

  .about {
    flex-direction: column;
    padding: 56px 16px;
    gap: 24px;
  }

  .about-text h2 {
    font-size: 24px;
  }

  .about-photo img {
    height: 320px;
  }

  .menu {
    padding: 56px 16px;
  }

  .menu h2 {
    font-size: 24px;
  }

  .menu .cards {
    flex-direction: column;
  }

  .gallery {
    padding: 56px 16px;
  }

  .gallery h2 {
    font-size: 24px;
  }

  .gallery .photos {
    flex-direction: column;
  }

  .access {
    padding: 56px 16px;
  }

  .access h2 {
    font-size: 24px;
  }

  .access table th {
    width: 110px;
    padding: 12px 16px;
  }

  .access table td {
    padding: 12px 16px;
  }
}

@media (max-width: 768px) { ... }メディアクエリ(画面の条件によってCSSを切り替える仕組み)で、「画面幅が768px以下のときだけ、この中を適用する」という意味です。書き方は上のCSSと同じで、変えたいところだけを書きます。書かなかったものは上の指定が残ります。

やっていることは3種類だけです。flex-direction: column で横並びを縦積みに変える.about .menu .cards .gallery .photos)、余白を96px→56px・24px→16pxに詰める見出しを32px→24pxに小さくする。この3つでレイアウトはスマートフォンで成立します。ヘッダーの flex-wrap: wrap は、並びきらない子要素を次の行へ折り返す指定で、ハンバーガーメニューの代わりにナビを折り返しています(開閉するメニューは02で作ります)。

ヒーローの写真は、HTMLの <picture> が768px以下で hero-sp.webp に切り替えてくれるので、CSSでは高さを560pxに伸ばすだけです。同じ「切り替え」でも、画像の差し替えはHTML、寸法はCSSと役割が分かれています。

ブラウザの幅をゆっくり狭めて、768pxをまたぐ瞬間を見てください。段組みが一斉に縦へ積み上がります。それから390pxまで狭めて、横スクロールバーが出ないことを確かめます。出るときは、どこかに画面より広い要素があります。

ここまでで OK:390px幅で、写真が縦位置に変わり、全部のセクションが1カラムに積み上がっている状態。

AIには聞くだけにする

このレッスンでは、コードを1行もAIに書かせません。代わりに、聞き方を3つ練習します。 身につけるのは「正しい答えを引き出す方法」ではなく、自分の手元に判断材料を増やす頼み方です。

Q1 効かないCSSの原因を聞く

「書いたはずのCSSが効かない」はこの先ずっと出てきます。そのとき最初に投げるのがこれです。

text
いま作っているHTML/CSSで、思ったとおりに表示されません。
コードは直さないでください。原因の候補を、可能性の高い順に3つまで挙げてください。
それぞれについて、DevToolsのどこを見れば確かめられるかを1行で教えてください。

【症状】
ヒーローの見出しを中央に寄せたいのに、左に寄ったままです。

【該当のHTML】
<該当部分だけを貼る>

【該当のCSS】
<該当部分だけを貼る>

< > で囲んだところは、自分のファイルから該当部分だけを切り出して貼り替えます。全文は貼りません。 貼る範囲を選ぶ作業そのものが、原因の見当をつける練習になります。

疑うポイント

  • 候補のうち、DevToolsで確かめられないものは無視してよい
  • 「たぶん」と書いてある候補はまだ推測。自分で1つずつ潰す
  • 直させない。直させると、次に同じ症状が出たとき自分で解けない

Q2 用語の意味を聞く

このレッスンで一番聞かれるのが box-sizing です。説明を読むより、手元で幅が変わるのを見たほうが早く分かります。

text
CSSの「box-sizing: border-box」が何をしているのか、
padding を足したときに何が変わるかを、コード例2つ(border-box あり/なし)で説明してください。
説明は日本語で、300字以内でお願いします。

疑うポイント

  • 例のコードを自分のファイルに貼って、実際に幅が変わることを目で見る
  • 説明が長いときは「短く」と言い直す。読まない説明は意味がない

Q3 自分の書いたHTMLが妥当かを聞く

書き終わったHTMLについて、タグの選び方だけを見てもらいます。

text
下のHTMLについて、タグの選び方だけを見てください。CSSとコードの修正は不要です。
「この部分は別のタグのほうがよい」という箇所を、理由つきで3つまで挙げてください。

<index.html の body の中身>

疑うポイント

  • 指摘が3つ以上出たら、上から3つだけ直す
  • div を全部 section にせよ、のような機械的な指摘は従わなくてよい
  • 直した後にもう一度 html-validate をかける

3つとも「直さないでください」「〜だけ見てください」と範囲を切っているのが共通点です。切らないと、AIはたいてい全部を書き直した完成品を返します。それは動くかもしれませんが、あなたのファイルとどこが違うのかが分からなくなります。

ここまでで OK:3つのうち少なくとも1つを実際に投げて、返ってきた内容を自分で確かめた状態。

検品する

このレッスンで新しく足す検品は1つ、HTMLの構文検査です。index.html があるフォルダで実行します。

検品

bash
npx -y html-validate index.html

合格の見え方:何も表示されずに、次の入力待ちに戻る。エラーがあるときだけ、行番号と理由が出ます。

エラーが出たら、左端の数字が行番号です。よく出るのは、閉じ忘れ・<h1> が2つ・alt の抜けの3つ。どれもブラウザでは一見ふつうに表示されてしまうので、目で見つけるのは無理だと思ってください。この1行が代わりに見つけてくれます。

コマンドが通ったら、残りを自分の目で確かめます。全部にチェックが付いたら完成です。

  • npx -y html-validate index.html が error 0
  • 1440×900 のスクリーンショットが、完成イメージのPNGと見比べて崩れていない
  • 390×844 のスクリーンショットで横スクロールバーが出ない
  • h1 がページに1つだけ
  • すべての <img> に意味のある alt が入っている(装飾は alt=""
  • 電話番号が tel: リンクになっていて、スマートフォンで押すと発信画面が出る
  • file:// で開いても表示が崩れない(外部CSSは相対パス)
  • ページの合計サイズが 3MB 以内

最後の項目は、images/ とHTML・CSSを合わせた大きさです。3MBという上限は、この教材の決めです。手元の完成コードで測ったところ、画像9枚込みで約0.63MBでした。写真を自分のものに差し替えると跳ね上がりやすいので、そのときは測り直してください。

補足

4〜6時間かかるのは、あなたが遅いからではありません。上から下まで手で書けば、それくらいかかります。ここで打った量が、02以降でAIの出力を読む速さになって戻ってきます。大丈夫、まだ間に合います。

ここまでで OK:検品コマンドが何も言わずに戻り、8項目すべてにチェックが付いた状態。

次の回へ

02では工務店の3ページサイトを作ります。最初にやるのは、いま書いたこのCSSを組み替える作業です。.nav ul li a のような子孫セレクタをクラス名ひとつで指す書き方(BEM)に変え、何十か所にも出てくる 24px#2e2a26 を1か所にまとめます(CSS変数)。同じ見た目のまま、直しやすさだけが変わります。

そして02から、いよいよAIにコードを書かせます。任せるのは部品3つだけで、残り7割はまだ手です。今日1ページを全部書いた手が、そのまま出てきたコードの検品に使えます。

もっと聞きたいことがあれば、琥珀の公式LINEで質問できます。

琥珀の公式LINEで質問する(無料)