Dengan fondasi token dan tombol siap, tiga blok layout utama landing page dapat dibangun berdampingan: hero, baris keunggulan, dan grid menu. Ketiganya memperlihatkan pembagian tugas yang sehat antara flexbox dan grid — flexbox untuk satu baris item yang menyesuaikan diri, grid untuk lajur kolom yang sama lebar. Hero memakai dua item berbasis 55% dan 45%; keunggulan memakai flex: 1 agar ketiga kartu berbagi ruang secara adil; menu memakai repeat(3, 1fr). Judul hero menyusut dan membesar mengikuti layar memakai clamp() yang sudah kita kenal di Calc, Min, dan Max .

Tujuan Bagian dan Prasyarat #

Target bagian ini:

  • Hero flex dua kolom dengan gap 48px, item terpusat vertikal, dan .gambar-hero berbanding rasio 4:3.
  • Judul hero fluid clamp(32px, 16px + 3vw, 52px) yang menyentuh batas atas 52px pada viewport 1280px.
  • Baris keunggulan tiga kartu .keunggulan-item dengan flex: 1 (grow 1, shrink 1, basis 0%).
  • Grid menu repeat(3, 1fr) dengan gap 24px dan kartu berlatar permukaan.

Konsep pendukungnya: Flex Container dan Items , Flex Grow, Shrink, dan Basis , Grid Container dan Items , Repeat dan Minmax , dan Aspect Ratio .

Membangun Hero Flexbox Dua Kolom #

Blok berikut ditambahkan di bawah blok Bagian 1 pada styles/landing.css:

css styles/landing.css — Bagian 2 (bagian hero)
.hero {
  display: flex;
  align-items: center;
  gap: var(--spasi-12);
  padding: var(--spasi-16) 0;
}

.hero-teks {
  flex: 1 1 55%;
}

.hero-gambar {
  flex: 1 1 45%;
  display: flex;
}

.gambar-hero {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-besar);
  object-fit: cover;
}

.judul-hero {
  font-size: clamp(32px, 16px + 3vw, 52px);
  line-height: 1.15;
}

.teks-aksen {
  color: var(--warna-primer);
}

.deskripsi-hero {
  margin-top: var(--spasi-4);
  font-size: 18px;
  color: var(--warna-teks-lembut);
}

.hero-aksi {
  display: flex;
  gap: var(--spasi-4);
  margin-top: var(--spasi-6);
}

Markup hero dan sepasang tombol dari Bagian 1 menyusul di index.html:

html index.html — hero
<section class="wadah" id="beranda">
  <div class="hero">
    <div class="hero-teks">
      <span class="badge">Diseduh setiap pagi</span>
      <h1 class="judul-hero">Kopi <span class="teks-aksen">Senja</span></h1>
      <p class="deskripsi-hero">
        Biji lereng selatan, disangrai sendiri, diseduh
        saat kamu datang.
      </p>
      <div class="hero-aksi">
        <a class="tombol tombol-primer" href="#menu">Pesan Sekarang</a>
        <a class="tombol tombol-kedua" href="#cerita">Cerita Kami</a>
      </div>
    </div>
    <div class="hero-gambar">
      <img class="gambar-hero" src="assets/gelas-kopi.svg" alt="Ilustrasi gelas kopi susu dengan uap">
    </div>
  </div>
</section>

Hasil di Browser:

text
Hero terbagi dua kolom 563.2px dan 460.8px dengan gap 48px.
Judul terukur 52px (batas atas clamp pada viewport 1280px)
dengan line-height 59.8px. Kotak gambar menjaga rasio 4:3
sehingga tingginya 345.6px. Grid menu menghasilkan tiga
tiga kolom 1fr dengan gap 24px.

Menyusun Keunggulan dan Grid Menu #

Dua blok berikut menyelesaikan Bagian 2:

css styles/landing.css — Bagian 2 (keunggulan dan menu)
.keunggulan {
  display: flex;
  gap: var(--spasi-6);
}

.keunggulan-item {
  flex: 1;
  padding: var(--spasi-6);
  background-color: var(--warna-permukaan);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-sedang);
}

.grid-menu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spasi-6);
  margin-top: var(--spasi-8);
}

.kartu-menu {
  display: flex;
  flex-direction: column;
  background-color: var(--warna-permukaan);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-sedang);
  overflow: hidden;
}

