Teks yang rapi pada Bagian 1 masih berupa kolom tulisan tanpa wadah. Pada bagian ini kita mengemasnya ke dalam kartu putih dan memberi setiap unsur jarak yang terukur. Dua alat yang bekerja sama di sini adalah box model — width, padding, border, border-radius — dan ritme spasi berbasis kelipatan 8px lewat margin serta gap. Setelah bagian ini selesai, halaman profil sudah menyerupai hasil akhirnya; tinggal penyesuaian responsif pada Bagian 3.

Tujuan Bagian dan Prasyarat #

Target bagian ini:

  • Kartu profil selebar 300px yang terpusat, dengan box-sizing: border-box agar padding dan border tidak memperlebar totalnya.
  • Avatar 96px berbentuk lingkaran sebagai item pertama kartu.
  • Ritme spasi vertikal 16px dan 24px, serta gap antarchip keahlian 8px.

Konsepnya dibahas tuntas pada chapter Content, Padding, Border, dan Margin , Box Sizing , Width dan Height , serta Gap . Pastikan kita paham perbedaan content-box dan border-box sebelum lanjut, karena perbedaan itu menjadi inti pembahasan di bawah.

Memperluas Reset dan Menyusun Kartu #

Dua perubahan dilakukan pada styles/profil.css. Pertama, reset * diperluas dengan box-sizing: border-box — praktik umum yang membuat nilai width kita benar-benar berarti lebar akhir elemen. Kedua, blok aturan Bagian 2 ditambahkan di bawah aturan Bagian 1.

css styles/profil.css — Bagian 2 (tambahan)
* {
  margin: 0;
  box-sizing: border-box; /* diperluas dari Bagian 1 */
}

.kartu-profil {
  width: 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%);
}

.profil-kepala {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
}

.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);
}

Markup index.html kemudian dibungkus elemen <section class="kartu-profil">, dan kepala kartu memuat avatar beserta blok identitas. Gambar avatar berasal dari assets/avatar-budi.svg.

html index.html — potongan kartu
<section class="kartu-profil">
  <div class="profil-kepala">
    <img class="avatar" src="assets/avatar-budi.svg" alt="Avatar Budi Santoso">
    <div>
      <h1 class="profil-nama">Budi Santoso</h1>
      <p class="profil-peran">Frontend Web Developer</p>
    </div>
  </div>
  <p class="profil-bio">Saya membangun antarmuka web yang cepat, rapi, dan mudah dipakai.</p>
  <section class="profil-bagian">
    <h2 class="profil-bagian-judul">Keahlian</h2>
    <ul class="daftar-keahlian">
      <li class="keahlian">HTML</li>
      <li class="keahlian">CSS</li>
      <li class="keahlian">JavaScript</li>
      <li class="keahlian">Aksesibilitas</li>
    </ul>
  </section>
</section>

Hasil di Browser:

text
Kartu tampil selebar 300px dengan padding 24px dan border 1px
rgb(226, 232, 240). Avatar 96px berbentuk lingkaran. Jarak
avatar ke blok identitas 16px, jarak antarbagian kartu 24px,
dan chip keahlian bergap 8px.

Bedah Aturan .kartu-profil #

Aturan kartu adalah contoh box model paling padat di proyek ini, jadi kita bedah baris demi baris:

  1. width: 300px: lebar kartu ditetapkan eksplisit. Karena reset * memakai box-sizing: border-box, nilai 300px mencakup padding dan border; konten di dalamnya efektif selebar 250px (300 − 2 × 24 − 2 × 1). Tanpa border-box, padding dan border akan menambah lebar total menjadi 350px.
  2. margin: 0 auto: margin atas dan bawah nol, sedangkan auto pada margin horizontal membagi sisa ruang wadah secara sama. Pada wadah selebar 728px (760 − 2 × 16), browser menghitung margin kiri dan kanan menjadi 214px.
  3. padding: 24px: ruang di dalam border seragam di keempat sisi, memberi jarak konten ke tepi kartu.
  4. border: 1px solid var(--warna-garis): garis tipis satu piksel dengan token warna abu-abu muda; token --warna-garis dari Bagian 1 akhirnya terpakai.
  5. border-radius: 12px dan box-shadow: 0 4px 12px rgb(15 23 42 / 8%): sudut membulat plus bayangan lembut yang cukup samar untuk mengangkat kartu dari latar tanpa terkesan berat.

Spasi antarelemen memakai dua mekanisme berbeda: gap untuk jarak antaritem flex (.profil-kepala 16px, .daftar-keahlian 8px) dan margin untuk jarak antarblok (.profil-kepala margin-bottom 16px, .profil-bagian margin-top 24px). Perhatikan pula flex: none pada .avatar: tanpa deklarasi itu, avatar menjadi flex item yang dapat menyusut (flex-shrink bawaan 1) ketika blok identitas di sebelahnya membutuhkan ruang, dan ukuran 96px tidak lagi terjaga. Karena kartu punya padding 24px, margin-top .profil-bagian tidak collapse dengan margin di luar kartu.

Uji Bagian Ini #

Halaman uji bagian ini tersedia di /tests/css/proyek/halaman-profil/box-model-dan-spacing.html , dengan spesifikasi lengkap pada tests/box-model-dan-spacing.spec.js. Probe memverifikasi:

ElemenPropertiDiharapkan
.kartu-profilwidth300px
.kartu-profilpadding24px
.kartu-profilborder-top-width1px
.kartu-profilborder-radius12px
.profil-kepalagap16px
.avatarwidth / height96px
.avatarborder-radius50%
.profil-bagianmargin-top24px
.daftar-keahliangap8px
.keahlianpadding-left12px

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" },
];