Sampai bagian kedua, halaman profil terlihat baik selama viewport cukup lebar. Pada layar 360px, padding halaman 48px menyisakan ruang sempit, dan kepala kartu yang tersusun horizontal terasa memaksa. Bagian terakhir ini menambahkan tiga lapisan adaptasi: tipografi fluid dengan clamp(), kartu yang menyesuaikan lebar dengan min(), dan satu breakpoint min-width: 640px bergaya mobile-first untuk menyusun ulang kepala kartu. Tidak ada framework dan tidak ada library — hanya CSS yang kita tulis sendiri di file yang sama.

Tujuan Bagian dan Prasyarat #

Target bagian ini:

  • Judul profil berukuran fluid antara 30px dan 40px mengikuti lebar viewport.
  • Kartu profil tetap 300px di layar lebar, tetapi menyusut mengikuti lebar konten di layar sempit.
  • Kepala kartu tersusun vertikal di mobile dan kembali horizontal pada viewport minimal 640px.

Konsep pendukungnya ada pada chapter Media Queries , Mobile First , Breakpoints , serta Calc, Min, dan Max . Pola clamp() untuk teks juga diulas pada Responsive Typography .

Tipografi Fluid dan Kartu yang Menyusut #

Tiga aturan dari bagian sebelumnya diperbarui. Nilai lama di dalam aturan tersebut diganti langsung — file tetap satu, isinya berkembang.

css styles/profil.css — aturan yang diperbarui
.profil-nama {
  /* Bagian 1: font-size: 36px */
  font-size: clamp(30px, 24px + 1vw, 40px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--warna-teks);
}

.kartu-profil {
  /* Bagian 2: width: 300px */
  width: min(100%, 300px);
  margin: 0 auto;
  padding: 24px;
  background-color: var(--warna-kartu);
  border: 1px solid var(--warna-garis);
  border-radius: 12px;
  box-shadow: 0 4px 12px rgb(15 23 42 / 8%);
}

.halaman {
  max-width: 760px;
  margin: 0 auto;
  /* Bagian 1: padding: 48px 16px */
  padding: 32px 16px;
}

Perhitungan clamp(30px, 24px + 1vw, 40px) mudah ditelusuri: pada viewport 1280px, nilai 1vw adalah 12,8px sehingga komponen tengah bernilai 36,8px — di dalam rentang, maka judul tampil 36,8px. Pada viewport 360px hasilnya 27,6px, di bawah batas minimum, sehingga judul terkunci di 30px. Sementara itu min(100%, 300px) memilih nilai terkecil: 300px ketika konten lebih lebar dari itu, atau lebar konten penuh ketika lebih sempit.

Breakpoint Mobile-First untuk Kepala Kartu #

Kepala kartu sekarang dideklarasikan ulang: susunan vertikal menjadi bawaan, dan media query min-width: 640px mengembalikannya ke susunan horizontal pada layar yang lega. Blok media query diletakkan di ujung file.

css styles/profil.css — Bagian 3 (tambahan)
.profil-kepala {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
  text-align: center;
}

@media (min-width: 640px) {
  .halaman {
    padding: 64px 32px;
  }

  .profil-kepala {
    flex-direction: row;
    text-align: left;
    margin-bottom: 24px;
  }
}

Hasil di Browser:

text
Pada viewport 1280px breakpoint 640px aktif: padding halaman
64px 32px, kartu tetap 300px dengan margin kiri-kanan 198px,
kepala kartu flex-direction row, dan judul terukur 36.8px.
Pada viewport di bawah 640px seluruh aturan media query tidak
berlaku dan susunan vertikal yang dipakai.