.gambar-menu {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.kartu-menu-isi {
  display: grid;
  gap: var(--spasi-2);
  padding: var(--spasi-6);
}
html index.html — keunggulan dan menu
<section class="wadah" id="keunggulan">
  <div class="keunggulan">
    <div class="keunggulan-item">Biji segar setiap minggu</div>
    <div class="keunggulan-item">Disangrai di tempat</div>
    <div class="keunggulan-item">Barista bersertifikat</div>
  </div>
</section>
<section class="wadah" id="menu">
  <h2 class="judul-bagian">Menu Unggulan</h2>
  <div class="grid-menu">
    <article class="kartu-menu">
      <img class="gambar-menu" src="assets/menu-kopi-susu.svg" alt="Ilustrasi kopi susu senja">
      <div class="kartu-menu-isi">
        <h3 class="kartu-menu-nama">Kopi Susu Senja</h3>
        <span class="kartu-menu-harga">Rp22.000</span>
        <p class="kartu-menu-deskripsi">Espresso, susu, dan gula aren.</p>
      </div>
    </article>
    <article class="kartu-menu">
      <img class="gambar-menu" src="assets/menu-americano.svg" alt="Ilustrasi americano dingin">
      <div class="kartu-menu-isi">
        <h3 class="kartu-menu-nama">Americano Dingin</h3>
        <span class="kartu-menu-harga">Rp18.000</span>
        <p class="kartu-menu-deskripsi">Dua shot espresso dan air es.</p>
      </div>
    </article>
    <article class="kartu-menu">
      <img class="gambar-menu" src="assets/menu-croissant.svg" alt="Ilustrasi croissant mentega">
      <div class="kartu-menu-isi">
        <h3 class="kartu-menu-nama">Croissant Mentega</h3>
        <span class="kartu-menu-harga">Rp16.000</span>
        <p class="kartu-menu-deskripsi">Dipanggang pagi, renyah sepanjang hari.</p>
      </div>
    </article>
  </div>
</section>

Bedah Aturan Kunci #

  1. .hero (flex 55/45): dua deklarasi flex: 1 1 55% dan flex 1 1 45% menjumlah basis menjadi 100%; ditambah gap 48px, total melebihi konten wadah (1072px) sehingga kedua item menyusut sebanding. Hasil akhirnya 563.2px dan 460.8px — basis persen tetap terbaca di panel computed, sedangkan width memperlihatkan hasil akhirnya.
  2. .gambar-hero: aspect-ratio: 4 / 3 dengan width: 100% membuat tinggi kotak dihitung otomatis: 460.8 × 3/4 = 345.6px. Gambar object-fit: cover mengisi kotak tanpa melar.
  3. .judul-hero: clamp(32px, 16px + 3vw, 52px); pada 1280px nilai tengahnya 16 + 38.4 = 54.4px sehingga yang berlaku batas atas 52px, dengan line-height 59.8px.
  4. .keunggulan-item: flex: 1 adalah singkatan flex: 1 1 0% — basis nol membuat lebar tidak dipengaruhi isi teks; ketiga kartu murni berbagi ruang secara sama.
  5. .grid-menu: repeat(3, 1fr) di konten 1072px dengan gap 24px memberi ruang per track (1072 − 48) / 3 = 341,33px. Sisa pecahan piksel tidak dibagi rata: browser menampilkannya sebagai 341.328px 341.328px 341.344px — track terakhir menerima kelebihan satu piksel perangkat. Inilah nilai yang dijanjikan halaman uji.

Uji Bagian Ini #

Halaman uji bagian ini tersedia di /tests/css/proyek/landing-page-responsif/flexbox-dan-grid.html , dengan spesifikasi lengkap pada tests/flexbox-dan-grid.spec.js. Asersi yang diverifikasi:

ElemenPropertiDiharapkan
.herogap48px
.heroalign-itemscenter
.hero-teksflex-basis55%
.hero-tekswidth563.2px
.hero-gambarwidth460.8px
.gambar-heroaspect-ratio4 / 3
.gambar-heroheight345.6px
.judul-herofont-size52px
.judul-heroline-height59.8px
.keunggulan-itemflex-basis0%
.grid-menugrid-template-columns341.328px 341.328px 341.344px
.kartu-menuborder-radius12px

Berkas Sumber

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

styles/landing.css

Unduh berkas
css
/* =========================================================
   Proyek: Landing Page Responsif — styles/landing.css
   File utuh proyek yang berkembang mengikuti empat bagian:
   Bagian 1 (Visual Foundation), Bagian 2 (Flexbox dan Grid),
   Bagian 3 (Responsive Navigation), Bagian 4 (Animations dan
   Polish). Komentar "Bagian N" menandai kelompok aturan yang
   dibahas pada setiap bagian.
   ========================================================= */

/* Bagian 1: Visual Foundation ----------------------------- */

:root {
  /* Warna semantik — brand kedai Kopi Senja */
  --warna-primer: #c2410c;
  --warna-primer-tua: #9a3412;
  --warna-latar: #fffbeb;
  --warna-permukaan: #ffffff;
  --warna-teks: #292524;
  --warna-teks-lembut: #57534e;
  --warna-teks-balik: #ffffff;
  --warna-garis: #e7e5e4;

  /* Skala spasi */
  --spasi-1: 4px;
  --spasi-2: 8px;
  --spasi-3: 12px;
  --spasi-4: 16px;
  --spasi-6: 24px;
  --spasi-8: 32px;
  --spasi-12: 48px;
  --spasi-16: 64px;

  /* Bentuk */
  --radius-sedang: 12px;
  --radius-besar: 16px;
  --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);
}

