/* ============================================================
   Zeinuar Kusumatresna — Profil Personal
   Aurora gelap + panel kaca. Vanilla CSS, tanpa framework.

   Efek di sini meniru template portofolio berbayar (scroll reveal,
   parallax, marquee, panel kaca) TAPI dikerjakan dengan CSS native
   scroll-driven animation, bukan GSAP + Lenis (~300 KB JavaScript).
   Halaman ini berargumen soal kecepatan; memuat 300 KB pustaka
   animasi untuk membuktikannya akan membatalkan argumennya sendiri.

   Semua animasi bersifat PENYEMPURNAAN. Di peramban yang belum
   mendukung animation-timeline (Firefox), halaman tetap tampil utuh
   tanpa gerak — tidak ada konten yang hilang.
   ============================================================ */

/* ---------- 1. TOKEN ---------- */
:root {
  --bg:        #07080D;
  --bg-2:      #0B0D14;
  --ink:       #E9ECF3;
  --ink-soft:  #A2AAC0;
  --ink-faint: #6B7385;

  --glass:      rgba(255, 255, 255, .045);
  --glass-hi:   rgba(255, 255, 255, .075);
  --glass-line: rgba(255, 255, 255, .10);

  --accent:   #7C9CFF;
  --accent-2: #4DE0C8;
  --accent-3: #C084FC;
  --warn:     #FBBF24;

  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, system-ui, sans-serif;
  --font-mono: ui-monospace, 'JetBrains Mono', 'SF Mono', 'Cascadia Code', Menlo, monospace;

  --fs-h1:   clamp(2.05rem, 1.05rem + 3.3vw, 3.6rem);
  --fs-h2:   clamp(1.6rem, 1.2rem + 1.9vw, 2.6rem);
  --fs-h3:   clamp(1.08rem, 1rem + .4vw, 1.3rem);
  --fs-lead: clamp(1.05rem, .98rem + .5vw, 1.32rem);
  --fs-sm:   .885rem;
  --fs-xs:   .78rem;

  --gap:       clamp(1rem, .8rem + 1vw, 1.6rem);
  --section-y: clamp(3.8rem, 2.6rem + 5vw, 7rem);
  --radius:    18px;
  --maxw:      1320px;
}

*, *::before, *::after { box-sizing: border-box; }

/* ---------- 2. LATAR AURORA ---------- */
/* Tiga gumpalan cahaya yang bergeser sangat lambat. Dipasang di
   ::before/::after supaya hanya transform yang beranimasi — properti
   itu ditangani compositor, jadi tidak memicu layout ulang. */
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  line-height: 1.68;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  position: relative;
}

/* filter: blur() SENGAJA TIDAK DIPAKAI di sini.
   Sebelumnya kedua lapisan ini diburamkan 90px, dan itu memaksa peramban
   meraster ulang tekstur seukuran layar penuh setiap frame saat digulir —
   penyebab terbesar scroll terasa berat di HP.

   Radial-gradient dengan perhentian yang landai sudah menghasilkan tepi
   selembut hasil blur, tanpa biaya raster sama sekali. Sisa banding
   halusnya ditutup lapisan .grain. */
body::before, body::after {
  content: '';
  position: fixed;
  inset: -30% -20%;
  z-index: -2;
  pointer-events: none;
  opacity: .55;
}
body::before {
  background:
    radial-gradient(46% 50% at 22% 18%, rgba(124, 156, 255, .40) 0%, rgba(124, 156, 255, .16) 42%, transparent 72%),
    radial-gradient(42% 46% at 78% 12%, rgba(192, 132, 252, .30) 0%, rgba(192, 132, 252, .12) 45%, transparent 74%);
  animation: drift-a 26s ease-in-out infinite alternate;
}
body::after {
  background:
    radial-gradient(48% 48% at 68% 72%, rgba(77, 224, 200, .24) 0%, rgba(77, 224, 200, .10) 45%, transparent 74%),
    radial-gradient(38% 42% at 12% 78%, rgba(124, 156, 255, .22) 0%, rgba(124, 156, 255, .09) 45%, transparent 74%);
  animation: drift-b 32s ease-in-out infinite alternate;
}
@keyframes drift-a { to { transform: translate3d(4%, 3%, 0) scale(1.12); } }
@keyframes drift-b { to { transform: translate3d(-5%, -4%, 0) scale(1.08); } }

/* Lapisan kedalaman: gumpalan cahaya buram yang bergerak lebih lambat
   dari isi halaman saat digulir — inti dari rasa parallax. Dibuat dari
   gradien, bukan gambar: foto latar yang layak akan menambah 200-400 KB
   pada halaman yang seluruhnya baru 37 KB, dan mematikan klaim 0,4 detik
   yang justru jadi argumen utama situs ini. */
.depth {
  position: fixed; inset: -15% -10% auto; height: 130%; z-index: -1;
  pointer-events: none; opacity: .5;
  background:
    radial-gradient(34% 30% at 18% 28%, rgba(124, 156, 255, .34) 0%, rgba(124, 156, 255, .12) 46%, transparent 72%),
    radial-gradient(30% 28% at 82% 44%, rgba(192, 132, 252, .28) 0%, rgba(192, 132, 252, .10) 48%, transparent 74%),
    radial-gradient(32% 30% at 42% 76%, rgba(77, 224, 200, .22) 0%, rgba(77, 224, 200, .08) 48%, transparent 74%);
}
@supports (animation-timeline: scroll()) {
  .depth { animation: parallax linear both; animation-timeline: scroll(root); }
}
@keyframes parallax { to { transform: translateY(-14%) scale(1.1); } }

