Seluruh chapter pada Course HTML ini mengajarkan potongan-potongan pengetahuan: satu chapter membahas elemen article, chapter lain membahas metadata atau stylesheet. Proyek ini menjadi tempat kita merangkai potongan tersebut menjadi satu karya utuh yang layak tayang. Format artikel dipilih karena merupakan jenis halaman web yang paling umum dijumpai, mulai dari blog pribadi hingga portal berita, sehingga keputusan struktur yang kita latih di sini langsung berguna pada pekerjaan nyata.

Hasil akhir proyek adalah satu halaman artikel bertema kebun rumah bernama Aneka Baca dengan fitur berikut:

  • Struktur artikel semantik: header, section, figure dengan figcaption, dan footer berisi penulis serta tanggal terbit.
  • Navigasi antarartikel beserta metadata head yang lengkap: description, canonical, dan Open Graph.
  • Stylesheet eksternal dengan token warna dan tipografi yang nyaman dibaca.
  • Daftar isi berbasis details dan tombol bookmark yang tersimpan melalui localStorage.

Proyek ini menuntut penguasaan beberapa section sebelumnya. Kerjakan bila kita telah melewati: Semantic HTML, Links dan Navigasi, Metadata dan SEO, HTML dan CSS, HTML dan JavaScript, serta Validation dan Code Quality. Konsep yang belum familiar sebaiknya dirujuk kembali pada chapter asalnya saat diperlukan.

Proyek dibagi menjadi empat bagian, berurutan dari kerangka sampai interaksi:

BagianFokusHasil tahap ini
Bagian 1Struktur artikel semantikFile artikel.html berisi satu artikel utuh
Bagian 2Navigasi dan metadata headLink antarartikel serta description, canonical, Open Graph
Bagian 3Styling dasarFile styles/style.css untuk tipografi artikel
Bagian 4Peningkatan interaksiDaftar isi details dan tombol bookmark localStorage

Tiga folder pendukung menemani keempat bagian tersebut. Folder assets/ menyimpan gambar ilustrasi hero.svg yang dipakai pada figure artikel. Folder styles/ menyimpan style.css yang mulai ditulis pada Bagian 3 dan dipakai halaman artikel sampai akhir proyek. Folder tests/ menyimpan cek-struktur.html, sebuah halaman checklist mandiri dengan checkbox dan progres yang tersimpan otomatis; centang butir-butirnya setiap kali satu bagian tuntas. Struktur folder proyek tampak seperti berikut:

text
website-artikel/
├── bagian-1/
│   └── artikel.html
├── bagian-2/
│   └── artikel-navigasi.html
├── bagian-4/
│   ├── interaksi-artikel.html
│   └── interaksi-artikel.js
├── assets/
│   └── hero.svg
├── styles/
│   └── style.css
└── tests/
    └── cek-struktur.html

File contoh pada setiap bagian berada satu tingkat di bawah akar proyek, sehingga rujukan ke aset bersama memakai awalan ../ — misalnya ../styles/style.css dan ../assets/hero.svg. Dengan struktur ini, setiap file contoh dapat diunduh dari halaman bagiannya lalu langsung bekerja tanpa penyesuaian path.

Berkas Sumber

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

styles/style.css

Unduh berkas
css
/* styles/style.css — Aneka Baca
   Stylesheet bersama proyek Website Artikel.
   Ditulis pada Bagian 3, dipakai seluruh halaman sampai akhir proyek. */

/* Token desain: ubah nilai di sini, seluruh halaman ikut menyesuaikan. */
:root {
  --warna-teks: #26312b;
  --warna-teks-lembut: #5c6b62;
  --warna-latar: #faf7f1;
  --warna-aksen: #1a6b54;
  --warna-garis: #ddd5c7;
  --lebar-baca: 65ch;
  --font-artikel: Georgia, "Times New Roman", serif;
  --font-antarmuka: "Segoe UI", Arial, sans-serif;
}

body {
  margin: 0;
  background-color: var(--warna-latar);
  color: var(--warna-teks);
  font-family: var(--font-artikel);
  line-height: 1.7;
}