.wadah {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 var(--spasi-6);
}

.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;
  /* Bagian 4: menambah baris transition di bawah ini */
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.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-3);
  border-radius: var(--radius-penuh);
  background-color: rgb(194 65 12 / 10%);
  color: var(--warna-primer-tua);
  font-size: 14px;
  font-weight: 600;
  /* Bagian 4: menambah baris animation di bawah ini */
  animation: denyut 2s ease-in-out infinite;
}

.judul-bagian {
  font-size: 28px;
  font-weight: 700;
}

.deskripsi-bagian {
  margin-top: var(--spasi-2);
  max-width: 560px;
  color: var(--warna-teks-lembut);
}

/* Bagian 2: Flexbox dan Grid ------------------------------ */

.hero {
  display: flex;
  align-items: center;
  gap: var(--spasi-12);
  padding: var(--spasi-16) 0;
}

.hero-teks {
  flex: 1 1 55%;
}

.hero-gambar {
  flex: 1 1 45%;
  display: flex;
}

.gambar-hero {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-besar);
  object-fit: cover;
}

.judul-hero {
  font-size: clamp(32px, 16px + 3vw, 52px);
  line-height: 1.15;
}

.teks-aksen {
  color: var(--warna-primer);
}

.deskripsi-hero {
  margin-top: var(--spasi-4);
  font-size: 18px;
  color: var(--warna-teks-lembut);
}

.hero-aksi {
  display: flex;
  gap: var(--spasi-4);
  margin-top: var(--spasi-6);
}

.keunggulan {
  display: flex;
  gap: var(--spasi-6);
}

.keunggulan-item {
  flex: 1;
  padding: var(--spasi-6);
  background-color: var(--warna-permukaan);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-sedang);
}

.keunggulan-judul {
  font-size: 18px;
  font-weight: 600;
}

.keunggulan-isi {
  margin-top: var(--spasi-2);
  font-size: 14px;
  color: var(--warna-teks-lembut);
}

.grid-menu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spasi-6);
  margin-top: var(--spasi-8);
}

.kartu-menu {
  display: flex;
  flex-direction: column;
  background-color: var(--warna-permukaan);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-sedang);
  overflow: hidden;
  /* Bagian 4: menambah baris transition di bawah ini */
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.gambar-menu {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.kartu-menu-isi {
  display: grid;
  gap: var(--spasi-2);
  padding: var(--spasi-6);
}

.kartu-menu-nama {
  font-size: var(--teks-lg);
  font-weight: 600;
}

.kartu-menu-harga {
  color: var(--warna-primer);
  font-weight: 600;
}

.kartu-menu-deskripsi {
  font-size: 14px;
  color: var(--warna-teks-lembut);
}

/* Bagian 3: Responsive Navigation ------------------------- */

.navigasi {
  position: sticky;
  top: 0;
  z-index: 20;
  background-color: rgb(255 251 235 / 90%);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--warna-garis);
}

.navigasi-isi {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spasi-6);
  height: 64px;
}

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