Bedah Media Query #

  1. @media (min-width: 640px): kondisi berbunyi “berlaku pada viewport minimal 640px”. Di bawah 640px seluruh blok diabaikan browser — tidak ada yang perlu “dimatikan” secara manual.
  2. .halaman { padding: 64px 32px }: layar yang lebih lebar diberi ruang lebih lega; nilai dasar 32px 16px tetap dipakai di mobile.
  3. .profil-kepala { flex-direction: row }: flexbox kembali ke arah utama horizontal; align-items: center dari aturan dasar kini menyejajarkan avatar dan teks secara vertikal, bukan horizontal.
  4. text-align: left dan margin-bottom: 24px: teks identitas kembali rata kiri, dan jarak kepala kartu ke bio diperbesar mengikuti ritme spasi blok.

Pola ini disebut mobile-first: gaya dasar ditulis untuk layar kecil, lalu media query menambah peningkatan seiring viewport melebar. Satu breakpoint saja cukup untuk halaman ini; tambah breakpoint baru hanya bila ada masalah nyata di rentang lebar tertentu.

Uji Bagian Ini #

Halaman uji bagian ini tersedia di /tests/css/proyek/halaman-profil/responsive-layout.html , dengan spesifikasi lengkap pada tests/responsive-layout.spec.js. Nilai yang diverifikasi pada viewport 1280px:

ElemenPropertiDiharapkan
.halamanpadding-top64px
.kartu-profilwidth300px
.kartu-profilmargin-left198px
.profil-namafont-size36.8px
.profil-kepalaflex-directionrow
.profil-kepalamargin-bottom24px
.daftar-keahliangap8px

Nilai margin-left: 198px adalah bukti margin: 0 auto bekerja: konten halaman selebar 696px (760 − 2 × 32) dikurangi kartu 300px, lalu dibagi dua. Apabila semua baris di tabel ini hijau, proyek halaman profil tuntas dan siap dipakai sebagai templat profil pribadi kita sendiri.

Berkas Sumber

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

styles/profil.css

Unduh berkas
css
/* =========================================================
   Proyek: Halaman Profil — styles/profil.css
   File utuh proyek yang berkembang mengikuti tiga bagian:
   Bagian 1 (Typography dan Warna), Bagian 2 (Box Model dan
   Spacing), Bagian 3 (Responsive Layout). Komentar "Bagian N"
   menandai kelompok aturan yang dibahas pada setiap bagian,
   dan komentar kecil mencatat perubahan antarbagian.
   ========================================================= */

/* Bagian 1: Typography dan Warna -------------------------- */

:root {
  --warna-teks: #0f172a;
  --warna-teks-lembut: #475569;
  --warna-aksen: #0284c7;
  --warna-latar: #f1f5f9;
  --warna-kartu: #ffffff;
  --warna-garis: #e2e8f0;
}

* {
  margin: 0;
  box-sizing: border-box; /* ditambahkan pada Bagian 2 */
}

body {
  font-family: system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: var(--warna-teks);
  background-color: var(--warna-latar);
}

.halaman {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 16px; /* Bagian 3: padding dasar diturunkan dari 48px 16px */
}

.profil-nama {
  /* Bagian 3: nilai tetap 36px diganti clamp() agar fluid */
  font-size: clamp(30px, 24px + 1vw, 40px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--warna-teks);
}

.profil-peran {
  font-size: 18px;
  font-weight: 500;
  color: var(--warna-aksen);
}

.profil-bio {
  color: var(--warna-teks-lembut);
}

.profil-bagian-judul {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--warna-teks);
}

.tautan-kontak {
  color: var(--warna-aksen);
  font-weight: 500;
  text-decoration: underline;
}

.tautan-kontak:hover {
  text-decoration-thickness: 2px;
}

/* Bagian 2: Box Model dan Spacing ------------------------- */

.kartu-profil {
  width: min(100%, 300px); /* Bagian 3: mulanya 300px tetap */
  margin: 0 auto;
  padding: 24px;
  background-color: var(--warna-kartu);
  border: 1px solid var(--warna-garis);
  border-radius: 12px;
  box-shadow: 0 4px 12px rgb(15 23 42 / 8%);
}