nav {
  max-width: var(--lebar-baca);
  margin-inline: auto;
  padding: 1rem 1rem 0;
}

nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.25rem;
  font-family: var(--font-antarmuka);
  font-size: 0.95rem;
}

nav a {
  color: var(--warna-aksen);
}

article {
  max-width: var(--lebar-baca);
  margin-inline: auto;
  padding: 1.5rem 1rem 3rem;
}

h1,
h2 {
  font-family: var(--font-antarmuka);
  line-height: 1.25;
}

h1 {
  font-size: 2rem;
}

.lead {
  font-size: 1.125rem;
  color: var(--warna-teks-lembut);
}

figure {
  margin: 2rem 0;
}

figure img {
  max-width: 100%;
  height: auto;
  border-radius: 8px;
}

figcaption {
  margin-top: 0.5rem;
  font-size: 0.9rem;
  color: var(--warna-teks-lembut);
}

article > footer {
  margin-top: 2.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--warna-garis);
  font-size: 0.95rem;
  color: var(--warna-teks-lembut);
}

details {
  border: 1px solid var(--warna-garis);
  border-radius: 8px;
  padding: 0.75rem 1rem;
  margin: 0 0 2rem;
  font-family: var(--font-antarmuka);
}

details ul {
  margin: 0.5rem 0 0.25rem;
  padding-left: 1.25rem;
}

#tombol-bookmark {
  font-family: var(--font-antarmuka);
  font-size: 1rem;
  background-color: var(--warna-aksen);
  color: #ffffff;
  border: 0;
  border-radius: 999px;
  padding: 0.5rem 1.25rem;
  cursor: pointer;
}

#tombol-bookmark[aria-pressed="true"] {
  background-color: var(--warna-garis);
  color: var(--warna-teks);
}

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>Cek Struktur — Proyek Website Artikel</title>
  <link rel="stylesheet" href="../styles/style.css">
  <script src="cek-struktur.js" defer></script>
  <style>
    .cek {
      list-style: none;
      padding-left: 0;
    }

    .cek li {
      margin-bottom: 0.5rem;
    }

    .cek input {
      margin-right: 0.5rem;
    }

    #progres {
      font-weight: 700;
    }
  </style>
