Landing page yang baik bergerak secukupnya: tombol memberi umpan balik saat disentuh, badge menghadirkan kedipan kecil yang menarik perhatian ke pesan utama, dan guliran antarbagian terasa halus. Pada bagian terakhir ini kita menambahkan tiga lapis polish ke styles/landing.css: transition pada tombol dan kartu menu, @keyframes pada badge, serta scroll-behavior: smooth untuk tautan navigasi. Setiap lapis animasi kemudian dibungkus prefers-reduced-motion — pengguna yang meminta gerak dikurangi menerima halaman yang tenang. Pola aksesibilitas ini dibahas tuntas pada chapter Prefers Reduced Motion .

Tujuan Bagian dan Prasyarat #

Target bagian ini:

  • .tombol dan .kartu-menu bertransisi pada transform dan box-shadow selama 0.2 detik.
  • Badge beranimasi denyut 2 detik tanpa henti dengan pelan gerak ease-in-out.
  • Pita CTA berlatar primer dengan bayangan kehangatan rgb(154 52 18 / 25%).
  • Guliran antarbagian memakai scroll-behavior: smooth dan dimatikan bila pengguna memilih reduce motion.

Konsep pendukungnya: Transition Properties , Transition Timing , Keyframe Animations , Box Shadow , dan Animation Performance .

Menganimasikan Interaksi #

Polish pertama menyentuh aturan yang sudah ada — kita menambahkan baris, bukan menduplikasi selector. Perbarui .tombol dari Bagian 1 dan .kartu-menu dari Bagian 2:

css styles/landing.css — Bagian 4 (transition)
.tombol {
  /* ...aturan Bagian 1 tetap... */
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.kartu-menu {
  /* ...aturan Bagian 2 tetap... */
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.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, Keyframes, dan Guliran Halus #

Tiga aturan baru ditambahkan pada kelompok Bagian 4, di ujung file. Pita CTA menutup halaman dengan ajakan bertindak; badge dari Bagian 1 mendapat animasinya di sini:

css styles/landing.css — Bagian 4 (pita, keyframes, guliran)
@keyframes denyut {
  0%, 100% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.06);
    opacity: 0.85;
  }
}

.badge {
  /* ...aturan Bagian 1 tetap... */
  animation: denyut 2s ease-in-out infinite;
}

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

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

Hasil di Browser:

text
Tombol dan kartu menu bertransisi pada properti "transform,
box-shadow" selama 0.2s, 0.2s. Badge menjalankan keyframes
"denyut" selama 2s dengan ease-in-out tanpa henti. Pita CTA
berlatar rgb(194, 65, 12) dengan bayangan
rgba(154, 52, 18, 0.25) 0px 12px 24px 0px. Nilai computed
scroll-behavior pada html adalah smooth.

Menghormati Preferensi Gerak #

Lapis terakhir adalah pengaman aksesibilitas. Aturan di bawah ini membungkus seluruh gerak halaman dalam satu media query:

css styles/landing.css — Bagian 4 (reduced motion)
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .badge {
    animation: none;
  }

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

Bedah Aturan Kunci #

  1. transition pada .tombol: dua properti sekaligus dianimasikan — transform dan box-shadow. Computed transition-property terbaca sebagai daftar transform, box-shadow dengan durasi 0.2s, 0.2s. Keduanya termasuk properti yang dianimasikan di compositor, sehingga gerakannya tidak memicu layout ulang.
  2. :hover mengangkat: translateY(-2px) memberi kesan ditekan-mundur yang ringan. Karena transition terpasang di kelas dasar, kedua varian tombol bergerak tanpa deklarasi tambahan.
  3. @keyframes denyut: frame 0% dan 100% sama dengan kondisi diam (scale(1), opacity: 1) sehingga loop tanpa lompatan; frame 50% membesar 6% dan meredup sejenak. Perubahan transform dan opacity saja menjaga animasi tetap murah.
  4. animation: denyut 2s ease-in-out infinite: shorthand mengisi name, duration, timing function, dan iteration count sekaligus — computed animation-iteration-count terbaca infinite.
  5. box-shadow pita CTA: nilai 0 12px 24px rgb(154 52 18 / 25%) diserialisasi browser menjadi rgba(154, 52, 18, 0.25) 0px 12px 24px 0px — warna di depan, lalu offset-x, offset-y, blur, dan spread.
  6. scroll-behavior: smooth pada html: seluruh tautan anchor (#menu, #kontak) bergulir halus. Pada media query reduce motion, nilainya kembali auto — guliran melompat langsung seperti biasa.

Uji Bagian Ini #

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

ElemenPropertiDiharapkan
htmlscroll-behaviorsmooth
.tombol-primertransition-propertytransform, box-shadow
.tombol-primertransition-duration0.2s, 0.2s
.kartu-menutransition-propertytransform, box-shadow
.badgeanimation-namedenyut
.badgeanimation-duration2s
.badgeanimation-timing-functionease-in-out
.badgeanimation-iteration-countinfinite
.pita-ctabox-shadowrgba(154, 52, 18, 0.25) 0px 12px 24px 0px
.pita-ctaborder-radius16px

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