.profil-kepala {
  display: flex;
  flex-direction: column; /* Bagian 3: kembali ke baris pada breakpoint */
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
  text-align: center;
}

.avatar {
  flex: none; /* jaga ukuran: avatar tidak ikut menyusut */
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 2px solid var(--warna-garis);
}

.profil-bagian {
  margin-top: 24px;
}

.daftar-keahlian {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
}

.keahlian {
  padding: 8px 12px;
  background-color: var(--warna-latar);
  border-radius: 8px;
  font-size: 14px;
  color: var(--warna-teks-lembut);
}

/* Bagian 3: Responsive Layout ----------------------------- */

@media (min-width: 640px) {
  .halaman {
    padding: 64px 32px;
  }

  .profil-kepala {
    flex-direction: row;
    text-align: left;
    margin-bottom: 24px;
  }
}

tests/box-model-dan-spacing.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Halaman Profil
// Bagian 2: Box Model dan Spacing
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/halaman-profil/box-model-dan-spacing.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Kartu profil selebar 300px berkat box-sizing: border-box,
  // padding dan border tidak menambah lebar totalnya.
  { elemen: ".kartu-profil", properti: "width", diharapkan: "300px" },

  // Padding 24px di semua sisi memberi ruang lega bagi konten.
  { elemen: ".kartu-profil", properti: "padding", diharapkan: "24px" },

  // Border tipis 1px dengan warna token --warna-garis (#e2e8f0).
  { elemen: ".kartu-profil", properti: "border-top-width", diharapkan: "1px" },
  { elemen: ".kartu-profil", properti: "border-top-color", diharapkan: "rgb(226, 232, 240)" },

  // Sudut kartu membulat 12px, latar kartu putih.
  { elemen: ".kartu-profil", properti: "border-radius", diharapkan: "12px" },
  { elemen: ".kartu-profil", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },

  // .profil-kepala adalah flex container dengan jarak antaritem 16px.
  { elemen: ".profil-kepala", properti: "display", diharapkan: "flex" },
  { elemen: ".profil-kepala", properti: "gap", diharapkan: "16px" },

  // Ritme vertikal: kepala kartu diberi margin bawah 16px.
  { elemen: ".profil-kepala", properti: "margin-bottom", diharapkan: "16px" },

  // Avatar berukuran 96px tetap (flex: none agar tidak menyusut
  // di dalam flex container) dengan sudut penuh (lingkaran).
  { elemen: ".avatar", properti: "width", diharapkan: "96px" },
  { elemen: ".avatar", properti: "height", diharapkan: "96px" },
  { elemen: ".avatar", properti: "border-radius", diharapkan: "50%" },

  // Setiap subbagian kartu berjarak 24px dari blok di atasnya.
  { elemen: ".profil-bagian", properti: "margin-top", diharapkan: "24px" },

  // Daftar keahlian adalah flex yang meliputi baris dengan gap 8px.
  { elemen: ".daftar-keahlian", properti: "display", diharapkan: "flex" },
  { elemen: ".daftar-keahlian", properti: "flex-wrap", diharapkan: "wrap" },
  { elemen: ".daftar-keahlian", properti: "gap", diharapkan: "8px" },

  // Chip keahlian berpadding 8px vertikal dan 12px horizontal.
  { elemen: ".keahlian", properti: "padding-top", diharapkan: "8px" },
  { elemen: ".keahlian", properti: "padding-left", diharapkan: "12px" },

  // Chip membulat 8px dengan teks 14px.
  { elemen: ".keahlian", properti: "border-radius", diharapkan: "8px" },
  { elemen: ".keahlian", properti: "font-size", diharapkan: "14px" },
];

