Sebuah toko membutuhkan satu halaman yang memamerkan produknya: nama barang, gambar, deskripsi singkat, dan harga. Halaman semacam itu disebut katalog produk. Proyek ini merangkai materi daftar, gambar, tabel, dan sedikit JavaScript dari Course HTML ini menjadi katalog utuh untuk Toko Aroma, penjual kopi dan teh milik Siti.

Hasil Akhir yang Dibangun #

Target akhirnya adalah katalog Toko Aroma berisi empat produk dan satu filter kategori. Fitur yang kita bangun secara bertahap:

  • Dokumen valid dengan metadata lengkap (charset, viewport, title, description).
  • Kerangka header, main, dan footer dengan daftar ul berisi kartu produk.
  • Setiap kartu menampilkan gambar SVG beratribut alt, width, height, deskripsi, serta berat dan harga di dalam dl.
  • Tampilan rapi dari styles/style.css: custom properties palet cokelat-krem dan deretan kartu flex.
  • Filter kategori Kopi/Teh yang menyembunyikan kartu lewat atribut hidden dan tombol ber-aria-pressed.

Prasyarat #

Proyek ini merangkai konsep dari section sebelumnya; baca dahulu bila materinya masih asing:

  • Section Lists dan Semantic HTML: ul, li, header, main, footer.
  • Section Images dan Media: img dengan alt, width, dan height.
  • Section Elemen dan Atribut: atribut kustom data-*.
  • Section HTML dan CSS: selector, custom properties, dan flexbox.
  • Section HTML dan JavaScript serta Interactive Elements: addEventListener dan aria-pressed.

Empat Bagian Proyek #

Kerjakan keempat bagian berurutan; setiap bagian memakai hasil bagian sebelumnya:

BagianFokus
Bagian 1: Menyusun Struktur KatalogKerangka valid dan daftar produk
Bagian 2: Gambar dan Deskripsiimg, deskripsi, harga dl, data-kategori
Bagian 3: Styling Dasarcustom properties, kartu, deretan flex
Bagian 4: Filter SederhanaTombol kategori, aria-pressed, JavaScript

Memakai File Pendukung #

Tiga folder proyek menyimpan berkas pendukung. Susun salinan lokalnya mengikuti pohon berikut:

text
katalog-produk/
├── assets/
│   ├── kopi.svg
│   └── teh.svg
├── styles/
│   └── style.css
├── tests/
│   └── cek-struktur.html
├── part-1-struktur-katalog/
│   └── katalog.html
├── part-2-gambar-dan-deskripsi/
│   └── katalog-lengkap.html
├── part-3-styling-dasar/
│   └── katalog-styling.html
└── part-4-filter-sederhana/
    ├── katalog-filter.html
    └── filter.js
  • assets/kopi.svg dan teh.svg: gambar ilustrasi kartu produk.
  • styles/style.css: stylesheet final dari Bagian 3, dipakai Bagian 4.
  • tests/cek-struktur.html: checklist sepuluh butir; kemajuan tersimpan di localStorage.

Unduh contoh tiap bagian lewat tautan pada header code block, lalu letakkan sesuai pohon di atas agar jalur relatif seperti ../assets/kopi.svg dan ../styles/style.css tetap benar.

Referensi resmi untuk pendalaman:

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

styles/style.css

Unduh berkas
css
/* Stylesheet proyek Katalog Produk - ditulis pada Bagian 3, dipakai Bagian 3 dan 4 */
:root {
  --warna-utama: #6f4e37;
  --warna-utama-tua: #53392a;
  --warna-teks: #1f2937;
  --warna-latar: #f4efe7;
  --warna-putih: #ffffff;
  --warna-garis: #e3d9cb;
  --jarak: 16px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  color: var(--warna-teks);
  background-color: var(--warna-latar);
}

header {
  background-color: var(--warna-utama);
  color: var(--warna-putih);
  padding: var(--jarak);
}

header h1 {
  margin: 0;
}

header p {
  margin: 4px 0 0;
}

main {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--jarak);
}

.filter {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: var(--jarak);
}

.filter button {
  padding: 8px 18px;
  border: 1px solid var(--warna-utama);
  border-radius: 999px;
  font-size: 1rem;
  background-color: var(--warna-putih);
  color: var(--warna-utama);
  cursor: pointer;
}

.filter button[aria-pressed="true"] {
  background-color: var(--warna-utama);
  color: var(--warna-putih);
}

.katalog {
  display: flex;
  flex-wrap: wrap;
  gap: var(--jarak);
  margin: 0 0 var(--jarak);
  padding: 0;
  list-style: none;
}

.kartu {
  width: 230px;
  padding: var(--jarak);
  background-color: var(--warna-putih);
  border: 1px solid var(--warna-garis);
  border-radius: 8px;
}

.kartu img {
  display: block;
  margin: 0 auto var(--jarak);
  max-width: 100%;
  height: auto;
}