</head>
<body>
  <main>
    <header>
      <h1>Cek Struktur Proyek Website Artikel</h1>
      <p>Halaman ini membantu memeriksa hasil setiap Bagian proyek Aneka Baca secara mandiri. Centang butir yang terpenuhi; progres tersimpan otomatis di browser dan tidak perlu dicentang ulang.</p>
      <p id="progres">0 dari 14 butir terpenuhi</p>
    </header>

    <fieldset>
      <legend>Struktur semantik (Bagian 1)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-s1" data-cek="s1"><label for="cek-s1">Dokumen memakai doctype, <code>html lang="id"</code>, <code>meta charset</code>, dan <code>title</code> gabungan judul artikel plus nama situs</label></li>
        <li><input type="checkbox" id="cek-s2" data-cek="s2"><label for="cek-s2">Seluruh konten dibungkus satu elemen <code>article</code></label></li>
        <li><input type="checkbox" id="cek-s3" data-cek="s3"><label for="cek-s3">Artikel memakai <code>header</code> berisi satu <code>h1</code> dan paragraf lead berclass</label></li>
        <li><input type="checkbox" id="cek-s4" data-cek="s4"><label for="cek-s4">Gambar berada di dalam <code>figure</code> dengan <code>figcaption</code> berbahasa Indonesia</label></li>
        <li><input type="checkbox" id="cek-s5" data-cek="s5"><label for="cek-s5">Setiap bagian isi memakai <code>section</code> dengan <code>h2</code> sendiri</label></li>
        <li><input type="checkbox" id="cek-s6" data-cek="s6"><label for="cek-s6">Footer artikel berisi penulis dan <code>time</code> dengan atribut <code>datetime</code> format YYYY-MM-DD</label></li>
        <li><input type="checkbox" id="cek-s7" data-cek="s7"><label for="cek-s7">Nav berisi daftar tautan dengan <code>aria-label</code> pembeda</label></li>
      </ul>
    </fieldset>

    <fieldset>
      <legend>Metadata head (Bagian 2)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-m1" data-cek="m1"><label for="cek-m1"><code>meta name="description"</code> berisi ringkasan satu-dua kalimat</label></li>
        <li><input type="checkbox" id="cek-m2" data-cek="m2"><label for="cek-m2"><code>link rel="canonical"</code> menunjuk alamat resmi halaman</label></li>
        <li><input type="checkbox" id="cek-m3" data-cek="m3"><label for="cek-m3"><code>og:title</code> dan <code>og:description</code> terisi dengan nilai berbeda dari kosong</label></li>
      </ul>
    </fieldset>

    <fieldset>
      <legend>File pendukung (Bagian 3)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-f1" data-cek="f1"><label for="cek-f1">File <code>assets/hero.svg</code> tersedia dan tampil pada halaman artikel</label></li>
        <li><input type="checkbox" id="cek-f2" data-cek="f2"><label for="cek-f2">File <code>styles/style.css</code> terhubung lewat <code>link</code> dengan path <code>../styles/style.css</code></label></li>
        <li><input type="checkbox" id="cek-f3" data-cek="f3"><label for="cek-f3">Warna dan jenis huruf diatur lewat token <code>:root</code>, bukan nilai lepas di tiap aturan</label></li>
      </ul>
    </fieldset>

    <fieldset>
      <legend>Interaksi (Bagian 4)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-i1" data-cek="i1"><label for="cek-i1">Daftar isi <code>details</code> tampil, dapat dibuka, dan tautannya melompat ke section terkait</label></li>
        <li><input type="checkbox" id="cek-i2" data-cek="i2"><label for="cek-i2">Tombol bookmark mengubah label dan <code>aria-pressed</code> saat diklik</label></li>
        <li><input type="checkbox" id="cek-i3" data-cek="i3"><label for="cek-i3">Status bookmark bertahan setelah halaman dimuat ulang</label></li>
      </ul>
    </fieldset>

    <p><img src="../assets/hero.svg" alt="" width="112" height="63"></p>
    <p>Halaman pemeriksaan ini juga memakai stylesheet proyek dan gambar aset melalui path relatif <code>../</code>, sekaligus menjadi contoh rujukan lintas folder.</p>
  </main>
</body>
</html>

tests/cek-struktur.js

Unduh berkas
javascript
// cek-struktur.js — checklist mandiri proyek dengan progres tersimpan.
// Digunakan bersama cek-struktur.html pada folder tests/ proyek.

const KUNCI_SIMPAN = "cek:website-artikel";

function bacaTersimpan() {
  try {
    return JSON.parse(localStorage.getItem(KUNCI_SIMPAN)) || {};
  } catch (galat) {
    return {};
  }
}

function tulisTersimpan(nilai) {
  try {
    localStorage.setItem(KUNCI_SIMPAN, JSON.stringify(nilai));
  } catch (galat) {
    // Penyimpanan tidak tersedia; checklist tetap berfungsi tanpa persist.
  }
}

function perbaruiProgres() {
  const kotak = Array.from(document.querySelectorAll("[data-cek]"));
  const terpenuhi = kotak.filter(function (item) {
    return item.checked;
  }).length;
  const progres = document.querySelector("#progres");
  if (progres) {
    progres.textContent = terpenuhi + " dari " + kotak.length + " butir terpenuhi";
  }
}

function pasang() {
  const tersimpan = bacaTersimpan();
  const kotak = Array.from(document.querySelectorAll("[data-cek]"));
  kotak.forEach(function (item) {
    item.checked = tersimpan[item.dataset.cek] === true;
    item.addEventListener("change", function () {
      const nilai = bacaTersimpan();
      nilai[item.dataset.cek] = item.checked;
      tulisTersimpan(nilai);
      perbaruiProgres();
    });
  });
  perbaruiProgres();
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", pasang);
} else {
  pasang();
}