.navigasi-tautan:focus-visible {
  outline: 2px solid var(--warna-primer);
  outline-offset: 2px;
}

/* Checkbox hamburger disembunyikan secara visual, tetap
   bisa di-fokus dan dibaca screen reader */
.menu-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.tombol-menu {
  display: none;
  padding: var(--spasi-2) var(--spasi-4);
  border: 1px solid var(--warna-garis);
  border-radius: var(--radius-sedang);
  background-color: var(--warna-permukaan);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
}

/* Fokus keyboard dari checkbox yang tersembunyi ditunjukkan
   pada label hamburger di sebelahnya */
.menu-toggle:focus-visible + .tombol-menu {
  outline: 2px solid var(--warna-primer);
  outline-offset: 2px;
}

@media (max-width: 899px) {
  .hero {
    flex-direction: column;
    align-items: flex-start;
  }

  .hero-gambar {
    width: 100%;
  }

  .keunggulan {
    flex-direction: column;
  }

  .grid-menu {
    grid-template-columns: 1fr;
  }

  .tombol-menu {
    display: inline-flex;
  }

  .navigasi-menu {
    display: none;
    position: absolute;
    top: 64px;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: var(--spasi-1);
    padding: var(--spasi-4) var(--spasi-6);
    background-color: var(--warna-permukaan);
    border-bottom: 1px solid var(--warna-garis);
  }

  /* Panel menu terbuka tanpa JavaScript memakai :checked */
  .menu-toggle:checked ~ .navigasi-menu {
    display: flex;
  }
}

/* Bagian 4: Animations dan Polish ------------------------- */

@keyframes denyut {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.06);
    opacity: 0.85;
  }
}

.tombol-primer:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgb(154 52 18 / 25%);
}

.tombol-kedua:hover {
  transform: translateY(-2px);
  background-color: rgb(194 65 12 / 8%);
}

.kartu-menu:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 24px rgb(41 37 36 / 12%);
}

.pita-cta {
  margin-top: var(--spasi-16);
  padding: var(--spasi-12) var(--spasi-8);
  background-color: var(--warna-primer);
  color: var(--warna-teks-balik);
  text-align: center;
  border-radius: var(--radius-besar);
  box-shadow: 0 12px 24px rgb(154 52 18 / 25%);
}

.pita-cta-judul {
  font-size: 24px;
  font-weight: 700;
}

.pita-cta-isi {
  margin-top: var(--spasi-2);
}

.kaki {
  display: flex;
  justify-content: space-between;
  gap: var(--spasi-4);
  margin-top: var(--spasi-16);
  padding: var(--spasi-8) 0;
  border-top: 1px solid var(--warna-garis);
  font-size: 14px;
  color: var(--warna-teks-lembut);
}

html {
  scroll-behavior: smooth;
}

/* Pengguna yang mengaktifkan reduce motion menerima halaman
   tanpa animasi: keyframes dan transition dimatikan */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .badge {
    animation: none;
  }

  .tombol,
  .kartu-menu {
    transition: none;
  }
}

tests/animations-dan-polish.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Landing Page Responsif
// Bagian 4: Animations dan Polish
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720):
//   /tests/css/proyek/landing-page-responsif/animations-dan-polish.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Guliran antarbagian bergerak halus lewat scroll-behavior.
  { elemen: "html", properti: "scroll-behavior", diharapkan: "smooth" },

  // Tombol bertransisi pada properti transform dan box-shadow.
  { elemen: ".tombol-primer", properti: "transition-property", diharapkan: "transform, box-shadow" },
  { elemen: ".tombol-primer", properti: "transition-duration", diharapkan: "0.2s, 0.2s" },

  // Kartu menu bertransisi dengan nilai yang sama seperti tombol.
  { elemen: ".kartu-menu", properti: "transition-property", diharapkan: "transform, box-shadow" },
  { elemen: ".kartu-menu", properti: "transition-duration", diharapkan: "0.2s, 0.2s" },

  // Badge berdenyut: keyframes denyut, 2 detik, tanpa henti.
  { elemen: ".badge", properti: "animation-name", diharapkan: "denyut" },
  { elemen: ".badge", properti: "animation-duration", diharapkan: "2s" },
  { elemen: ".badge", properti: "animation-timing-function", diharapkan: "ease-in-out" },
  { elemen: ".badge", properti: "animation-iteration-count", diharapkan: "infinite" },

  // Pita CTA berlatar primer dengan bayangan kehangatan primer-tua.
  { elemen: ".pita-cta", properti: "background-color", diharapkan: "rgb(194, 65, 12)" },
  { elemen: ".pita-cta", properti: "color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".pita-cta", properti: "border-radius", diharapkan: "16px" },
  { elemen: ".pita-cta", properti: "text-align", diharapkan: "center" },
  { elemen: ".pita-cta", properti: "box-shadow", diharapkan: "rgba(154, 52, 18, 0.25) 0px 12px 24px 0px" },

  // Judul pita CTA 24px dengan bobot 700.
  { elemen: ".pita-cta-judul", properti: "font-size", diharapkan: "24px" },
  { elemen: ".pita-cta-judul", properti: "font-weight", diharapkan: "700" },

  // Footer dua sisi: flex dengan ruas terpisah dan garis atas.
  { elemen: ".kaki", properti: "display", diharapkan: "flex" },
  { elemen: ".kaki", properti: "justify-content", diharapkan: "space-between" },
  { elemen: ".kaki", properti: "border-top-width", diharapkan: "1px" },
];

