Delapan kartu dari Bagian 2 masih menumpuk vertikal, dan cara lama menata ulangnya adalah menulis tiga media query: empat kolom di layar lebar, tiga di tablet, dua di ponsel. Bagian 3 mengganti semuanya dengan satu deklarasi. CSS Grid mampu menghitung berapa kolom yang muat pada lebar yang tersedia, lalu menghitung ulang setiap kali lebar berubah — tanpa satu pun breakpoint. Bagian ini juga menambahkan baris info hasil di atas grid, yang angkanya nanti dihidupkan oleh JavaScript di Bagian 5.
Kolom Otomatis dalam Satu Baris #
Berikut keseluruhan aturan yang dibutuhkan:
.grid-produk {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: var(--jarak-5);
}Uraian baris per baris: repeat(auto-fit, minmax(210px, 1fr)) dibaca dari dalam ke luar. minmax(210px, 1fr) berarti setiap kolom berlebar minimum 210 px dan boleh melebar hingga mendapat bagian 1fr dari sisa ruang. auto-fit menyuruh browser menumpangkan sebanyak mungkin kolom 210 px ke lebar yang tersedia — di konten 1056 px hasilnya empat kolom, di tablet tiga atau dua, di ponsel satu. Saat kartu tidak lagi muat, kolom terakhir turun ke baris baru secara otomatis.
Bedakan dengan saudaranya auto-fill: keduanya menghitung kolom dengan cara sama, tetapi auto-fill tetap menyisakan trek kosong bila kartu kurang, sedangkan auto-fit melipat trek kosong dan membagi ruangnya ke kartu yang ada. Pilihan auto-fit penting untuk proyek ini: begitu Bagian 5 menyembunyikan kartu dengan display: none, kartu yang tersisa langsung mengisi ulang baris tanpa kode tambahan. Nilai 210 px sendiri dipilih setelah menguji: cukup sempit agar empat kolom muat di lebar konten, cukup lebar agar nama tanaman tidak patah terlalu banyak.
Jarak dan Info Hasil #
gap: var(--jarak-5) memberi jarak 24 px sekaligus untuk baris dan kolom — grid tidak membutuhkan margin per kartu, dan jarak terakhir di tepi tetap rapi karena gap hanya berada di antara trek. Satu token menjaga ritme ini konsisten dengan sisa halaman. Pendekatan ini juga lebih bersih daripada pola kartu berbasis flexbox yang mengandalkan margin per anak: gap tidak menciptakan jarak ganda di tepi wadah sehingga tidak perlu dikoreksi. Bila kelak katalog diperluas, wadah tidak disentuh sama sekali — cukup tambahkan <article> baru dan grid menempatkannya pada trek berikutnya.
Di atas grid, snapshot menambahkan paragraf info hasil pada HTML:
<p class="info-hasil"><span id="hitung-katalog">8</span> tanaman ditemukan</p>.info-hasil hanya mengatur margin bawah, ukuran 14 px, dan warna teks lembut; angka 8 di dalam <span id="hitung-katalog"> masih statis. Nilai id itulah pegangan JavaScript di Bagian 5: setiap kali filter dijalankan, jumlah kartu yang terlihat ditulis ulang ke span tersebut. Menyiapkan pegangannya sekarang berarti Bagian 5 cukup menyentuh JavaScript, bukan merombak markup.
/* ============================================================
Katalog Tanaman — stylesheet utama
Urutan: token → base → kerangka halaman → layout katalog
→ tombol → kartu tanaman → grid produk → panel filter
→ state dan gerak → responsive
============================================================ */
/* ---------- 1. Design token ---------- */
:root {
/* Warna semantik tema hijau */
--warna-latar: #f3f7f2; --warna-permukaan: #ffffff;
--warna-teks: #1f2a1d; --warna-teks-lembut: #5b6b57;
--warna-garis: #d9e4d5; --warna-utama: #2f6b3a;
--warna-utama-pekat: #215029; --warna-utama-kontras: #ffffff;
--warna-utama-lembut: #e3f0e0; --warna-aksen: #b3372b;
--warna-indoor: #2e5f8a; --warna-solar: #8a5a12;
--warna-fokus: #1d4ed8;
/* Skala spasi 4/8 px */
--jarak-1: 4px; --jarak-2: 8px;
--jarak-3: 12px; --jarak-4: 16px;
--jarak-5: 24px; --jarak-6: 32px;
--jarak-7: 48px; --jarak-8: 64px;
/* Radius dan bayangan */
--radius-kecil: 6px; --radius: 12px; --radius-penuh: 999px;
--bayangan-1: 0 1px 2px rgba(31, 42, 29, 0.08);
--bayangan-2: 0 6px 16px rgba(31, 42, 29, 0.14);
/* Tipografi dan gerak */
--font-dasar: system-ui, "Segoe UI", sans-serif;
--tinggi-baris: 1.55;
--durasi-cepat: 120ms; --durasi: 200ms; --pelunakan: ease-out;
/* Kerangka halaman */
--lebar-isi: 1120px;
--lebar-bilah: 264px;
}
/* ---------- 2. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--font-dasar);
line-height: var(--tinggi-baris);
background-color: var(--warna-latar);
color: var(--warna-teks);
}
h1, h2, h3, p, figure { margin: 0; }
img { display: block; max-width: 100%; }
/* ---------- 3. Kerangka halaman ---------- */
.pembungkus { max-width: var(--lebar-isi); margin-inline: auto; padding-inline: var(--jarak-4); }
.laman-kepala { padding-block: var(--jarak-5); background-color: var(--warna-permukaan); border-bottom: 1px solid var(--warna-garis); }
.kepala-baris { display: flex; align-items: center; justify-content: space-between; gap: var(--jarak-3); flex-wrap: wrap; }
.brand { display: flex; align-items: center; gap: var(--jarak-3); }
.brand img { width: 40px; height: 40px; }
.brand-nama { font-size: 20px; font-weight: 700; }
.tagline { font-size: 14px; color: var(--warna-teks-lembut); }
.laman-utama { padding-block: var(--jarak-7) var(--jarak-8); }
.judul-halaman { font-size: 28px; font-weight: 800; letter-spacing: -0.01em; }
.pengantar { margin-top: var(--jarak-2); max-width: 62ch; color: var(--warna-teks-lembut); }
.laman-kaki { background-color: var(--warna-permukaan); border-top: 1px solid var(--warna-garis); }
.kaki-baris { display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--jarak-2) var(--jarak-4); padding-block: var(--jarak-5); font-size: 13.5px; color: var(--warna-teks-lembut); }
/* ---------- 4. Layout katalog: bilah filter dan isi ---------- */
.layout-katalog {
display: grid;
grid-template-columns: var(--lebar-bilah) minmax(0, 1fr);
gap: var(--jarak-6);
align-items: start;
margin-top: var(--jarak-6);
}
.bilah-filter {
position: sticky; top: var(--jarak-5);
display: flex; flex-direction: column;
gap: var(--jarak-4);
}
.isi-katalog { min-width: 0; }
/* ---------- 5. Tombol ---------- */
.tombol {
padding: 9px 18px; border: none; border-radius: var(--radius-kecil);
background-color: var(--warna-utama);
color: var(--warna-utama-kontras);
font: inherit; font-weight: 600; cursor: pointer;
}
/* ---------- 6. Kartu tanaman ---------- */
.kartu-tanaman {
display: flex; flex-direction: column;
overflow: hidden;
background-color: var(--warna-permukaan);
border: 1px solid var(--warna-garis); border-radius: var(--radius);
box-shadow: var(--bayangan-1);
}
.media-tanaman {
aspect-ratio: 4 / 3;
display: flex; flex-direction: column;
align-items: flex-start; gap: var(--jarak-2);
padding: var(--jarak-3);
background-color: var(--warna-utama-lembut);
}
/* Foto produk digantikan dua lapis gradasi: lapisan pertama
kilau cahaya, lapisan kedua warna khas tiap tanaman. */
.media--monstera { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.30) 0%, rgba(255, 255, 255, 0) 55%), linear-gradient(135deg, #8fce8f 0%, #2f6b3a 100%); }
.media--lidah { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0) 50%), linear-gradient(135deg, #3d7a5f 0%, #163823 100%); }
.media--aglaonema { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.30) 0%, rgba(255, 255, 255, 0) 55%), linear-gradient(135deg, #c25b5b 0%, #6b2f4a 100%); }
.media--calathea { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.28) 0%, rgba(255, 255, 255, 0) 55%), linear-gradient(135deg, #4f8a8b 0%, #274156 100%); }
.media--pakis { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.28) 0%, rgba(255, 255, 255, 0) 55%), linear-gradient(135deg, #679436 0%, #2c4a1e 100%); }
.media--bougenville { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.32) 0%, rgba(255, 255, 255, 0) 55%), linear-gradient(135deg, #d16ba5 0%, #7a2f5c 100%); }
.media--kamboja { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.40) 0%, rgba(255, 255, 255, 0) 60%), linear-gradient(135deg, #f2e3b3 0%, #d9a441 100%); }
.media--melati { background-image: linear-gradient(160deg, rgba(255, 255, 255, 0.45) 0%, rgba(255, 255, 255, 0) 60%), linear-gradient(135deg, #eef7e9 0%, #a8c98a 100%); }
.lencana {
padding: 3px 10px; border-radius: var(--radius-penuh);
font-size: 11px; font-weight: 700;
letter-spacing: 0.06em; text-transform: uppercase;
}
.lencana--indoor { background-color: var(--warna-indoor); color: #ffffff; }
.lencana--rimbu { background-color: var(--warna-utama-lembut); color: var(--warna-utama-pekat); }
.lencana--solar { background-color: #f7e3b8; color: var(--warna-solar); }
.lencana--promo { background-color: var(--warna-aksen); color: #ffffff; }
.badan-kartu { display: flex; flex-direction: column; gap: var(--jarak-2); flex: 1; padding: var(--jarak-4); }
.nama-tanaman { font-size: 17px; font-weight: 700; line-height: 1.3; }
.keterangan { font-size: 13.5px; color: var(--warna-teks-lembut); }
.baris-beli {
display: flex; align-items: center;
justify-content: space-between; gap: var(--jarak-2);
margin-top: auto; padding-top: var(--jarak-2);
}
.harga { font-size: 16px; font-weight: 700; color: var(--warna-utama-pekat); }
/* ---------- 7. Grid produk ---------- */
.info-hasil { margin-bottom: var(--jarak-4); font-size: 14px; color: var(--warna-teks-lembut); }
.grid-produk {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: var(--jarak-5);
}<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Katalog Tanaman</title>
<link rel="icon" href="assets/logo.svg" type="image/svg+xml">
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<header class="laman-kepala">
<div class="pembungkus kepala-baris">
<div class="brand">
<img src="assets/logo.svg" alt="Logo Katalog Tanaman" width="40" height="40">
<span class="brand-nama">Katalog Tanaman</span>
</div>
<p class="tagline">Rumah tanam mini untuk semua rumah</p>
</div>
</header>
<main class="laman-utama">
<div class="pembungkus">
<h1 class="judul-halaman">Katalog tanaman hias</h1>
<p class="pengantar">Delapan tanaman pilihan untuk memulai koleksi: dari penghuni sudut ruangan sampai penyuka matahari di teras.</p>
<section class="isi-katalog" aria-label="Daftar tanaman">
<p class="info-hasil"><span id="hitung-katalog">8</span> tanaman ditemukan</p>
<div class="grid-produk">
<article class="kartu-tanaman">
<div class="media-tanaman media--monstera" role="img" aria-label="Ilustrasi gradasi monstera">
<span class="lencana lencana--indoor">Indoor</span>
<span class="lencana lencana--promo">Terlaris</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Monstera Deliciosa</h2>
<p class="keterangan">Daun lebar bercelah khas; tumbuh rapi di sudut ruangan.</p>
<div class="baris-beli"><span class="harga">Rp95.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--lidah" role="img" aria-label="Ilustrasi gradasi lidah mertua">
<span class="lencana lencana--indoor">Indoor</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Lidah Mertua</h2>
<p class="keterangan">Daun tegak berpola garis; toleran penyiraman jarang.</p>
<div class="baris-beli"><span class="harga">Rp45.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--aglaonema" role="img" aria-label="Ilustrasi gradasi aglaonema">
<span class="lencana lencana--rimbu">Rimbu</span>
<span class="lencana lencana--promo">Baru</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Aglaonema Merah</h2>
<p class="keterangan">Daun merah marun bermotif; senang tempat teduh.</p>
<div class="baris-beli"><span class="harga">Rp78.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--calathea" role="img" aria-label="Ilustrasi gradasi calathea">
<span class="lencana lencana--rimbu">Rimbu</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Calathea Orbifolia</h2>
<p class="keterangan">Daun bulat bergaris silver; butuh kelembapan tinggi.</p>
<div class="baris-beli"><span class="harga">Rp110.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--pakis" role="img" aria-label="Ilustrasi gradasi pakis hias">
<span class="lencana lencana--rimbu">Rimbu</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Pakis Hias</h2>
<p class="keterangan">Rumpun daun rimbun untuk beranda yang teduh.</p>
<div class="baris-beli"><span class="harga">Rp52.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--bougenville" role="img" aria-label="Ilustrasi gradasi bougenville">
<span class="lencana lencana--solar">Solar</span>
<span class="lencana lencana--promo">Terlaris</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Bougenville</h2>
<p class="keterangan">Bunga merah muda; mekar deras di matahari penuh.</p>
<div class="baris-beli"><span class="harga">Rp60.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--kamboja" role="img" aria-label="Ilustrasi gradasi kamboja">
<span class="lencana lencana--solar">Solar</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Kamboja Jepang</h2>
<p class="keterangan">Bunga kuning lembut; kuat di teras yang panas.</p>
<div class="baris-beli"><span class="harga">Rp85.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
<article class="kartu-tanaman">
<div class="media-tanaman media--melati" role="img" aria-label="Ilustrasi gradasi melati">
<span class="lencana lencana--solar">Solar</span>
<span class="lencana lencana--promo">Baru</span>
</div>
<div class="badan-kartu">
<h2 class="nama-tanaman">Melati</h2>
<p class="keterangan">Putih wangi sepanjang tahun; primadona pekarangan.</p>
<div class="baris-beli"><span class="harga">Rp38.000</span><button class="tombol" type="button">Tambah</button></div>
</div>
</article>
</div>
</section>
</div>
</main>
<footer class="laman-kaki">
<div class="pembungkus kaki-baris">
<span>Katalog Tanaman — proyek latihan CSS</span>
<span>Semua foto tanaman digantikan gradasi warna</span>
</div>
</footer>
</body>
</html>Ringkasan #
Katalog kini benar-benar berbentuk grid: satu deklarasi repeat(auto-fit, minmax(210px, 1fr)) menghasilkan empat kolom di layar lebar dan menyusut dengan anggun sampai satu kolom di ponsel, gap menjaga jaraknya, dan info hasil siap dihitung ulang. Yang masih hilang adalah sisi kiri halaman: bilah filter beserta panel-panelnya. Bagian 4 membangun tampilannya seluruhnya — tombol kategori, daftar cek, rentang harga statis, dan tombol atur ulang.