/* Butiran halus supaya gradien tidak terlihat seperti pita warna (banding) */
body > .grain {
  position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: .22;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}

/* ---------- 3. TIPOGRAFI ---------- */
h1, h2, h3 { line-height: 1.12; letter-spacing: -.028em; margin: 0 0 .5em; font-weight: 650; }
h1 { font-size: var(--fs-h1); letter-spacing: -.042em; }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
p  { margin: 0 0 1em; }
a  { color: var(--accent); text-underline-offset: 3px; }

.wrap { max-width: var(--maxw); margin-inline: auto; padding-inline: clamp(1.1rem, 4vw, 2.5rem); position: relative; }
section { padding-block: var(--section-y); }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.eyebrow {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--accent-2); margin: 0 0 1rem;
  display: flex; align-items: center; gap: .8rem;
}
.eyebrow::after { content: ''; flex: 1; height: 1px;
  background: linear-gradient(90deg, var(--glass-line), transparent); }

/* ---------- 4. GANTI BAHASA ---------- */
/* Tautan biasa ke halaman lain, bukan penukar teks lewat JavaScript —
   supaya kedua bahasa benar-benar terindeks mesin pencari. */
.lang {
  position: absolute; top: 1.4rem; right: clamp(1.1rem, 4vw, 2.5rem);
  display: flex; gap: 1px; font-family: var(--font-mono); font-size: var(--fs-xs);
  border: 1px solid var(--glass-line); border-radius: 8px; overflow: hidden;
  background: rgba(19, 23, 33, .78);
}
.lang a { padding: .35rem .7rem; text-decoration: none; color: var(--ink-faint); }
.lang a[aria-current] { background: var(--glass-hi); color: var(--ink); }

/* Di layar sempit tombol bahasa yang melayang menimpa baris identitas
   (kata "Freelance" tertutup). Dikembalikan ke aliran normal, di atas teks. */
@media (max-width: 700px) {
  .lang { position: static; margin: 0 0 1.4rem; width: max-content; }
}

/* ---------- 4b. BILAH ATAS MENEMPEL ----------
   Pola .is-stuck diambil dari Coffeeshop. Tersembunyi selama hero masih
   terlihat supaya tidak menutupi judul, lalu turun setelah dilewati. */
.topbar {
  position: fixed; inset: 0 0 auto; z-index: 40;
  display: flex; align-items: center; gap: 1.4rem;
  padding: .7rem clamp(1.1rem, 4vw, 2.5rem);
  background: rgba(7, 8, 13, .72);
  backdrop-filter: blur(14px) saturate(1.3);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  border-bottom: 1px solid transparent;
  transform: translateY(-100%);
  opacity: 0;
  transition: transform .35s cubic-bezier(.22,.61,.36,1), opacity .3s ease, border-color .3s ease;
}
.topbar.is-stuck { transform: none; opacity: 1; border-bottom-color: var(--glass-line); }

.topbar__brand { display: inline-flex; align-items: center; gap: .55rem; }
.topbar__mark { width: 22px; height: 22px; flex: none; }
.topbar__name { font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink); white-space: nowrap; }
.topbar__nav { display: flex; gap: 1.3rem; margin-left: auto; }
.topbar__nav a { font-size: var(--fs-sm); text-decoration: none; color: var(--ink-faint);
  transition: color .2s ease; }
.topbar__nav a:hover, .topbar__nav a.is-active { color: var(--accent-2); }
.topbar .lang { position: static; }

@media (max-width: 700px) {
  .topbar__nav { display: none; }
  .topbar .lang { margin-left: auto; margin-bottom: 0; }
}

/* ---------- 5. HERO ---------- */
.hero { padding-block: clamp(2.2rem, 1.5rem + 2.4vw, 4.2rem) clamp(1.6rem, 1.2rem + 1.6vw, 2.6rem); }

/* Di HP, 91px ruang kosong di atas tulisan pertama adalah pemborosan
   layar yang paling mahal: satu-satunya layar yang pasti dilihat semua
   pengunjung. Bilah atas berposisi fixed dan tersembunyi saat di puncak,
   jadi tidak ada yang perlu dihindari di sana. */
@media (max-width: 700px) {
  .hero { padding-block: 1.6rem 1.4rem; }
  .hero__lead { margin-bottom: 1.6rem; }
  .hero__fig { margin-bottom: 1.6rem; }
  .lang { margin-bottom: 1rem; }
}

.hero__id { font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: .15em; text-transform: uppercase; color: var(--ink-faint); margin: 0 0 1.5rem; }
.hero__id b { color: var(--accent-2); font-weight: 500; }

.hero h1 { max-width: 15ch; margin-bottom: .4em; }
.hero h1 em { font-style: normal; }

/* Gradien dipasang pada tiap .word, BUKAN pada <em> induknya.
   background-clip:text hanya mengecat teks milik kotak itu sendiri;
   karena tiap kata adalah inline-block (dibutuhkan agar bisa dianimasi
   translateY), gradien induk tidak menembusnya dan teksnya jadi
   transparan di atas latar kosong — alias hilang sama sekali. */