.kartu h2 {
  margin: 0 0 8px;
  font-size: 1.1rem;
  color: var(--warna-utama-tua);
}

.kartu p {
  margin: 0 0 var(--jarak);
  font-size: 0.95rem;
}

.kartu dl {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 10px;
  margin: 0;
  font-size: 0.9rem;
}

.kartu dt {
  color: #6b7280;
}

.kartu dd {
  margin: 0;
  font-weight: bold;
}

.kartu[hidden] {
  display: none;
}

footer {
  background-color: var(--warna-utama);
  color: var(--warna-putih);
}

footer p {
  margin: 0;
  padding: var(--jarak);
  text-align: center;
}

tests/cek-struktur.html

Unduh berkas
html
<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Checklist Pemeriksaan Katalog Produk</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      max-width: 720px;
      margin: 0 auto;
      padding: 16px;
      line-height: 1.6;
      color: #1f2937;
    }
    h1 {
      font-size: 1.4rem;
    }
    ul {
      padding-left: 20px;
    }
    li {
      margin-bottom: 8px;
    }
    #progres {
      width: 100%;
      height: 16px;
    }
  </style>
</head>
<body>
  <header>
    <img src="../assets/kopi.svg" alt="Ilustrasi cangkir kopi" width="48" height="48">
    <h1>Checklist Pemeriksaan Katalog Produk</h1>
  </header>
  <p>Gunakan halaman ini untuk memeriksa hasil setiap Bagian proyek. Centang butir yang telah dipenuhi; kemajuan tersimpan otomatis di browser.</p>
  <p id="ringkasan" aria-live="polite">0 dari 10 butir terpenuhi.</p>
  <progress id="progres" value="0" max="10"></progress>
  <ul>
    <li><label><input type="checkbox" data-cek="1"> Dokumen dimulai dengan <code>doctype</code> dan <code>html lang="id"</code></label></li>
    <li><label><input type="checkbox" data-cek="2"> Head memuat <code>meta charset</code>, <code>viewport</code>, <code>title</code>, dan <code>description</code></label></li>
    <li><label><input type="checkbox" data-cek="3"> Header, main, dan footer membentuk kerangka halaman</label></li>
    <li><label><input type="checkbox" data-cek="4"> Produk tersusun di dalam <code>ul</code> dengan satu <code>li</code> per kartu</label></li>
    <li><label><input type="checkbox" data-cek="5"> Setiap gambar produk memakai <code>img</code> dengan <code>alt</code>, <code>width</code>, dan <code>height</code></label></li>
    <li><label><input type="checkbox" data-cek="6"> Berat dan harga tiap produk berpasangan dalam <code>dl</code>, <code>dt</code>, dan <code>dd</code></label></li>
    <li><label><input type="checkbox" data-cek="7"> Setiap kartu membawa atribut <code>data-kategori</code> yang sesuai</label></li>
    <li><label><input type="checkbox" data-cek="8"> Halaman terhubung ke stylesheet <code>styles/style.css</code></label></li>
    <li><label><input type="checkbox" data-cek="9"> Tombol filter memakai <code>button</code> dengan <code>aria-pressed</code> dan <code>data-kategori</code></label></li>
    <li><label><input type="checkbox" data-cek="10"> Filter menyembunyikan kartu lewat atribut <code>hidden</code> tanpa menghalangi keyboard</label></li>
  </ul>
  <p>Sintaks seluruh file proyek juga dapat diperiksa dengan Nu Html Checker di <a href="https://validator.w3.org/nu/">validator.w3.org/nu</a>.</p>
  <script>
    const daftar = document.querySelectorAll("input[type='checkbox']");
    const progres = document.querySelector("#progres");
    const ringkasan = document.querySelector("#ringkasan");
    const KUNCI = "cek-katalog-produk";

    function simpan() {
      const status = [];
      daftar.forEach(function (butir) {
        status.push(butir.checked);
      });
      try {
        localStorage.setItem(KUNCI, JSON.stringify(status));
      } catch (galat) {
        // penyimpanan tidak tersedia; status tetap berlaku selama halaman terbuka
      }
    }

    function muat() {
      try {
        const status = JSON.parse(localStorage.getItem(KUNCI));
        if (Array.isArray(status)) {
          daftar.forEach(function (butir, indeks) {
            butir.checked = status[indeks] === true;
          });
        }
      } catch (galat) {
        // biarkan semua butir pada keadaan awalnya
      }
    }

    function perbarui() {
      let terisi = 0;
      daftar.forEach(function (butir) {
        if (butir.checked) {
          terisi += 1;
        }
      });
      progres.value = terisi;
      ringkasan.textContent = terisi + " dari " + daftar.length + " butir terpenuhi.";
    }

    muat();
    perbarui();
    daftar.forEach(function (butir) {
      butir.addEventListener("change", function () {
        perbarui();
        simpan();
      });
    });
  </script>
</body>
</html>