tests/responsive-layout.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Halaman Profil
// Bagian 3: Responsive Layout
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720, sehingga breakpoint
// min-width: 640px berada pada keadaan aktif):
//   /tests/css/proyek/halaman-profil/responsive-layout.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Wadah halaman dibatasi 760px dan, pada layar >= 640px,
  // berpadding 64px vertikal serta 32px horizontal.
  { elemen: ".halaman", properti: "max-width", diharapkan: "760px" },
  { elemen: ".halaman", properti: "padding-top", diharapkan: "64px" },

  // Kartu tetap 300px dan terpusat: margin auto terhitung browser
  // menjadi 198px pada konten selebar 696px (760 - 2 x 32).
  { elemen: ".kartu-profil", properti: "width", diharapkan: "300px" },
  { elemen: ".kartu-profil", properti: "margin-left", diharapkan: "198px" },

  // Judul fluid: clamp(30px, 24px + 1vw, 40px) pada viewport 1280px
  // menghasilkan 24 + 12.8 = 36.8px, masih di dalam rentang.
  { elemen: ".profil-nama", properti: "font-size", diharapkan: "36.8px" },

  // Pada layar >= 640px kepala profil kembali tersusun horizontal.
  { elemen: ".profil-kepala", properti: "flex-direction", diharapkan: "row" },
  { elemen: ".profil-kepala", properti: "gap", diharapkan: "16px" },

  // Jarak kepala ke bio diperbesar menjadi 24px pada breakpoint.
  { elemen: ".profil-kepala", properti: "margin-bottom", diharapkan: "24px" },

  // Daftar keahlian tetap berjajar dengan gap 8px.
  { elemen: ".daftar-keahlian", properti: "flex-direction", diharapkan: "row" },
  { elemen: ".daftar-keahlian", properti: "gap", diharapkan: "8px" },
];

tests/typography-dan-warna.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Halaman Profil
// Bagian 1: Typography dan Warna
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/halaman-profil/typography-dan-warna.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // body membawa warna teks utama dari token --warna-teks (#0f172a
  // dikonversi browser menjadi rgb(15, 23, 42)).
  { elemen: "body", properti: "color", diharapkan: "rgb(15, 23, 42)" },

  // Latar halaman memakai token --warna-latar (#f1f5f9).
  { elemen: "body", properti: "background-color", diharapkan: "rgb(241, 245, 249)" },

  // Ukuran teks dasar 16px dengan line-height 1.5 (16 x 1.5 = 24px).
  { elemen: "body", properti: "font-size", diharapkan: "16px" },
  { elemen: "body", properti: "line-height", diharapkan: "24px" },

  // .profil-nama berada di puncak skala tipografi: 36px, tebal 700.
  { elemen: ".profil-nama", properti: "font-size", diharapkan: "36px" },
  { elemen: ".profil-nama", properti: "font-weight", diharapkan: "700" },

  // line-height 1.2 pada font 36px dihitung browser menjadi 43.2px.
  { elemen: ".profil-nama", properti: "line-height", diharapkan: "43.2px" },

  // .profil-peran memakai warna aksen (#0284c7) dan bobot medium.
  { elemen: ".profil-peran", properti: "color", diharapkan: "rgb(2, 132, 199)" },
  { elemen: ".profil-peran", properti: "font-size", diharapkan: "18px" },
  { elemen: ".profil-peran", properti: "font-weight", diharapkan: "500" },

  // .profil-bio memakai warna teks lembut (#475569).
  { elemen: ".profil-bio", properti: "color", diharapkan: "rgb(71, 85, 105)" },

  // Judul bagian 20px, bobot 600, dengan letter-spacing 0.5px.
  { elemen: ".profil-bagian-judul", properti: "font-size", diharapkan: "20px" },
  { elemen: ".profil-bagian-judul", properti: "font-weight", diharapkan: "600" },
  { elemen: ".profil-bagian-judul", properti: "letter-spacing", diharapkan: "0.5px" },

  // Tautan kontak berwarna aksen dan digarisbawahi.
  { elemen: ".tautan-kontak", properti: "color", diharapkan: "rgb(2, 132, 199)" },
  { elemen: ".tautan-kontak", properti: "text-decoration-line", diharapkan: "underline" },
];