.hero h1 em .word {
  background: linear-gradient(100deg, var(--accent), var(--accent-3) 60%, var(--accent-2));
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.hero__lead { font-size: var(--fs-lead); color: var(--ink-soft); max-width: 58ch; margin-bottom: 2.3rem; }

/* ---------- 5b. DIAGRAM ALUR SISTEM ----------
   Gambar pembuka sengaja berupa diagram, bukan ilustrasi dekoratif.
   Ilustrasi server isometrik terlihat seperti stok gambar dan tidak
   mengatakan apa-apa; diagram ini menunjukkan pekerjaannya langsung:
   beberapa sistem sumber masuk ke antrean, diproses aturan, dikirim ke
   tujuan, dan setiap langkah meninggalkan jejak audit.

   Inline SVG (~2 KB), bukan berkas gambar: tidak ada permintaan
   jaringan tambahan, tajam di layar kepadatan berapa pun, dan warnanya
   ikut token tema. */
/* Bagan ditempatkan paling depan dan selebar penuh — tidak lagi
   bersebelahan dengan blok teks. Alasannya: saat berdampingan, ia
   menyusut jadi setengah lebar dan tulisan di dalam kotaknya nyaris
   tidak terbaca di layar kecil. */
.hero__grid { display: block; }
.hero__fig { margin: 0 0 2.2rem; max-width: 620px; }

/* Di layar lebar bagan pindah ke samping judul. Saat ditumpuk, separuh
   kanan layar menganggur DAN hero jadi terlalu tinggi sehingga tombol
   ajakan terdorong jauh ke bawah lipatan — dua masalah dari satu sebab.

   Ambangnya 1100px, bukan lebih kecil: di bawah itu kolom bagan menyempit
   di bawah 480px dan tulisan di dalam kotaknya mulai sulit dibaca. */
@media (min-width: 1100px) {
  .hero__grid {
    display: grid;
    grid-template-columns: minmax(0, 1.02fr) minmax(440px, .98fr);
    column-gap: clamp(2rem, 1rem + 2.4vw, 3.4rem);
    row-gap: 0;
    align-items: center;
  }
  /* Penempatan diatur lewat grid, bukan dengan menukar urutan HTML:
     urutan DOM tetap runtut untuk pembaca layar dan untuk tampilan HP. */
  .hero__intro { grid-column: 1; grid-row: 1; align-self: end; }
  .hero__rest  { grid-column: 1; grid-row: 2; align-self: start; }
  .hero__fig   { grid-column: 2; grid-row: 1 / span 2; align-self: center; margin: 0; max-width: none; }
  .proof       { grid-column: 1 / -1; grid-row: 3; margin-top: 2.4rem; }
  .hero h1 { max-width: 17ch; }
  .hero__lead { margin: 1rem 0 1.5rem; max-width: 50ch; }
}

.hero__fig { margin: 0; }
.hero__fig figcaption { font-size: var(--fs-xs); color: var(--ink-faint);
  margin-top: .9rem; text-align: center; }
.flow { width: 100%; height: auto; display: block; overflow: visible; }
.flow__box { fill: rgba(19, 23, 33, .9); stroke: var(--glass-line); stroke-width: 1; }
.flow__box--hi { stroke: rgba(124, 156, 255, .55); }
/* Ukuran huruf dinaikkan dari 11px: SVG menyusut proporsional, jadi di
   layar 390px label lama tinggal setara 8px dan sulit dibaca. */
.flow__label { font-family: var(--font-mono); font-size: 13px; letter-spacing: .07em;
  fill: var(--ink-soft); text-transform: uppercase; }
.flow__label--hi { fill: var(--accent-2); }
.flow__sub { font-family: var(--font-mono); font-size: 10.5px; fill: var(--ink-faint); }
.flow__line { stroke: var(--glass-line); stroke-width: 1.2; fill: none; }
.flow__line--dash { stroke-dasharray: 3 4; }

/* Titik data mengalir MENGIKUTI garisnya, termasuk yang membelok.
   Dipakai offset-path: titik ditempatkan pada kurva yang sama persis
   dengan garis penghubung, lalu offset-distance dianimasikan 0-100%.
   Versi sebelumnya hanya menggeser lurus mendatar, sehingga di jalur yang
   menikung titiknya melayang keluar dari garis. */
.flow__dot { fill: var(--accent-2); offset-rotate: 0deg;
  animation: travel 3.4s linear infinite; }
@keyframes travel { from { offset-distance: 0%; } to { offset-distance: 100%; } }

/* Jalur tiap titik HARUS sama persis dengan garis penghubungnya.
   Kalau sebuah titik tidak punya offset-path, ia jatuh ke koordinat nol
   dan tampak nyasar di pojok kiri atas — itu yang sempat terjadi saat
   kotak baru ditambahkan tapi jalurnya belum ikut diperbarui. */
.flow__dot--a    { offset-path: path('M88 49 C118 49 106 76 118 76'); }
.flow__dot--a2   { offset-path: path('M88 49 C118 49 106 76 118 76'); animation-delay: -1.7s; fill: var(--accent); }
.flow__dot--b    { offset-path: path('M88 105 C118 105 106 76 118 76'); animation-delay: -.9s; fill: var(--accent); }
.flow__dot--v    { offset-path: path('M206 76 H226'); animation-duration: 1s; animation-delay: -.4s; }
.flow__dot--c    { offset-path: path('M318 76 H338'); animation-duration: 1s; animation-delay: -.7s; }
.flow__dot--appr { offset-path: path('M384 100 V140'); animation-duration: 1.5s; animation-delay: -.5s; fill: var(--accent); }
.flow__dot--tgt  { offset-path: path('M384 176 V212'); animation-duration: 1.5s; animation-delay: -1.1s; }
/* Jalur catatan: lebih pelan dan lebih redup — audit berjalan di latar,
   bukan bagian dari alur utama. */
.flow__dot--audit { offset-path: path('M272 100 C272 150 200 150 172 158'); animation-duration: 2.8s; fill: var(--ink-faint); }
.flow__dot--arch  { offset-path: path('M110 176 V212'); animation-duration: 2.2s; animation-delay: -1.4s; fill: var(--ink-faint); }

/* Denyut berbentuk kotak yang mengembang keluar dari simpul tujuan.
   transform-box: fill-box membuat penskalaan berpusat pada kotak itu
   sendiri, bukan pada titik nol kanvas SVG. */
.flow__pulse-c { fill: none; stroke: var(--accent-2); stroke-width: 1.3;
  animation: pulse-c 2.6s ease-out infinite; }
.flow__pulse-c--2 { animation-delay: -1.3s; }
@keyframes pulse-c {
  0%   { r: 26; opacity: .8; }
  100% { r: 42; opacity: 0; }
}

/* Denyut di simpul: antrean berdenyut paling kuat karena di situlah
   pekerjaan menunggu giliran; aturan berdenyut lebih pelan. */
@keyframes pulse-ring { 0% { r: 5; opacity: .75; } 100% { r: 17; opacity: 0; } }
@keyframes pulse-ring-sm { 0% { r: 4; opacity: .55; } 100% { r: 13; opacity: 0; } }
.flow__pulse { fill: none; stroke: var(--accent-2); stroke-width: 1.2;
  animation: pulse-ring 2.6s ease-out infinite; }
.flow__pulse--slow  { animation-delay: -1.3s; }
.flow__pulse--rules { stroke: var(--accent); animation: pulse-ring-sm 3.4s ease-out infinite; animation-delay: -1s; }

@media (prefers-reduced-motion: reduce) {
  .flow__dot, .flow__pulse, .flow__pulse-c { animation: none !important; }
  .flow__pulse-c { opacity: .3; r: 30; }
  .flow__pulse { opacity: .25; }
  .flow__dot { offset-distance: 50%; }
}

/* Tiap kata muncul berurutan. --i diisi dari HTML. */
.word { display: inline-block; }
@supports (animation-timeline: view()) {
  .word { opacity: 0; transform: translateY(.4em); animation: word-in .5s ease-out forwards;
          animation-delay: calc(var(--i, 0) * 55ms); }
}
@keyframes word-in { to { opacity: 1; transform: none; } }

.hero__cta { display: flex; flex-wrap: wrap; gap: .8rem; }

/* Petunjuk gulir — keyframes scrollHint dipakai ulang dari Coffeeshop.
   Garis tipis yang memanjang-memendek, mengisyaratkan ada isi di bawah. */
.hero__scroll { margin-top: 2.6rem; display: flex; align-items: center; gap: .7rem;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--ink-faint); }
.hero__scroll-line { width: 1px; height: 34px; transform-origin: top;
  background: linear-gradient(to bottom, var(--accent-2), transparent);
  animation: scrollHint 2s ease-in-out infinite; }
