Katalog daring yang nyaman dipakai berdiri di atas dua keputusan layout: seberapa lebar kolom filter, dan bagaimana daftar produk mengisi sisa ruangnya. Bagian 1 menjawab keduanya sebelum satu pun kartu ditulis. Kita menetapkan dulu kosakata visual halaman lewat custom property, menyiapkan base reset yang ringkas, merangkai kerangka header dan footer, lalu membagi area utama menjadi dua kolom dengan CSS Grid: bilah filter menempati kolom tetap 264 px di kiri, dan katalog produk mengambil seluruh sisa lebar. Snapshot styles.css pada tahap ini belum memuat satu pun kartu; isinya adalah fondasi yang dipakai empat bagian berikutnya.
Token Warna dan Spasi #
Palet proyek ini bertema hijau daun. --warna-latar memberi latar hijau muda yang tenang, --warna-permukaan dipakai untuk bidang komponen seperti header, kartu, dan panel, sedangkan --warna-teks dan --warna-teks-lembut membedakan teks utama dari teks sekunder. Warna utama --warna-utama beserta turunannya — pekat untuk hover, kontras untuk teks di atas tombol, dan lembut untuk latar sekunder — akan menghidupi tombol serta lencana di Bagian 2 dan 4.
Tiga token disiapkan khusus untuk lencana kategori tanaman: --warna-indoor (biru) untuk tanaman penghuni ruangan, --warna-solar (kuning kecokelatan) untuk tanaman penyuka matahari penuh, dan --warna-aksen (merah) untuk lencana promosi seperti Terlaris dan Baru. Kategori ketiga, Rimbu, memakai pasangan --warna-utama-lembut dan --warna-utama-pekat agar tetap dalam keluarga hijau. Skala --jarak-1 sampai --jarak-8 berbasis 4 px dan 8 px menjaga semua jarak berasal dari kelipatan yang sama, dua token --bayangan-* menampung dua tingkat elevasi, dan --lebar-bilah: 264px menyimpan lebar kolom filter agar bisa dirujuk grid di bagian ini sekaligus media query di Bagian 5.
Base dan Kerangka Halaman #
Blok base hanya berisi tiga aturan. Selector universal memasang box-sizing: border-box agar padding tidak menambah lebar elemen, body mengambil keluarga huruf, tinggi baris, latar, dan warna teks dari token, lalu margin h1–h3 dan p dinolkan supaya jarak antarelemen diputuskan per komponen. Dengan fondasi sesederhana ini, setiap komponen baru nanti tidak perlu melawan gaya bawaan browser.
Kerangka halaman berada di kelas .pembungkus (lebar maksimum 1120 px, tebal di tengah, padding sisi 16 px), .laman-kepala berisi brand dan tagline dalam satu baris flex space-between, serta .laman-kaki yang mencerminkan header dengan garis di sisi atasnya. Perhatikan bahwa header tidak menempati grid apa pun; ia berdiri sendiri di atas .laman-utama, persis seperti pola kerangka halaman pada umumnya.
Grid Bilah Filter dan Isi Katalog #
Inti bagian ini adalah satu aturan berikut:
.layout-katalog {
display: grid;
grid-template-columns: var(--lebar-bilah) minmax(0, 1fr);
gap: var(--jarak-6);
align-items: start;
}Baris per baris: kolom pertama dikunci pada token --lebar-bilah (264 px) agar filter tidak ikut menyempit; kolom kedua memakai minmax(0, 1fr) — angka 1fr berarti “isi sisa ruang secara adil”, sedangkan batas bawah 0 mencegah isi kolom yang lebar, misalnya teks tanpa spasi, memaksa kolom melar dan membuat halaman bergulir menyamping. gap: 32 px memberi jarak antar kedua area, dan align-items: start menahan bilah filter agar tidak meregang setinggi katalog yang jauh lebih panjang.
Dua aturan pendamping melengkapi pola ini. .bilah-filter memakai position: sticky; top: 24px sehingga panel filter tetap terlihat saat katalog digulir ke bawah, dan .isi-katalog menambahkan min-width: 0 sebagai pengaman tambahan agar konten di dalamnya tidak melampaui lebar kolomnya. Struktur HTML-nya sendiri sederhana: satu pembungkus .layout-katalog berisi <aside class="bilah-filter"> dan <section class="isi-katalog"> berurutan — urutan inilah yang nanti menentukan bilah muncul di atas katalog pada layar sempit.
/* ============================================================
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; }Ringkasan #
Bagian pertama tidak menghasilkan komponen yang bisa diklik, tetapi menetapkan semua keputusan struktural: palet hijau per-peran, skala spasi 4/8 px, dua tingkat bayangan, kerangka halaman, dan grid dua kolom dengan bilah sticky 264 px. Base reset memastikan semua browser mulai dari titik yang sama. Sejak Bagian 2, setiap aturan baru akan memanggil token ini, bukan nilai mentah, dan mengisi katalog dengan kartu produk pertama.