tests/flexbox-dan-grid.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Landing Page Responsif
// Bagian 2: Flexbox dan Grid
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720):
//   /tests/css/proyek/landing-page-responsif/flexbox-dan-grid.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Hero adalah flex container dengan item terpusat vertikal.
  { elemen: ".hero", properti: "display", diharapkan: "flex" },
  { elemen: ".hero", properti: "align-items", diharapkan: "center" },
  { elemen: ".hero", properti: "gap", diharapkan: "48px" },

  // Teks hero: basis 55% yang menyusut menjadi 563.2px akibat gap.
  { elemen: ".hero-teks", properti: "flex-grow", diharapkan: "1" },
  { elemen: ".hero-teks", properti: "flex-shrink", diharapkan: "1" },
  { elemen: ".hero-teks", properti: "flex-basis", diharapkan: "55%" },
  { elemen: ".hero-teks", properti: "width", diharapkan: "563.2px" },

  // Gambar hero: basis 45%, lebar akhir 460.8px.
  { elemen: ".hero-gambar", properti: "flex-basis", diharapkan: "45%" },
  { elemen: ".hero-gambar", properti: "width", diharapkan: "460.8px" },

  // Kotak gambar menjaga rasio 4:3 sehingga tingginya 345.6px.
  { elemen: ".gambar-hero", properti: "aspect-ratio", diharapkan: "4 / 3" },
  { elemen: ".gambar-hero", properti: "height", diharapkan: "345.6px" },
  { elemen: ".gambar-hero", properti: "border-radius", diharapkan: "16px" },

  // Judul hero fluid mencapai batas atas clamp 52px pada 1280px.
  { elemen: ".judul-hero", properti: "font-size", diharapkan: "52px" },
  { elemen: ".judul-hero", properti: "line-height", diharapkan: "59.8px" },

  // Baris tombol aksi adalah flex dengan gap 16px.
  { elemen: ".hero-aksi", properti: "display", diharapkan: "flex" },
  { elemen: ".hero-aksi", properti: "gap", diharapkan: "16px" },

  // Item keunggulan memakai flex: 1 = grow 1, shrink 1, basis 0%.
  { elemen: ".keunggulan-item", properti: "flex-grow", diharapkan: "1" },
  { elemen: ".keunggulan-item", properti: "flex-shrink", diharapkan: "1" },
  { elemen: ".keunggulan-item", properti: "flex-basis", diharapkan: "0%" },

  // Grid menu tiga kolom sama lebar dengan gap 24px.
  { elemen: ".grid-menu", properti: "display", diharapkan: "grid" },
  { elemen: ".grid-menu", properti: "gap", diharapkan: "24px" },
  { elemen: ".grid-menu", properti: "grid-template-columns", diharapkan: "341.328px 341.328px 341.344px" },

  // Kartu menu berlatar permukaan putih dengan radius 12px.
  { elemen: ".kartu-menu", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".kartu-menu", properti: "border-radius", diharapkan: "12px" },
  { elemen: ".kartu-menu", properti: "display", diharapkan: "flex" },
];