@keyframes scrollHint {
  0%, 100% { transform: scaleY(.4); opacity: .4; }
  50%      { transform: scaleY(1);  opacity: 1; }
}
.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .85rem 1.5rem; border-radius: 11px;
  font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
  border: 1px solid transparent; transition: transform .16s ease, box-shadow .2s ease, border-color .2s ease;
}
.btn--solid { background: linear-gradient(100deg, var(--accent), var(--accent-3)); color: #0A0B12;
  box-shadow: 0 6px 26px rgba(124, 156, 255, .28); }
.btn--solid:hover { transform: translateY(-2px); box-shadow: 0 10px 34px rgba(124, 156, 255, .42); }
.btn--ghost { border-color: var(--glass-line); color: var(--ink); background: rgba(19, 23, 33, .7); }
.btn--ghost:hover { border-color: var(--accent); transform: translateY(-2px); }

/* Laptop 1366x768 hanya menyisakan sekitar 670px setelah bilah peramban.
   Alih-alih mengecilkan semua layar, tinggi layar dipakai sebagai syarat:
   hanya jendela pendek yang dirapatkan, layar besar tetap lega. */
@media (min-width: 1100px) and (max-height: 800px) {
  .hero { padding-block: 1.5rem 1.2rem; }
  .hero h1 { font-size: clamp(1.85rem, .9rem + 2.5vw, 2.9rem); max-width: 21ch; }
  .hero__lead { margin: .85rem 0 1.25rem; font-size: 1.02rem; }
  .hero__id { margin-bottom: .9rem; }
  .proof { margin-top: 1.6rem; }
}

/* ---------- 6. PANEL KACA ---------- */
/* backdrop-filter SENGAJA DIHAPUS dari sini.
   Dulu 15 kotak masing-masing memburamkan latarnya sendiri setiap frame.
   Padahal backdrop-filter baru berguna kalau ada DETAIL di belakangnya —
   yang ada di sini gradien halus, dan memburamkan gradien halus nyaris
   tidak mengubah apa pun secara visual. Biaya penuh, manfaat hampir nol.

   Digantikan latar semi-tembus yang sedikit lebih pekat: tampak sama,
   tapi hanya sekali digambar, bukan dihitung ulang tiap frame.

   contain: paint memberi tahu peramban bahwa tidak ada yang digambar
   melampaui kotak ini, sehingga area cat ulang bisa dipersempit. */
.glass {
  background: rgba(19, 23, 33, .62);
  border: 1px solid var(--glass-line);
  border-radius: var(--radius);
  position: relative; overflow: hidden;
  contain: paint;
}
/* Garis cahaya tipis di tepi atas — yang membuat panel terlihat "tebal" */
.glass::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 1px; z-index: 2;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.35), transparent);
}

