Dengan token yang siap, dua komponen paling pertama dilihat pengunjung dapat dibangun cepat: navigation bar dan hero. Navigation memakai position: sticky agar tetap terlihat saat halaman digulir, ditambah latar semi transparan dan backdrop-filter: blur() agar konten di belakangnya melembut. Hero memakai grid dua kolom dengan rasio 1.1fr dan 0.9fr — teks sedikit lebih lebar daripada gambar — serta judul fluid yang memakai clamp(). Kedua komponen ini juga memperkenalkan .wadah, wadah konten 1120px yang menjadi acuan lebar seluruh halaman.
Tujuan Bagian dan Prasyarat #
Target bagian ini:
- Navigation sticky dengan
z-index: 10, latarrgb(255 255 255 / 85%), dan menu flex berjarak 24px. - Wadah
.wadahselebar maksimum 1120px yang terpusat otomatis. - Hero grid dua kolom (572px dan 468px pada wadah 1120px) dengan gap 32px.
- Judul hero fluid
clamp(30px, 28px + 1.25vw, 44px)dan sepasang tombol dari Bagian 1.
Konsep pendukungnya: Sticky Positioning untuk navigation, Flex Container dan Items untuk menu, Grid Container dan Items untuk hero, Centered Container untuk wadah, dan Calc, Min, dan Max untuk fluid sizing.
Menyusun Wadah dan Navigation #
Blok berikut ditambahkan di bawah blok Bagian 1 pada styles/portofolio.css. Wadah .wadah didefinisikan lebih dulu karena dipakai markup di mana-mana, lalu .navigasi dan isinya.
.navigasi {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spasi-4);
padding: var(--spasi-4) var(--spasi-6);
background-color: rgb(255 255 255 / 85%);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--warna-garis);
}
.logo {
font-size: var(--teks-lg);
font-weight: 700;
color: var(--warna-teks);
text-decoration: none;
}
.navigasi-menu {
display: flex;
gap: var(--spasi-6);
list-style: none;
}
.navigasi-tautan {
color: var(--warna-teks-lembut);
font-weight: 500;
text-decoration: none;
}
.navigasi-tautan:hover {
color: var(--warna-primer);
}
.wadah {
max-width: 1120px;
margin: 0 auto;
padding: 0 var(--spasi-6);
}Markup header dan kerangka halaman utama menyusul di index.html:
<header class="navigasi">
<a class="logo" href="#">BS.</a>
<nav>
<ul class="navigasi-menu">
<li><a class="navigasi-tautan" href="#beranda">Beranda</a></li>
<li><a class="navigasi-tautan" href="#proyek">Proyek</a></li>
<li><a class="navigasi-tautan" href="#kontak">Kontak</a></li>
</ul>
</nav>
</header>
<main>
<section class="wadah" id="beranda">
<div class="bagian-hero">
<div>
<span class="badge">Terbuka untuk proyek</span>
<h1 class="judul-hero">Halo, saya <span class="teks-aksen">Budi Santoso</span></h1>
<p class="deskripsi-hero">
Frontend developer yang menekuni antarmuka web cepat dan rapi.
</p>
<div class="hero-aksi">
<a class="tombol tombol-primer" href="#proyek">Lihat Proyek</a>
<a class="tombol tombol-kedua" href="mailto:[email protected]">Hubungi Saya</a>
</div>
</div>
<img class="foto-hero" src="assets/foto-hero.svg" alt="Ilustrasi jendela editor kode">
</div>
</section>
</main>Menyelesaikan Hero #
Blok hero ditambahkan tepat di bawah blok navigation, tetap pada Bagian 2:
.bagian-hero {
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: var(--spasi-8);
align-items: center;
padding: var(--spasi-16) 0;
}
.judul-hero {
font-size: clamp(30px, 28px + 1.25vw, 44px);
line-height: 1.15;
color: var(--warna-teks);
}
.teks-aksen {
color: var(--warna-primer);
}
.deskripsi-hero {
margin-top: var(--spasi-4);
font-size: 18px;
line-height: 1.6;
color: var(--warna-teks-lembut);
}
.hero-aksi {
display: flex;
gap: var(--spasi-4);
margin-top: var(--spasi-6);
}
.foto-hero {
display: block;
width: 100%;
height: auto;
border-radius: 16px;
border: 1px solid var(--warna-garis);
}Hasil di Browser:
Navigation menempel di atas (sticky, top 0px, z-index 10) dengan
latar rgba(255, 255, 255, 0.85) dan blur 8px. Menu berjarak
antartautan 24px. Hero terbagi dua kolom 572px dan 468px dengan
gap 32px; judul terukur 44px (batas atas clamp pada viewport
1280px) dan baris tombol ber-gap 16px.Bedah Aturan Kunci #
.navigasi(sticky):position: stickydengantop: 0membuat header menempel di tepi atas viewport saat digulir. Sticky hanya bekerja bila elemen punya nilai inset, dan tetap menempati alur dokumen — tidak sepertifixed.z-index: 10menjaga header di atas kartu yang muncul kemudian.- Latar semi transparan + blur:
rgb(255 255 255 / 85%)membuat header tembus pandang 15%, danbackdrop-filter: blur(8px)mengaburkan konten yang terlihat menembusnya. Kombinasi ini lazim disebut efek kaca susu (glassmorphism). .wadah:max-width: 1120px+margin: 0 automemusatkan konten; padding horizontal 24px menjaga jarak minimum di tepi. Pada viewport 1280px, browser menghitung margin kiri-kanan wadah menjadi 80px..bagian-hero: dua track1.1frdan0.9frmembagi ruang bebas. Dengan konten wadah 1072px (1120 − 2 × 24) dan gap 32px, ruang untuk track adalah 1040px; track pertama mendapat 572px, track kedua 468px..judul-hero:clamp(30px, 28px + 1.25vw, 44px)mengunci judul antara 30px dan 44px; pada viewport 1280px nilai tengahnya 28 + 16 = 44px, tepat menyentuh batas atas.
Uji Bagian Ini #
Halaman uji bagian ini tersedia di /tests/css/proyek/website-portofolio/navigation-dan-hero.html
, dengan spesifikasi lengkap pada tests/navigation-dan-hero.spec.js. Asersi yang diverifikasi:
| Elemen | Properti | Diharapkan |
|---|---|---|
.navigasi | position | sticky |
.navigasi | top | 0px |
.navigasi | z-index | 10 |
.navigasi | backdrop-filter | blur(8px) |
.navigasi-menu | gap | 24px |
.wadah | max-width | 1120px |
.wadah | margin-left | 80px |
.judul-hero | font-size | 44px |
.judul-hero | line-height | 50.6px |
.hero-aksi | gap | 16px |
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
styles/portofolio.css
Unduh berkas/* =========================================================
Proyek: Website Portofolio — styles/portofolio.css
File utuh proyek yang berkembang mengikuti empat bagian:
Bagian 1 (Design Tokens), Bagian 2 (Navigation dan Hero),
Bagian 3 (Project Grid), Bagian 4 (Responsive Design).
Komentar "Bagian N" menandai kelompok aturan yang dibahas
pada setiap bagian.
========================================================= */
/* Bagian 1: Design Tokens --------------------------------- */
:root {
/* Warna semantik */
--warna-primer: #2563eb;
--warna-primer-tua: #1e40af;
--warna-latar: #f8fafc;
--warna-permukaan: #ffffff;
--warna-teks: #0f172a;
--warna-teks-lembut: #475569;
--warna-teks-balik: #ffffff;
--warna-garis: #e2e8f0;
/* Skala spasi */
--spasi-1: 4px;
--spasi-2: 8px;
--spasi-3: 12px;
--spasi-4: 16px;
--spasi-6: 24px;
--spasi-8: 32px;
--spasi-16: 64px;
/* Bentuk */
--radius-sedang: 10px;
--radius-penuh: 999px;
/* Tipografi */
--font-utama: system-ui, sans-serif;
--teks-dasar: 16px;
--teks-lg: 20px;
}
* {
margin: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-utama);
font-size: var(--teks-dasar);
line-height: 1.6;
color: var(--warna-teks);
background-color: var(--warna-latar);
}
.tombol {
display: inline-block;
padding: var(--spasi-3) var(--spasi-4);
border-radius: var(--radius-sedang);
font-size: var(--teks-dasar);
font-weight: 500;
text-decoration: none;
}
.tombol-primer {
background-color: var(--warna-primer);
color: var(--warna-teks-balik);
}
.tombol-kedua {
background-color: var(--warna-permukaan);
color: var(--warna-primer);
border: 1px solid var(--warna-primer);
}
.badge {
display: inline-block;
padding: var(--spasi-1) var(--spasi-2);
margin-bottom: var(--spasi-4);
border-radius: var(--radius-penuh);
background-color: rgb(37 99 235 / 12%);
color: var(--warna-primer-tua);
font-size: 14px;
font-weight: 500;
}
/* Bagian 2: Navigation dan Hero --------------------------- */
.navigasi {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spasi-4);
padding: var(--spasi-4) var(--spasi-6);
background-color: rgb(255 255 255 / 85%);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--warna-garis);
}
.logo {
font-size: var(--teks-lg);
font-weight: 700;
color: var(--warna-teks);
text-decoration: none;
}
.navigasi-menu {
display: flex;
gap: var(--spasi-6);
list-style: none;
}
.navigasi-tautan {
color: var(--warna-teks-lembut);
font-weight: 500;
text-decoration: none;
}
.navigasi-tautan:hover {
color: var(--warna-primer);
}
.wadah {
max-width: 1120px;
margin: 0 auto;
padding: 0 var(--spasi-6); /* Bagian 4: 16px pada layar <= 767px */
}
.bagian-hero {
display: grid;
grid-template-columns: 1.1fr 0.9fr; /* Bagian 4: satu kolom pada layar <= 1023px */
gap: var(--spasi-8);
align-items: center;
padding: var(--spasi-16) 0;
}
.judul-hero {
font-size: clamp(30px, 28px + 1.25vw, 44px);
line-height: 1.15;
color: var(--warna-teks);
}
.teks-aksen {
color: var(--warna-primer);
}
.deskripsi-hero {
margin-top: var(--spasi-4);
font-size: 18px;
line-height: 1.6;
color: var(--warna-teks-lembut);
}
.hero-aksi {
display: flex;
gap: var(--spasi-4);
margin-top: var(--spasi-6);
}
.foto-hero {
display: block;
width: 100%;
height: auto;
border-radius: 16px;
border: 1px solid var(--warna-garis);
}
/* Bagian 3: Project Grid ---------------------------------- */
.judul-bagian {
font-size: 28px;
font-weight: 700;
}
.deskripsi-bagian {
margin-top: var(--spasi-2);
max-width: 560px;
color: var(--warna-teks-lembut);
}
.grid-proyek {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); /* Bagian 4: minmax(240px, 1fr) pada layar <= 767px */
gap: var(--spasi-6);
margin-top: var(--spasi-8);
}
.kartu-proyek {
display: flex;
flex-direction: column;
background-color: var(--warna-permukaan);
border: 1px solid var(--warna-garis);
border-radius: var(--radius-sedang);
overflow: hidden;
}
.kartu-proyek-img {
display: block;
width: 100%;
aspect-ratio: 16 / 10;
object-fit: cover;
}
.kartu-proyek-isi {
display: grid;
gap: var(--spasi-2);
padding: var(--spasi-6);
}
.kartu-proyek-judul {
font-size: var(--teks-lg);
font-weight: 600;
}
.kartu-proyek-deskripsi {
font-size: 14px;
color: var(--warna-teks-lembut);
}
.tautan-kartu {
color: var(--warna-primer);
font-weight: 500;
text-decoration: none;
}
/* Bagian 4: Responsive Design ----------------------------- */
@media (max-width: 767px) {
.wadah {
padding: 0 var(--spasi-4);
}
.navigasi-menu {
flex-direction: column;
gap: var(--spasi-2);
}
.grid-proyek {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
}
@media (max-width: 1023px) {
.bagian-hero {
grid-template-columns: 1fr;
}
}
tests/design-tokens.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Website Portofolio
// Bagian 1: Design Tokens
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
// /tests/css/proyek/website-portofolio/design-tokens.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// Token tersimpan di :root (elemen html) dan terbaca apa adanya.
{ elemen: "html", properti: "--warna-primer", diharapkan: "#2563eb" },
{ elemen: "html", properti: "--spasi-6", diharapkan: "24px" },
{ elemen: "html", properti: "--radius-sedang", diharapkan: "10px" },
// body mengonsumsi token: latar slate-50 dan teks slate-900.
{ elemen: "body", properti: "background-color", diharapkan: "rgb(248, 250, 252)" },
{ elemen: "body", properti: "color", diharapkan: "rgb(15, 23, 42)" },
{ elemen: "body", properti: "font-size", diharapkan: "16px" },
// .tombol-primer: padding dari token spasi, radius dari token bentuk.
{ elemen: ".tombol-primer", properti: "background-color", diharapkan: "rgb(37, 99, 235)" },
{ elemen: ".tombol-primer", properti: "color", diharapkan: "rgb(255, 255, 255)" },
{ elemen: ".tombol-primer", properti: "padding-top", diharapkan: "12px" },
{ elemen: ".tombol-primer", properti: "padding-left", diharapkan: "16px" },
{ elemen: ".tombol-primer", properti: "border-radius", diharapkan: "10px" },
// .tombol-kedua: permukaan putih dengan teks dan border primer.
{ elemen: ".tombol-kedua", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
{ elemen: ".tombol-kedua", properti: "color", diharapkan: "rgb(37, 99, 235)" },
{ elemen: ".tombol-kedua", properti: "border-top-color", diharapkan: "rgb(37, 99, 235)" },
// .badge memakai warna primer dengan alpha 12% dan radius penuh.
{ elemen: ".badge", properti: "background-color", diharapkan: "rgba(37, 99, 235, 0.12)" },
{ elemen: ".badge", properti: "color", diharapkan: "rgb(30, 64, 175)" },
{ elemen: ".badge", properti: "border-radius", diharapkan: "999px" },
{ elemen: ".badge", properti: "font-size", diharapkan: "14px" },
];
tests/navigation-dan-hero.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Website Portofolio
// Bagian 2: Navigation dan Hero
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720):
// /tests/css/proyek/website-portofolio/navigation-dan-hero.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// Navigation menempel di atas: sticky dengan jarak 0 dari tepi.
{ elemen: ".navigasi", properti: "position", diharapkan: "sticky" },
{ elemen: ".navigasi", properti: "top", diharapkan: "0px" },
{ elemen: ".navigasi", properti: "z-index", diharapkan: "10" },
// Latar navigasi putih transparan 85% dengan efek blur di belakangnya.
{ elemen: ".navigasi", properti: "background-color", diharapkan: "rgba(255, 255, 255, 0.85)" },
{ elemen: ".navigasi", properti: "backdrop-filter", diharapkan: "blur(8px)" },
// Menu navigasi tersusun horizontal dengan jarak antartautan 24px.
{ elemen: ".navigasi-menu", properti: "display", diharapkan: "flex" },
{ elemen: ".navigasi-menu", properti: "flex-direction", diharapkan: "row" },
{ elemen: ".navigasi-menu", properti: "gap", diharapkan: "24px" },
// Judul hero fluid: clamp mencapai batas atas 44px pada viewport 1280px.
{ elemen: ".judul-hero", properti: "font-size", diharapkan: "44px" },
{ elemen: ".judul-hero", properti: "line-height", diharapkan: "50.6px" },
// Deskripsi hero 18px dengan warna teks lembut.
{ elemen: ".deskripsi-hero", properti: "font-size", diharapkan: "18px" },
{ elemen: ".deskripsi-hero", properti: "color", diharapkan: "rgb(71, 85, 105)" },
// Baris tombol aksi adalah flex dengan gap 16px.
{ elemen: ".hero-aksi", properti: "display", diharapkan: "flex" },
{ elemen: ".hero-aksi", properti: "gap", diharapkan: "16px" },
// Wadah konten dibatasi 1120px dan terpusat di viewport 1280px.
{ elemen: ".wadah", properti: "max-width", diharapkan: "1120px" },
{ elemen: ".wadah", properti: "margin-left", diharapkan: "80px" },
];
tests/project-grid.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Website Portofolio
// Bagian 3: Project Grid
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720, wadah 1120px dengan
// padding 24px sehingga konten selebar 1072px):
// /tests/css/proyek/website-portofolio/project-grid.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// Grid proyek memakai auto-fill minmax(280px, 1fr): pada konten
// 1072px muat 3 track berukuran sekitar 341px dengan gap 24px.
// Nilai di bawah diambil dari serialisasi computed browser.
{ elemen: ".grid-proyek", properti: "display", diharapkan: "grid" },
{ elemen: ".grid-proyek", properti: "gap", diharapkan: "24px" },
{
elemen: ".grid-proyek",
properti: "grid-template-columns",
diharapkan: "341.328px 341.328px 341.344px",
},
// Kartu proyek: permukaan putih, garis 1px, radius 10px.
{ elemen: ".kartu-proyek", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
{ elemen: ".kartu-proyek", properti: "border-top-width", diharapkan: "1px" },
{ elemen: ".kartu-proyek", properti: "border-radius", diharapkan: "10px" },
// Isi kartu adalah grid kecil dengan gap 8px dan padding 24px.
{ elemen: ".kartu-proyek-isi", properti: "display", diharapkan: "grid" },
{ elemen: ".kartu-proyek-isi", properti: "gap", diharapkan: "8px" },
{ elemen: ".kartu-proyek-isi", properti: "padding", diharapkan: "24px" },
// Judul kartu 20px, deskripsi 14px dengan warna lembut.
{ elemen: ".kartu-proyek-judul", properti: "font-size", diharapkan: "20px" },
{ elemen: ".kartu-proyek-deskripsi", properti: "font-size", diharapkan: "14px" },
{ elemen: ".kartu-proyek-deskripsi", properti: "color", diharapkan: "rgb(71, 85, 105)" },
// Tautan kartu berwarna primer.
{ elemen: ".tautan-kartu", properti: "color", diharapkan: "rgb(37, 99, 235)" },
];
tests/responsive-design.spec.js
Unduh berkas// ============================================================
// Spesifikasi uji — Proyek: Website Portofolio
// Bagian 4: Responsive Design
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720, sehingga seluruh media
// query max-width berada pada keadaan tidak aktif dan nilai
// desktop yang berlaku):
// /tests/css/proyek/website-portofolio/responsive-design.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.
const asersi = [
// Wadah tetap 1120px dan berpadding 24px di luar breakpoint 767px.
{ elemen: ".wadah", properti: "max-width", diharapkan: "1120px" },
{ elemen: ".wadah", properti: "padding-left", diharapkan: "24px" },
// Hero dua kolom: track 1.1fr dan 0.9fr dari konten 1040px
// (1072 - gap 32) dibagi proporsional menjadi 572px dan 468px.
{ elemen: ".bagian-hero", properti: "display", diharapkan: "grid" },
{ elemen: ".bagian-hero", properti: "gap", diharapkan: "32px" },
{ elemen: ".bagian-hero", properti: "grid-template-columns", diharapkan: "572px 468px" },
// Menu navigasi tetap horizontal dengan gap 24px.
{ elemen: ".navigasi-menu", properti: "flex-direction", diharapkan: "row" },
{ elemen: ".navigasi-menu", properti: "gap", diharapkan: "24px" },
// Grid proyek tetap tiga track (minmax 280px) di desktop.
{
elemen: ".grid-proyek",
properti: "grid-template-columns",
diharapkan: "341.328px 341.328px 341.344px",
},
// Judul hero tetap 44px: clamp mencapai nilai maksimumnya.
{ elemen: ".judul-hero", properti: "font-size", diharapkan: "44px" },
// Navigation tetap sticky di atas halaman.
{ elemen: ".navigasi", properti: "position", diharapkan: "sticky" },
{ elemen: ".navigasi", properti: "top", diharapkan: "0px" },
];