tests/responsive-navigation.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Landing Page Responsif
// Bagian 3: Responsive Navigation
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720):
//   /tests/css/proyek/landing-page-responsif/responsive-navigation.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: "20" },

  // Latar krem semi transparan 90% dengan efek blur di belakangnya.
  { elemen: ".navigasi", properti: "background-color", diharapkan: "rgba(255, 251, 235, 0.9)" },
  { elemen: ".navigasi", properti: "backdrop-filter", diharapkan: "blur(8px)" },

  // Baris navigasi setinggi 64px dengan ruas logo dan menu terpisah.
  { elemen: ".navigasi-isi", properti: "height", diharapkan: "64px" },
  { elemen: ".navigasi-isi", properti: "justify-content", diharapkan: "space-between" },
  { elemen: ".navigasi-isi", properti: "gap", diharapkan: "24px" },
  { elemen: ".navigasi-isi", properti: "display", diharapkan: "flex" },

  // Logo memakai ukuran 20px dengan bobot 700.
  { elemen: ".logo", properti: "font-size", diharapkan: "20px" },
  { elemen: ".logo", properti: "font-weight", diharapkan: "700" },

  // Menu tersusun horizontal dengan jarak antartautan 24px.
  { elemen: ".navigasi-menu", properti: "display", diharapkan: "flex" },
  { elemen: ".navigasi-menu", properti: "gap", diharapkan: "24px" },

  // Tautan memakai warna teks lembut tanpa garis bawah.
  { elemen: ".navigasi-tautan", properti: "color", diharapkan: "rgb(87, 83, 78)" },
  { elemen: ".navigasi-tautan", properti: "font-weight", diharapkan: "500" },
  { elemen: ".navigasi-tautan", properti: "text-decoration-line", diharapkan: "none" },

  // Checkbox hamburger disembunyikan secara visual.
  { elemen: ".menu-toggle", properti: "position", diharapkan: "absolute" },
  { elemen: ".menu-toggle", properti: "opacity", diharapkan: "0" },
  { elemen: ".menu-toggle", properti: "width", diharapkan: "1px" },

  // Tombol hamburger hanya tampil pada breakpoint 899px ke bawah.
  { elemen: ".tombol-menu", properti: "display", diharapkan: "none" },
];

tests/visual-foundation.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Landing Page Responsif
// Bagian 1: Visual Foundation
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720):
//   /tests/css/proyek/landing-page-responsif/visual-foundation.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // body mengonsumsi token: latar krem hangat dan teks stone-800.
  { elemen: "body", properti: "font-size", diharapkan: "16px" },
  { elemen: "body", properti: "line-height", diharapkan: "25.6px" },
  { elemen: "body", properti: "color", diharapkan: "rgb(41, 37, 36)" },
  { elemen: "body", properti: "background-color", diharapkan: "rgb(255, 251, 235)" },

  // Wadah konten dibatasi 1120px dan terpusat di viewport 1280px.
  { elemen: ".wadah", properti: "max-width", diharapkan: "1120px" },
  { elemen: ".wadah", properti: "margin-left", diharapkan: "80px" },

  // Judul bagian memakai ukuran 28px dengan bobot 700.
  { elemen: ".judul-bagian", properti: "font-size", diharapkan: "28px" },
  { elemen: ".judul-bagian", properti: "font-weight", diharapkan: "700" },

  // Deskripsi bagian memakai warna teks lembut dan lebar terbatas.
  { elemen: ".deskripsi-bagian", properti: "color", diharapkan: "rgb(87, 83, 78)" },
  { elemen: ".deskripsi-bagian", properti: "max-width", diharapkan: "560px" },

  // Tombol primer: latar oranye terbakar, teks putih, padding dari token.
  { elemen: ".tombol-primer", properti: "background-color", diharapkan: "rgb(194, 65, 12)" },
  { 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: "12px" },

  // 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(194, 65, 12)" },
  { elemen: ".tombol-kedua", properti: "border-top-color", diharapkan: "rgb(194, 65, 12)" },

  // Badge memakai warna primer dengan alpha 10% dan radius penuh.
  { elemen: ".badge", properti: "background-color", diharapkan: "rgba(194, 65, 12, 0.1)" },
  { elemen: ".badge", properti: "color", diharapkan: "rgb(154, 52, 18)" },
  { elemen: ".badge", properti: "border-radius", diharapkan: "999px" },
  { elemen: ".badge", properti: "font-size", diharapkan: "14px" },
];