/* ---------- 6b. GARIS TEPI YANG BERPUTAR ----------
   Seberkas cahaya mengelilingi tepi kotak. Dibuat dari conic-gradient
   yang sudutnya dianimasikan lewat @property, lalu di-mask agar hanya
   cincin selebar 1px yang tampak — bukan seluruh isi kotak.

   Sengaja TIDAK dipasang pada tombol: tombol perlu terbaca sebagai
   sesuatu yang ditekan, bukan sesuatu yang bergerak sendiri.

   Hanya berjalan saat kotaknya terlihat di layar (kelas .lit dipasang
   IntersectionObserver). Efek ini memicu repaint tiap frame; kalau
   kelima belas kotak berputar bersamaan, baterai HP yang menanggung. */
@property --ang { syntax: '<angle>'; initial-value: 0deg; inherits: false; }

@supports ((-webkit-mask-composite: xor) or (mask-composite: exclude)) {
  .glass::after {
    content: ''; position: absolute; inset: 0; border-radius: inherit;
    padding: 1px; pointer-events: none; z-index: 1;
    /* Busur terang dibuat pendek tapi pekat: cincin selebar 1px yang
       warnanya pucat praktis tidak terbaca di layar HP. */
    background: conic-gradient(from var(--ang),
      transparent 0deg 200deg,
      rgba(124, 156, 255, .85) 268deg,
      rgba(180, 200, 255, 1) 300deg,
      rgba(77, 224, 200, 1) 322deg,
      transparent 352deg);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
            mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude;
    opacity: 0; transition: opacity .5s ease;
  }
  .glass.lit::after { opacity: 1; animation: borderspin 9s linear infinite; }

  /* Kartu karya dan tanya jawab TIDAK ikut berputar. Keduanya berisi
     bacaan panjang; garis yang bergerak di tepinya menarik mata menjauh
     dari teks yang justru ingin dibaca. Efek ini disisakan untuk strip
     pencapaian dan kartu layanan, yang isinya pendek dan memang ingin
     menarik perhatian. */
  .bento .glass::after, .faq .glass::after { content: none; }

  /* Jeda berbeda tiap kartu supaya tidak berputar serempak */
  .svc .glass:nth-child(2).lit::after { animation-delay: -4s; }
  .svc .glass:nth-child(3).lit::after { animation-delay: -8s; }
  .svc .glass:nth-child(4).lit::after { animation-duration: 12s; animation-delay: -6s; }
  .svc .glass:nth-child(5).lit::after { animation-duration: 11s; animation-delay: -2s; }
}
@keyframes borderspin { to { --ang: 360deg; } }

/* Kolom ditetapkan tegas: 2 di HP, 4 di layar lebar. Versi auto-fit
   sebelumnya menghasilkan 3 + 1 di HP — sel keempat jatuh sendirian ke
   baris bawah dan terbaca seperti tata letak yang rusak.

   Pembatas dibuat dari gap 1px di atas latar garis, bukan border-right,
   supaya tetap rapi berapa pun jumlah barisnya. */
.proof { display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 1px; background: var(--glass-line); margin-top: 3rem; }
.proof__cell { padding: 1.2rem 1.25rem; background: rgba(11, 13, 20, .82); }

@media (min-width: 860px) {
  .proof { grid-template-columns: repeat(4, 1fr); }
  .proof__cell { padding: 1.35rem 1.4rem; }
}
/* Koefisien vw ditahan rendah: kata terpanjang di sel ini ("PostgreSQL")
   melewati lebar selnya di sekitar 700px kalau fontnya tumbuh lebih cepat. */
.proof__num { font-family: var(--font-mono); font-size: clamp(1.1rem, .8rem + .9vw, 1.85rem);
  font-weight: 600; letter-spacing: -.03em; display: block; line-height: 1.15;
  background: linear-gradient(100deg, var(--ink), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent; }
.proof__lbl { font-size: var(--fs-xs); color: var(--ink-faint); letter-spacing: .03em; }

/* Nama basis data bergulir bergantian. Tingginya dikunci satu baris dan
   isinya dipotong, jadi hanya satu nama tampak pada satu waktu. Item
   pertama diulang di akhir daftar supaya lompatan balik tidak terlihat. */
/* Gradien dipindah ke tiap <span> anak, BUKAN dibiarkan di .proof__num.
   background-clip:text hanya mengecat teks milik kotak itu sendiri; nama
   basis data berada di kotak anak, jadi gradien induk tidak menembusnya
   dan teksnya jadi transparan di atas latar kosong — hilang sama sekali.
   Persis bug yang sama pernah terjadi pada judul hero. */
.proof__rot { overflow: hidden; height: 1.25em; background: none; }
.proof__rot-track { display: block; animation: rotate-db 9s cubic-bezier(.76, 0, .24, 1) infinite; }
.proof__rot-track span { display: block; height: 1.25em; line-height: 1.25em;
  background: linear-gradient(100deg, var(--ink), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent; }
@keyframes rotate-db {
  0%, 26%   { transform: translateY(0); }
  33%, 59%  { transform: translateY(-1.25em); }
  66%, 92%  { transform: translateY(-2.5em); }
  100%      { transform: translateY(-3.75em); }
}
@media (prefers-reduced-motion: reduce) { .proof__rot-track { animation: none; } }

/* ---------- 7. MARQUEE ---------- */
/* Carousel dipindah jadi bilah mengambang di tepi bawah layar.
   Disembunyikan selama pengunjung masih di puncak halaman supaya kesan
   pertama tidak terganggu, lalu naik begitu mulai menggulir. */
.ticker {
  position: fixed; inset: auto 0 0; z-index: 35;
  background: rgba(7, 8, 13, .82);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transform: translateY(100%); opacity: 0;
  transition: transform .4s cubic-bezier(.22,.61,.36,1), opacity .3s ease;
  overflow: hidden; padding-block: .7rem; border-top: 1px solid var(--glass-line);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); }
/* Marquee dihentikan saat tidak terlihat. Animasinya memang murah karena
   hanya transform, tapi selama berjalan compositor tetap dibangunkan tiap
   frame — sia-sia kalau pengunjung sedang membaca bagian lain. */
.ticker.is-shown { transform: none; opacity: 1; }

.ticker__row { display: flex; gap: 2.6rem; width: max-content; animation: slide 76s linear infinite;
  animation-play-state: paused;
  font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--ink-faint); }
/* Hanya berjalan saat bilahnya benar-benar tampak. */
.ticker.is-shown .ticker__row { animation-play-state: running; }

.ticker__row span { display: flex; align-items: center; gap: 2.6rem; white-space: nowrap; }
.ticker__row span::after { content: '\25C6'; color: var(--accent-2); font-size: .6em; }
@keyframes slide { to { transform: translateX(-50%); } }
.ticker:hover .ticker__row { animation-play-state: paused; }

/* ---------- 8. BENTO ---------- */
.bento { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--gap); }
.tile { grid-column: span 12; padding: clamp(1.35rem, 1rem + 1.3vw, 2.1rem);
  display: flex; flex-direction: column; transition: transform .25s ease, border-color .25s ease; }
.tile:hover { transform: translateY(-3px); border-color: rgba(124, 156, 255, .45); }
.tile__tag { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent-2);
  letter-spacing: .12em; text-transform: uppercase; margin-bottom: .8rem;
  display: flex; align-items: center; gap: .55rem; }
/* Ikon digambar inline sebagai SVG stroke — tanpa font ikon dan tanpa
   permintaan jaringan tambahan. currentColor membuatnya otomatis
   mengikuti warna teks di sebelahnya. */
.ico { width: 15px; height: 15px; flex: none; stroke: currentColor;
  fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.svc__item h3 { display: flex; align-items: center; gap: .55rem; }
.svc__item h3 .ico { width: 17px; height: 17px; color: var(--accent-2); }
.tile p { color: var(--ink-soft); font-size: var(--fs-sm); }
.tile__foot { margin-top: auto; padding-top: 1rem; font-size: var(--fs-xs); }

@media (min-width: 780px) {
  .tile--lg { grid-column: span 7; }
  .tile--md { grid-column: span 5; }
  .tile--full { grid-column: span 12; }
}

/* Muncul saat masuk layar — pola dipinjam dari proyek Coffeeshop.
   Digerakkan IntersectionObserver, bukan animation-timeline, karena
   animation-timeline belum jalan di Firefox sementara IO didukung semua
   peramban modern — sekaligus memungkinkan jeda bertahap antar kartu.

   Keadaan tersembunyi hanya berlaku kalau kelas .js terpasang, jadi
   tanpa JavaScript seluruh isi tetap tampil. */
.js .reveal { opacity: 0; transform: translateY(28px);
  transition: opacity .7s cubic-bezier(.22,.61,.36,1), transform .7s cubic-bezier(.22,.61,.36,1); }
.js .reveal.is-visible { opacity: 1; transform: none; }

/* ---------- 9. JEJAK PERHITUNGAN ---------- */
.trace { font-family: var(--font-mono); font-size: var(--fs-sm);
  background: rgba(0, 0, 0, .28); border: 1px solid var(--glass-line);
  border-radius: 12px; padding: 1rem 1.15rem; margin: 1rem 0 0; }
.trace__row { display: flex; justify-content: space-between; gap: 1rem; padding: .34rem 0;
  border-bottom: 1px dashed rgba(255, 255, 255, .09); }
.trace__row:last-child { border-bottom: 0; }
.trace__row--total { border-top: 2px solid var(--glass-line); margin-top: .4rem;
  padding-top: .62rem; font-weight: 600; }
.trace__why { color: var(--ink-faint); font-size: var(--fs-xs); }
.trace__val { white-space: nowrap; }
.trace__val--minus { color: var(--warn); }
.trace__val--ok { color: var(--accent-2); }

/* ---------- 10. ERD ---------- */
.erd { display: flex; flex-direction: column; margin-top: 1rem;
  font-family: var(--font-mono); font-size: var(--fs-xs); align-items: flex-start; }
.erd__box { border: 1px solid var(--glass-line); border-radius: 9px; padding: .48rem .75rem;
  background: rgba(0, 0, 0, .3); white-space: nowrap; }
.erd__box b { display: block; color: var(--accent); font-weight: 600; }
.erd__link { color: var(--ink-faint); padding: .2rem 0 .2rem 1.2rem; line-height: 1.25; }

/* ---------- 11. BATANG SEBELUM/SESUDAH ---------- */
/* Panjang batang proporsional terhadap angka asli (5,0 dtk vs <1,0 dtk).
   Kalau dibuat sekadar enak dilihat, grafiknya berbohong — dan seluruh
   argumen halaman ini justru soal angka yang bisa dipertanggungjawabkan. */
.bars { margin-top: 1.3rem; display: flex; flex-direction: column; gap: .6rem; }
.bar__row { display: grid; grid-template-columns: 4.4rem 1fr auto; align-items: center; gap: .85rem; }
.bar__lbl { font-size: var(--fs-xs); color: var(--ink-faint); }
.bar__track { background: rgba(0, 0, 0, .32); border: 1px solid var(--glass-line);
  border-radius: 6px; height: 1.6rem; overflow: hidden; }
.bar__fill { display: block; height: 100%; }
.bar__fill--bad  { background: linear-gradient(90deg, rgba(251,191,36,.25), var(--warn)); }
.bar__fill--good { background: linear-gradient(90deg, rgba(77,224,200,.35), var(--accent-2)); }
.bar__val { font-size: var(--fs-sm); white-space: nowrap; }
.bar__val em { font-style: normal; color: var(--warn); font-size: var(--fs-xs); }

@supports (animation-timeline: view()) {
  .bar__fill { animation: grow linear both; animation-timeline: view(); animation-range: entry 10% cover 32%;
               transform-origin: left; }
}
@keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }

@media (max-width: 480px) {
  .bar__row { grid-template-columns: 3.6rem 1fr; }
  .bar__val { grid-column: 2; text-align: right; }
}

/* ---------- 11b. VISUAL KECIL DI KARTU KARYA ----------
   Semuanya menganimasi diri saat kartunya masuk layar, memanfaatkan
   kelas .is-visible yang sudah dipasang IntersectionObserver untuk
   reveal — tidak perlu pengamat kedua. Nilai akhir ditulis di HTML
   sebagai --h / --w, jadi tanpa JavaScript pun bentuknya tetap benar. */

/* Histogram sebaran harga */
.hist { display: flex; align-items: flex-end; gap: .5rem; height: 96px;
  margin-top: 1.1rem; padding: 0 .2rem .2rem; border-bottom: 1px solid var(--glass-line); }
.hist__col { flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%;
  justify-content: flex-end; gap: .4rem; }
.hist__bar { width: 100%; height: var(--h); border-radius: 4px 4px 0 0;
  background: linear-gradient(180deg, var(--accent-2), rgba(77, 224, 200, .25));
  transform: scaleY(0); transform-origin: bottom; }
.tile.is-visible .hist__bar { animation: bar-up .7s cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i) * 90ms); }
@keyframes bar-up { to { transform: scaleY(1); } }
.hist__x { font-family: var(--font-mono); font-size: .68rem; color: var(--ink-faint);
  position: absolute; transform: translateY(1.35rem); }
.hist { position: relative; margin-bottom: 1.9rem; }

/* Bagan kecil: sumber ke tabel singgah lalu tujuan */
/* Lebar dibatasi: SVG meregang penuh membuat huruf 11px ikut membesar
   jadi ~16px dan bagan terasa jauh lebih besar dari teks di sekitarnya. */
.mini { width: 100%; max-width: 340px; height: auto; margin-top: 1.1rem; overflow: visible; }
.mini__box { fill: rgba(0,0,0,.3); stroke: var(--glass-line); stroke-width: 1; }
.mini__box--hi { stroke: rgba(124,156,255,.5); }
.mini__line { stroke: var(--glass-line); stroke-width: 1.1; fill: none; }
.mini__line--dash { stroke-dasharray: 3 4; }
.mini__t { font-family: var(--font-mono); font-size: 11px; fill: var(--ink-soft); }
.mini__t--hi { fill: var(--accent-2); }
.mini__note { font-family: var(--font-mono); font-size: 9.5px; fill: var(--ink-faint); }
.mini__dot { fill: var(--accent-2); offset-rotate: 0deg; opacity: 0; }
.tile.is-visible .mini__dot { opacity: 1; animation: travel 2.6s linear infinite; }
.mini__dot--1 { offset-path: path('M74 34 C104 34 104 65 128 65'); }
.mini__dot--2 { offset-path: path('M74 96 C104 96 104 65 128 65'); animation-delay: -1.1s; fill: var(--accent); }
.mini__dot--3 { offset-path: path('M232 65 H244'); animation-duration: 1.2s; }

/* Tahapan modernisasi: porsi lama menyusut, baru mengambil alih */
.phase { display: flex; flex-direction: column; gap: .5rem; margin-top: 1.1rem; }
.phase__row { display: grid; grid-template-columns: 4.6rem 1fr; align-items: center; gap: .7rem; }
.phase__lbl { font-size: var(--fs-xs); color: var(--ink-faint); }
.phase__track { display: flex; height: 1.15rem; border-radius: 5px; overflow: hidden;
  background: rgba(0,0,0,.3); border: 1px solid var(--glass-line); }
.phase__old, .phase__new { width: 0; }
.phase__old { background: linear-gradient(90deg, rgba(251,191,36,.5), rgba(251,191,36,.22)); }
.phase__new { background: linear-gradient(90deg, rgba(77,224,200,.35), var(--accent-2)); }
.tile.is-visible .phase__old, .tile.is-visible .phase__new {
  animation: grow-w .8s cubic-bezier(.22,.61,.36,1) forwards;
  animation-delay: calc(var(--i) * 130ms); }
@keyframes grow-w { to { width: var(--w); } }

/* Anatomi nomor dokumen */
.dn { display: flex; flex-wrap: wrap; align-items: flex-start; gap: .3rem; margin-top: 1.2rem; }
.dn__seg { display: flex; flex-direction: column; align-items: center; gap: .3rem;
  padding: .45rem .6rem; border: 1px solid var(--glass-line); border-radius: 8px;
  background: rgba(0,0,0,.28); opacity: 0; transform: translateY(6px); }
.dn__seg b { font-family: var(--font-mono); font-size: .92rem; color: var(--accent-2); font-weight: 600; }
.dn__seg i { font-style: normal; font-family: var(--font-mono); font-size: .64rem;
  color: var(--ink-faint); letter-spacing: .04em; }
.tile.is-visible .dn__seg { animation: seg-in .5s ease-out forwards;
  animation-delay: calc(var(--i) * 110ms); }
@keyframes seg-in { to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .hist__bar { transform: none; animation: none !important; }
  .phase__old { width: var(--w); animation: none !important; }
  .phase__new { width: var(--w); animation: none !important; }
  .dn__seg { opacity: 1; transform: none; animation: none !important; }
  .mini__dot { opacity: 1; animation: none !important; offset-distance: 50%; }
}

/* ---------- 12. LAYANAN ---------- */
/* Lima kartu tidak pernah rata di grid biasa: 3 kolom menyisakan lubang,
   2 kolom menyisakan satu kartu sendirian di baris akhir — cacat yang
   sama seperti pada strip pencapaian sebelumnya.

   Diselesaikan dengan grid 6 kolom: tiga kartu pertama masing-masing
   selebar 2, dua kartu terakhir masing-masing selebar 3. Kedua barisnya
   penuh, tanpa sisa. */
.svc { display: grid; grid-template-columns: 1fr; gap: var(--gap); }

@media (min-width: 620px) and (max-width: 899px) {
  .svc { grid-template-columns: repeat(2, 1fr); }
  .svc__item:nth-child(5) { grid-column: span 2; }
}
@media (min-width: 900px) {
  .svc { grid-template-columns: repeat(6, 1fr); }
  .svc__item { grid-column: span 2; }
  .svc__item:nth-child(4), .svc__item:nth-child(5) { grid-column: span 3; }
}
.svc__item { padding: 1.6rem; }
.svc__price { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--accent-2); margin-bottom: .6rem; }
.svc__item ul { margin: .9rem 0 0; padding-left: 1.05rem; color: var(--ink-soft); font-size: var(--fs-sm); }
.svc__item li { margin-bottom: .32rem; }

/* ---------- 12b. TANYA JAWAB ----------
   Memakai <details>/<summary> bawaan peramban: bisa dibuka-tutup tanpa
   sebaris JavaScript, dan isinya tetap ada di HTML sehingga terbaca
   mesin pencari walau sedang tertutup. */
.faq { display: grid; gap: .7rem; margin-top: 2.2rem; }
.faq details { padding: 1.1rem 1.3rem; }
.faq summary {
  cursor: pointer; font-weight: 620; font-size: var(--fs-h3);
  list-style: none; display: flex; justify-content: space-between; gap: 1rem;
  align-items: center; color: var(--ink);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; font-family: var(--font-mono); color: var(--accent-2);
  font-size: 1.3em; line-height: 1; transition: transform .2s ease; }
.faq details[open] summary::after { transform: rotate(45deg); }
.faq p { color: var(--ink-soft); font-size: var(--fs-sm); margin: .9rem 0 0; max-width: 68ch; }

/* ---------- 13. KONTAK & FOOTER ---------- */
.contact h2 { max-width: 19ch; }
.contact__mail { font-family: var(--font-mono);
  font-size: clamp(1.05rem, .85rem + 1.1vw, 1.7rem); text-decoration: none;
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  border-bottom: 2px solid rgba(124, 156, 255, .35); }
.contact__mail:hover { border-bottom-color: var(--accent); }

/* Ruang bawah 5,5rem menampung bilah carousel yang mengambang di tepi
   layar. Sebelumnya ditulis sebagai aturan terpisah di atas, tapi
   padding-block di sini menimpanya sehingga footer tetap tertutup. */
/* ---------- LOGO DI HERO ---------- */
.hero__mark { width: 1.15em; height: 1.15em; vertical-align: -.28em; margin-right: .1em; }

/* ---------- BAGIAN KONTAK: 2 KOLOM + MARKA ---------- */
/* Sebelumnya kolom kiri terisi teks, kanan kosong lebar. Marka logo besar
   di kanan menyeimbangkannya, sekaligus menggemakan diagram di hero —
   halaman jadi berbingkai brand di atas dan bawah. */
.contact__grid { display: block; }
.contact__mark { display: none; }
@media (min-width: 900px) {
  .contact__grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
    align-items: center; gap: clamp(2rem, 1rem + 3vw, 4rem); }
  .contact__mark { display: block; }
}
.contact__mark svg { width: 100%; max-width: 320px; height: auto; margin: 0 auto; display: block;
  filter: drop-shadow(0 0 40px rgba(124, 156, 255, .12)); }

/* Denyut halus di node tujuan — sama seperti diagram hero */
@keyframes contact-pulse { 0% { r: 7; opacity: .7; } 100% { r: 20; opacity: 0; } }
.contact__pulse { animation: contact-pulse 2.8s ease-out infinite; }
@media (prefers-reduced-motion: reduce) { .contact__pulse { animation: none; opacity: .3; } }


footer { border-top: 1px solid var(--glass-line); padding-block: 2.2rem 5.5rem;
  font-size: var(--fs-xs); color: var(--ink-faint); }
footer .wrap { display: flex; flex-wrap: wrap; gap: .7rem 1.6rem; justify-content: space-between; align-items: center; }
.footer__copy { width: 100%; padding-top: .8rem; border-top: 1px solid var(--glass-line); color: var(--ink-faint); }

/* ---------- 14. AKSESIBILITAS ---------- */
:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; border-radius: 5px; }

/* Wajib: aurora yang bergerak terus bisa memicu mual pada sebagian orang. */
@media (prefers-reduced-motion: reduce) {
  /* Aurora, marquee, dan petunjuk gulir berjalan tanpa henti — itu yang
     paling memicu keluhan, jadi dimatikan sepenuhnya. */
  body::before, body::after, .ticker__row, .hero__scroll-line,
  .word, .bar__fill, .depth { animation: none !important; }
  .glass::after { display: none !important; }
  .tile:hover, .btn:hover { transform: none; }
  .word { opacity: 1; transform: none; }

  /* Reveal tetap ada tapi hanya pudar, tanpa perpindahan posisi —
     perlakuan yang sama seperti di Coffeeshop. */
  .js .reveal { transform: none; transition: opacity .45s ease; }
  .js .reveal.is-visible { opacity: 1; }
}
