/* ============================================================
   GUNEX MOTION — Lapisan animasi & mikro-interaksi bersama
   ------------------------------------------------------------
   TUJUAN: memberi kesan modern, halus, dan "mahal" pada seluruh
   aplikasi TANPA MENGUBAH SATU PUN TATA LETAK (layout).

   ATURAN EMAS yang dipatuhi berkas ini:
   1. Hanya properti yang TIDAK memicu reflow yang dianimasikan:
      transform, opacity, box-shadow, filter, background-color,
      border-color, color. Tidak pernah width/height/margin/padding/
      top/left/display/gap/font-size.
   2. Semua aturan "keadaan" (hover/aktif) memakai :where(...) supaya
      bobot selektornya NOL. Artinya: bila sebuah halaman sudah punya
      gaya sendiri untuk elemen itu, gaya halaman SELALU menang.
      Berkas ini hanya mengisi yang belum diatur — tidak menimpa.
   3. Semua efek otomatis mati bila pengguna mengaktifkan
      "Kurangi Gerakan" (prefers-reduced-motion) di HP/laptopnya,
      dan saat mencetak (print) — supaya hasil cetak tetap bersih.
   4. Berlaku sama di desktop, iPad, Android, dan iOS. Efek "hover"
      hanya aktif pada perangkat bermouse; perangkat sentuh mendapat
      umpan balik "tekan" (press) yang lebih cocok untuk jari.

   Dipakai bersama /motion.js. CSS ini tetap aman bila JS gagal
   dimuat: yang hilang hanya efek riak & animasi bertahap, tidak ada
   konten yang menjadi tak terlihat.
   ============================================================ */

:root{
  /* Durasi — sengaja pendek. Animasi yang lambat terasa lambat,
     bukan mewah. 120–340ms adalah rentang yang terasa "instan tapi halus". */
  --gnx-t-fast: 130ms;
  --gnx-t:      220ms;
  --gnx-t-slow: 340ms;

  /* Kurva gerak. --gnx-ease-soft meniru gerak "decelerate" khas iOS:
     cepat di awal, mendarat perlahan — inilah yang membuat kesan premium. */
  --gnx-ease:      cubic-bezier(.22,.61,.36,1);
  --gnx-ease-soft: cubic-bezier(.16,1,.30,1);
  --gnx-ease-in:   cubic-bezier(.55,.06,.68,.19);

  /* Warna aksen untuk bilah progres & cincin fokus. Memakai variabel
     tema bila halaman menyediakannya, dengan cadangan biru Gunex. */
  --gnx-accent:      var(--blue, #2456b3);
  --gnx-accent-soft: var(--blue-bright, #3f7fd6);
}


/* ============================================================
   1. TRANSISI HALAMAN KE HALAMAN
   ============================================================ */

/* --- 1a. Cara modern (Chrome/Edge/Android & Safari 18.2+) ---
   View Transitions lintas-dokumen: browser memotret halaman lama,
   lalu memudarkannya ke halaman baru secara mulus — persis seperti
   aplikasi native. Tidak ada JavaScript, tidak ada jeda navigasi.
   Browser yang belum mendukung cukup mengabaikan blok ini. */
@view-transition{ navigation: auto; }

::view-transition-old(root){
  animation: gnxPageOut 170ms var(--gnx-ease-in) both;
}
::view-transition-new(root){
  animation: gnxPageIn 320ms var(--gnx-ease-soft) both;
}
@keyframes gnxPageOut{
  from{ opacity:1; transform:scale(1); }
  to  { opacity:0; transform:scale(.992); }
}
@keyframes gnxPageIn{
  from{ opacity:0; transform:scale(1.006); }
  to  { opacity:1; transform:scale(1); }
}

/* --- 1b. Cadangan untuk browser lama (Firefox, Safari lawas) ---
   motion.js menambahkan kelas .gnx-leaving ke <html> saat pengguna
   menekan tautan. Halaman meredup selama browser mengambil halaman
   berikutnya, jadi perpindahan tidak terasa "patah".
   Navigasi TIDAK ditahan sedikit pun — kelas ini murni visual. */
html.gnx-leaving body{
  opacity: 0;
  transition: opacity 200ms var(--gnx-ease-in);
}
/* Elemen yang barusan ditekan tetap sedikit "maju" saat halaman
   meredup — memberi tahu mata: inilah yang sedang dibuka. */
.gnx-launch{
  animation: gnxLaunch 240ms var(--gnx-ease-soft) both;
}
@keyframes gnxLaunch{
  from{ transform:scale(1); }
  to  { transform:scale(1.02); }
}


/* ============================================================
   2. BILAH PROGRES TIPIS DI ATAS LAYAR
   Muncul saat berpindah halaman atau saat data sedang diambil.
   position:fixed → tidak memengaruhi tata letak apa pun.
   ============================================================ */
.gnx-progress{
  position: fixed;
  top: 0; left: 0;
  height: 2.5px;
  width: 0;
  z-index: 2147483600;
  pointer-events: none;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(90deg, var(--gnx-accent), var(--gnx-accent-soft));
  box-shadow: 0 0 10px rgba(63,127,214,.55);
  opacity: 0;
  transition: width 220ms var(--gnx-ease), opacity 260ms var(--gnx-ease);
}
.gnx-progress.on{ opacity: 1; }


/* ============================================================
   3. MIKRO-INTERAKSI TOMBOL & KARTU
   ============================================================ */

/* 3a. Pasang transisi pada semua elemen yang bisa ditekan.
   Selektor sengaja memakai pola [class*="-btn"] dsb. supaya ikut
   menjangkau penamaan khas tiap menu (gs-btn, iv-btn, pay-btn,
   iv2-btn, bk-btn, ...) tanpa perlu menyentuh berkas CSS menu itu. */
button,
[role="button"],
summary,
select,
.btn, .bk-btn, .qcard, .menu-card, .tile, .rk-card,
.arrange-btn, .arrange-reset, .year-btn, .cat-chip,
[class*="-btn"], [class*="-tab"], [class*="-chip"], [class*="-pill"],
[class*="-card"], [class*="-kpi"], [class*="-tile"], [class*="-opt"],
[class*="-seg-btn"], [class*="-toggle"], [class*="-switch"],
input[type="submit"], input[type="button"], input[type="reset"]{
  -webkit-tap-highlight-color: transparent;
  transition:
    transform        var(--gnx-t-fast) var(--gnx-ease),
    box-shadow       var(--gnx-t)      var(--gnx-ease),
    background       var(--gnx-t)      var(--gnx-ease),
    border-color     var(--gnx-t)      var(--gnx-ease),
    color            var(--gnx-t)      var(--gnx-ease),
    opacity          var(--gnx-t)      var(--gnx-ease),
    filter           var(--gnx-t)      var(--gnx-ease);
}
/* CATATAN: dipakai `background`, BUKAN `background-color`. Beberapa tombol
   dan kartu aplikasi memakai gradient (linear-gradient) yang berubah saat
   disorot; `background-color` saja akan membuat perubahan itu mematah. */

/* 3b. Umpan balik TEKAN — berlaku di semua perangkat, paling terasa
   di layar sentuh (menggantikan kotak abu-abu bawaan Android/iOS yang
   sudah dimatikan di atas).
   PENTING: TANPA `:not(:disabled)`. Penambahan itu menaikkan bobot
   selektor menjadi 0,2,0 sehingga akan MENIMPA aturan asli aplikasi
   seperti `.gs-btn:active`, `.iv-btn:active`, dan `.iv2-btn:active`.
   Dengan bobot 0,1,0, gaya halaman selalu menang — dan tombol nonaktif
   tetap aman karena sudah dikunci aturan 3d di bawah. */
:where(button, [role="button"], .btn, .bk-btn, .arrange-btn, .arrange-reset,
       [class*="-btn"], [class*="-seg-btn"], [class*="-tab"], [class*="-chip"],
       input[type="submit"], input[type="button"]):active{
  transform: scale(.965);
}
:where(.qcard, .menu-card, .tile, .rk-card, [class*="-card"], [class*="-opt"],
       [class*="-tile"]):active{
  transform: scale(.988);
}

/* 3c. Hover — HANYA untuk perangkat bermouse. Di HP, :hover bisa
   "nyangkut" setelah disentuh; media query ini mencegahnya. */
@media (hover: hover) and (pointer: fine){
  :where(button, .btn, .bk-btn, [class*="-btn"], input[type="submit"]):hover{
    transform: translateY(-1px);
  }
  :where(.qcard, .menu-card, .tile, .rk-card, [class*="-card"]):hover{
    box-shadow: 0 12px 30px rgba(15,23,42,.10);
  }
  :where([class*="-tab"], [class*="-chip"], [class*="-pill"]):hover{
    filter: brightness(1.04);
  }
}

/* 3d. Tombol nonaktif tidak boleh ikut bergerak. */
button:disabled, [class*="-btn"]:disabled, .btn:disabled{
  transform: none !important;
}

/* 3e. Efek riak (ripple) yang digambar motion.js.
   Wadahnya position:fixed & pointer-events:none, jadi benar-benar
   melayang di atas halaman — nol pengaruh ke tata letak. */
.gnx-ripple-box{
  position: fixed;
  overflow: hidden;
  pointer-events: none;
  z-index: 2147483500;
  contain: strict;
}
.gnx-ripple{
  position: absolute;
  border-radius: 50%;
  background: currentColor;
  opacity: .26;
  transform: translate(-50%, -50%) scale(0);
  animation: gnxRipple 540ms var(--gnx-ease) forwards;
}
@keyframes gnxRipple{
  to{ transform: translate(-50%,-50%) scale(1); opacity: 0; }
}


/* ============================================================
   4. MASUKNYA KONTEN SECARA BERTAHAP (STAGGER)
   Kartu, baris tabel, dan KPI muncul berurutan dengan jeda sangat
   pendek. Inilah efek yang paling membuat aplikasi terasa "hidup".
   transform+opacity saja → posisi elemen di halaman tidak bergeser.
   ============================================================ */
.gnx-reveal{
  opacity: 0;
  animation: gnxRise 360ms var(--gnx-ease-soft) forwards;
  animation-delay: var(--gnx-d, 0ms);
  will-change: transform, opacity;
}
@keyframes gnxRise{
  from{ opacity:0; transform: translateY(9px); }
  to  { opacity:1; transform: none; }
}

/* Varian untuk baris tabel: geser sangat tipis supaya tabel panjang
   tidak terlihat "bergoyang". */
tr.gnx-reveal{
  animation-name: gnxRiseRow;
}
@keyframes gnxRiseRow{
  from{ opacity:0; transform: translateY(5px); }
  to  { opacity:1; transform: none; }
}


/* ============================================================
   5. DIALOG / OVERLAY / MODAL
   Semua menu memakai nama kelas berbeda (gs-overlay, iv-overlay,
   pay-overlay, iv2-overlay, match-overlay, gnx-overlay, ...), maka
   dicocokkan dengan pola [class*="overlay"].
   Animasi berjalan otomatis begitu elemen tampil — baik karena
   disisipkan ke halaman maupun karena display-nya berubah.
   ============================================================ */
[class*="overlay"]:not(.gnx-no-motion){
  animation: gnxBackdropIn 190ms var(--gnx-ease);
}
[class*="overlay"]:not(.gnx-no-motion) > *{
  animation: gnxDialogIn 300ms var(--gnx-ease-soft);
}
@keyframes gnxBackdropIn{
  from{ opacity: 0; }
  to  { opacity: 1; }
}
@keyframes gnxDialogIn{
  from{ opacity: 0; transform: translateY(12px) scale(.975); }
  to  { opacity: 1; transform: none; }
}
/* Catatan: fill-mode sengaja TIDAK dipakai, sehingga setelah animasi
   selesai tidak ada sisa transform pada dialog. Ini penting agar
   elemen position:fixed di dalam dialog (menu tarik-turun, tooltip)
   tetap berperilaku normal. */

/* Panel tab yang baru aktif. Bobot nol, jadi halaman yang sudah punya
   animasi sendiri (mis. .iv-panel.active) tetap memakai miliknya. */
:where([class*="-panel"], [class*="-pane"], [class*="-tabpanel"]).active{
  animation: gnxPanelIn 300ms var(--gnx-ease-soft);
}
@keyframes gnxPanelIn{
  from{ opacity:0; transform: translateY(7px); }
  to  { opacity:1; transform: none; }
}


/* ============================================================
   6. FORMULIR — kolom isian, pesan sukses & galat
   ============================================================ */
input, textarea, select{
  transition:
    border-color var(--gnx-t) var(--gnx-ease),
    box-shadow   var(--gnx-t) var(--gnx-ease),
    background-color var(--gnx-t) var(--gnx-ease);
}

/* Cincin fokus papan ketik (bukan saat diklik mouse) — membantu
   navigasi Tab tanpa mengubah ukuran elemen sedikit pun. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible{
  outline: 2px solid var(--gnx-accent);
  outline-offset: 2px;
}

/* Pesan galat bergetar halus saat kelas .err dipasang — pengguna
   langsung tahu ada yang salah tanpa harus mencari teksnya. */
:where([class*="-msg"], [class*="-error"], [class*="-hint"], [class*="-status"]).err,
:where([class*="-error"], [class*="error-"]):not(:empty){
  animation: gnxShake 400ms var(--gnx-ease);
}
@keyframes gnxShake{
  0%,100%{ transform: translateX(0); }
  18%    { transform: translateX(-5px); }
  38%    { transform: translateX(4px); }
  58%    { transform: translateX(-3px); }
  78%    { transform: translateX(2px); }
}

/* Pesan berhasil muncul dengan "pop" lembut. */
:where([class*="-msg"], [class*="-status"]).ok{
  animation: gnxPopSoft 340ms var(--gnx-ease-soft);
}
@keyframes gnxPopSoft{
  from{ opacity:0; transform: translateY(-4px); }
  to  { opacity:1; transform: none; }
}


/* ============================================================
   7. ANGKA YANG BERUBAH
   motion.js memasang kelas ini ketika isi sebuah angka (KPI, total,
   saldo) berganti — mata langsung tertuju ke nilai yang baru.
   ============================================================ */
.gnx-flip{
  animation: gnxFlip 420ms var(--gnx-ease-soft);
}
@keyframes gnxFlip{
  0%  { opacity:.25; transform: translateY(6px); }
  60% { opacity:1;   transform: translateY(-1px); }
  100%{ opacity:1;   transform: none; }
}


/* ============================================================
   8. KEADAAN MEMUAT
   Teks "Memuat…" berdenyut lembut, dan wadah yang sedang menunggu
   data ikut meredup — bukan diam membeku.
   ============================================================ */
:where(.loading, [class*="loading-"], [class*="-loading"], [class*="-empty"]:empty){
  animation: gnxBreathe 1.5s ease-in-out infinite;
}
@keyframes gnxBreathe{
  0%,100%{ opacity: .55; }
  50%    { opacity: 1; }
}
.gnx-busy{
  opacity: .55;
  transition: opacity var(--gnx-t) var(--gnx-ease);
}


/* ============================================================
   9. TOAST / NOTIFIKASI RINGAN
   Sebagian besar menu sudah punya transisi toast sendiri; aturan
   berbobot nol ini hanya melengkapi yang belum punya.
   ============================================================ */
:where([class*="toast"]).show{
  animation: gnxToastIn 340ms var(--gnx-ease-soft);
}
@keyframes gnxToastIn{
  from{ opacity:0; transform: translateY(14px); }
  to  { opacity:1; transform: none; }
}


/* ============================================================
   10. PENYESUAIAN PERANGKAT SENTUH (iOS & Android)
   ============================================================ */
@media (max-width: 1024px){
  /* Gulir tabel lebar terasa "melayang" seperti aplikasi native. */
  [class*="table-wrap"], [class*="-scroll"], [class*="-tabs"]{
    -webkit-overflow-scrolling: touch;
    scroll-behavior: smooth;
  }
  /* Di layar sentuh, tekanan dibuat sedikit lebih dalam supaya
     terasa jelas walau jari menutupi tombolnya. */
  :where(button, .btn, .bk-btn, [class*="-btn"]):active{
    transform: scale(.95);
  }
}

/* Hilangkan kilatan biru bawaan Android pada seluruh area sadap. */
a, button, label, summary, [role="button"], [class*="-card"], [class*="-tab"]{
  -webkit-tap-highlight-color: transparent;
}


/* ============================================================
   11. MUNCUL SAAT DIGULIR (SCROLL REVEAL)
   Konten di bawah layar baru muncul ketika benar-benar terlihat.
   Dikendalikan IntersectionObserver di motion.js — TANPA event
   'scroll' sama sekali, jadi menggulir tetap ringan di HP lawas.
   Setiap elemen hanya diamati sekali lalu dilepas.
   ============================================================ */
.gnx-sr{
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 460ms var(--gnx-ease-soft),
              transform 460ms var(--gnx-ease-soft);
  transition-delay: var(--gnx-d, 0ms);
}
.gnx-sr.gnx-sr-on{ opacity: 1; transform: none; }


/* ============================================================
   12. GAMBAR & FOTO
   Thumbnail (bukti bayar, foto barang, galeri) tidak lagi
   "melompat" muncul, tetapi larut masuk begitu selesai dimuat.
   Hanya opacity — ruang gambar sudah dipesan sejak awal oleh
   tata letak asli, jadi tidak ada pergeseran sedikit pun.
   ============================================================ */
.gnx-img{ opacity: 0; transition: opacity 420ms var(--gnx-ease); }
.gnx-img.gnx-img-on{ opacity: 1; }


/* ============================================================
   13. BILAH ATAS SAAT HALAMAN DIGULIR
   Begitu konten mulai lewat di bawahnya, bilah atas mengangkat
   diri dengan bayangan tipis — memberi kesan berlapis seperti
   aplikasi native. Dideteksi lewat "sentinel" IntersectionObserver,
   bukan event scroll.
   ============================================================ */
:where([class*="topbar"], [class*="-head"], header){
  transition: box-shadow var(--gnx-t) var(--gnx-ease),
              background-color var(--gnx-t) var(--gnx-ease);
}
html.gnx-scrolled :where([class*="topbar"], header){
  box-shadow: 0 6px 20px rgba(15,23,42,.10);
}


/* ============================================================
   14. BARIS DAFTAR & TABEL
   Sentuhan kecil yang membuat data terasa "bisa disentuh".
   ============================================================ */
tbody tr, [class*="-row"], [class*="-item"]{
  transition: background-color var(--gnx-t-fast) var(--gnx-ease),
              transform var(--gnx-t-fast) var(--gnx-ease);
}
@media (hover: hover) and (pointer: fine){
  :where(tbody tr:hover, [class*="-row"]:hover, [class*="-item"]:hover){
    background-color: rgba(36,86,179,.045);
  }
  /* Panah "buka" pada kartu ikut maju — isyarat arah yang jelas.
     CATATAN: `.go` pada Portal SENGAJA TIDAK disertakan. admin.css sudah
     menggeser `.menu-card:hover .go svg` sejauh 3px; bila wadah `.go`
     digeser juga, panah bergerak dua kali (6px) dan teks "Buka" ikut
     melenceng dari posisinya. */
  :where(.menu-card, .qcard, .rk-card):hover :where([class*="-arrow"], [class*="-go"]){
    transform: translateX(3px);
  }
}
:where([class*="-arrow"], [class*="-go"]){
  transition: transform var(--gnx-t) var(--gnx-ease-soft);
}


/* ============================================================
   15. FORMULIR — kolom aktif sedikit "terangkat"
   Hanya bayangan & warna garis: ukuran kolom tidak berubah,
   sehingga tidak ada satu pun elemen yang bergeser.
   ============================================================ */
:where(.field, [class*="-field"], label):focus-within{
  --gnx-focus: 1;
}
:where(input, textarea, select):focus{
  box-shadow: 0 0 0 3px rgba(36,86,179,.14);
}
/* Kotak centang & sakelar ikut merespons sentuhan. */
:where(input[type="checkbox"], input[type="radio"]){
  transition: transform var(--gnx-t-fast) var(--gnx-ease);
}
:where(input[type="checkbox"], input[type="radio"]):active{ transform: scale(.88); }
:where(input[type="checkbox"], input[type="radio"]):checked{
  animation: gnxCheck 300ms var(--gnx-ease-soft);
}
@keyframes gnxCheck{
  0%  { transform: scale(.8); }
  55% { transform: scale(1.12); }
  100%{ transform: scale(1); }
}


/* ============================================================
   16. TAB YANG BARU AKTIF
   Tab yang dipilih memberi "denyut" singkat supaya mata langsung
   tahu ke mana fokus berpindah.
   ============================================================ */
:where([class*="-tab"], [class*="-seg-btn"], [role="tab"]).active{
  animation: gnxTabOn 320ms var(--gnx-ease-soft);
}
@keyframes gnxTabOn{
  0%  { transform: translateY(2px); opacity: .6; }
  100%{ transform: none; opacity: 1; }
}

/* Lencana / badge yang nilainya berubah ikut berdenyut sekali. */
.gnx-pulse{ animation: gnxPulse 460ms var(--gnx-ease-soft); }
@keyframes gnxPulse{
  0%  { transform: scale(1); }
  40% { transform: scale(1.14); }
  100%{ transform: scale(1); }
}


/* ============================================================
   17. LACI / MENU GESER (halaman publik)
   ============================================================ */
:where([class*="mobile-nav"], [class*="-drawer"], [class*="-sheet"]){
  transition: transform var(--gnx-t-slow) var(--gnx-ease-soft),
              opacity var(--gnx-t) var(--gnx-ease);
}
:where([class*="nav-overlay"], [class*="-scrim"]){
  transition: opacity var(--gnx-t) var(--gnx-ease);
}


/* ============================================================
   18. MODE HEMAT — kunci "tidak memberatkan perangkat"
   motion.js memasang data-gnx-tier="hemat" pada <html> bila:
     • perangkat berRAM/inti kecil, atau mode hemat data aktif, ATAU
     • pengawas laju gambar mendeteksi halaman tersendat.
   Efek yang paling mahal dimatikan, efek murah tetap jalan —
   jadi aplikasi tetap terasa hidup, bukan mati total.
   ============================================================ */
html[data-gnx-tier="hemat"]{
  --gnx-t-fast: 90ms;
  --gnx-t:      150ms;
  --gnx-t-slow: 220ms;
}
html[data-gnx-tier="hemat"] .gnx-ripple-box{ display: none !important; }
html[data-gnx-tier="hemat"] .gnx-sr{ transform: none; }        /* cukup memudar */
html[data-gnx-tier="hemat"] [class*="overlay"]:not(.gnx-no-motion) > *{
  animation-duration: 180ms;
}
html[data-gnx-tier="hemat"] :where(.loading, [class*="-loading"]){
  animation: none; opacity: .7;
}
html[data-gnx-tier="hemat"] tr.gnx-reveal{ animation: none; opacity: 1; }
/* Bayangan besar adalah salah satu efek termahal untuk digambar ulang;
   di mode hemat cukup bayangan tipis. */
html[data-gnx-tier="hemat"] :where(.qcard,.menu-card,.tile,.rk-card,[class*="-card"]):hover{
  box-shadow: 0 4px 12px rgba(15,23,42,.08);
}

/* Gulir mulus hanya untuk perangkat yang sanggup. */
html:not([data-gnx-tier="hemat"]){ scroll-behavior: smooth; }


/* ============================================================
   19. FITUR LANJUTAN
   ============================================================ */

/* --- 19a. MENUTUP DIALOG ---
   Saat modal ditutup, motion.js menampilkan "bayangan" (salinan mati
   tanpa id dan tanpa pointer-events) lalu memudarkannya. Dialog aslinya
   sudah benar-benar dilepas oleh kode menu, jadi keadaan aplikasi tidak
   tersentuh sama sekali. */
.gnx-ghost{
  pointer-events: none !important;
  animation: gnxDialogOut 200ms var(--gnx-ease-in) forwards;
}
.gnx-ghost > *{
  animation: gnxDialogBoxOut 200ms var(--gnx-ease-in) forwards;
}
@keyframes gnxDialogOut{
  from{ opacity: 1; } to{ opacity: 0; }
}
@keyframes gnxDialogBoxOut{
  from{ opacity: 1; transform: none; }
  to  { opacity: 0; transform: translateY(8px) scale(.98); }
}
/* Overlay yang ditutup dengan melepas kelas .show perlu tetap tampil
   selama animasi keluar berjalan. */
[class*="overlay"].gnx-closing{
  display: flex !important;
  animation: gnxDialogOut 200ms var(--gnx-ease-in) forwards;
}
[class*="overlay"].gnx-closing > *{
  animation: gnxDialogBoxOut 200ms var(--gnx-ease-in) forwards;
}


/* --- 19b. TOMBOL BERHASIL ---
   Lapisan melayang di ATAS tombol, bukan di dalamnya: isi, lebar, dan
   posisi tombol tidak tersentuh sedikit pun. */
.gnx-ok-box{
  position: fixed;
  z-index: 2147483500;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: gnxOkIn 200ms var(--gnx-ease-soft),
             gnxOkOut 260ms var(--gnx-ease-in) 900ms forwards;
}
.gnx-ok-box svg{ width: 40%; max-width: 26px; height: auto; }
.gnx-ok-box path{
  fill: none; stroke-width: 3;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 30; stroke-dashoffset: 30;
  animation: gnxOkDraw 320ms var(--gnx-ease-soft) 120ms forwards;
}
@keyframes gnxOkIn { from{ opacity:0; transform: scale(.9); } to{ opacity:1; transform:none; } }
@keyframes gnxOkOut{ from{ opacity:1; } to{ opacity:0; } }
@keyframes gnxOkDraw{ to{ stroke-dashoffset: 0; } }


/* --- 19c. BARIS YANG BARU BERUBAH ---
   Hanya warna latar yang beranimasi; tinggi dan posisi baris tetap. */
.gnx-highlight{ animation: gnxHighlight 1.5s var(--gnx-ease) forwards; }
@keyframes gnxHighlight{
  0%  { background-color: rgba(34,180,120,.30); }
  35% { background-color: rgba(34,180,120,.22); }
  100%{ background-color: transparent; }
}


/* --- 19d. BARIS MELUNCUR SAAT DIURUT / DISARING (teknik FLIP) ---
   motion.js mengukur posisi lama, membiarkan menu menggambar ulang,
   lalu menggeser tiap baris kembali ke posisi lamanya dan melepasnya.
   Hanya transform: alur dokumen sudah final sebelum animasi dimulai. */
.gnx-move{
  transition: transform 340ms var(--gnx-ease-soft);
  will-change: transform;
}


/* --- 19e. KERANGKA MUAT (SKELETON) ---
   Digambar di lapisan absolut berkoordinat dokumen, sehingga ikut
   tergulir bersama halaman namun tetap di luar alur tata letak. */
.gnx-skel{
  position: absolute;
  z-index: 5;
  pointer-events: none;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 9px;
  animation: gnxFadeIn 200ms var(--gnx-ease);
}
.gnx-skel i{
  display: block;
  height: 11px;
  border-radius: 6px;
  background: linear-gradient(90deg,
    rgba(148,163,184,.16) 0%, rgba(148,163,184,.16) 40%,
    rgba(148,163,184,.34) 50%,
    rgba(148,163,184,.16) 60%, rgba(148,163,184,.16) 100%);
  background-size: 250% 100%;
  animation: gnxShimmer 1.25s linear infinite;
}
.gnx-skel i:nth-child(2){ width: 82%; animation-delay: .08s; }
.gnx-skel i:nth-child(3){ width: 64%; animation-delay: .16s; }
.gnx-skel i:nth-child(4){ width: 74%; animation-delay: .24s; }
@keyframes gnxShimmer{ from{ background-position: 120% 0; } to{ background-position: -120% 0; } }
@keyframes gnxFadeIn{ from{ opacity:0; } to{ opacity:1; } }


/* --- 19f. KARTU MENU MELEBUR JADI HALAMAN ---
   View Transitions dengan elemen bersama: kartu yang ditekan di Portal
   dan bilah atas halaman tujuan diberi nama yang sama, sehingga browser
   memuaikan yang satu menjadi yang lain. Hanya di browser yang mendukung;
   sisanya tetap mendapat pudar biasa. */
::view-transition-group(gnx-hero){
  animation-duration: 380ms;
  animation-timing-function: var(--gnx-ease-soft);
}
::view-transition-old(gnx-hero){ animation: gnxHeroOut 260ms var(--gnx-ease-in) both; }
::view-transition-new(gnx-hero){ animation: gnxHeroIn 380ms var(--gnx-ease-soft) both; }
@keyframes gnxHeroOut{ from{ opacity:1; } to{ opacity:0; } }
@keyframes gnxHeroIn { from{ opacity:0; } to{ opacity:1; } }


/* --- 19g. MODE HEMAT: matikan yang mahal --- */
html[data-gnx-tier="hemat"] .gnx-skel i{ animation: none; opacity: .5; }
html[data-gnx-tier="hemat"] .gnx-highlight{ animation-duration: .9s; }
html[data-gnx-tier="hemat"] .gnx-move{ transition: none; }


/* ============================================================
   20. GELOMBANG KEDUA FITUR
   ============================================================ */

/* --- 20a. GESER DARI TEPI UNTUK KEMBALI ---
   Halaman mengikuti jari. transform saja: alur dokumen tidak berubah,
   dan begitu gerakan selesai nilainya dibersihkan total. */
.gnx-swipe-anim{ transition: transform 280ms var(--gnx-ease-soft); }
.gnx-swipe-shadow{ box-shadow: -14px 0 34px rgba(15,23,42,.20); }


/* --- 20b. TITIK PIN ---
   Memantul saat diisi, bergetar saat PIN salah. */
.gnx-pin-pop{ animation: gnxPinPop 260ms var(--gnx-ease-soft); }
@keyframes gnxPinPop{
  0%  { transform: scale(1); }
  45% { transform: scale(1.18); }
  100%{ transform: scale(1); }
}
.gnx-pin-shake{ animation: gnxShake 420ms var(--gnx-ease); }


/* --- 20c. TOMBOL MENUNGGU (SPINNER) ---
   Lapisan melayang di atas tombol, sama seperti tanda centang: teks dan
   lebar tombol tidak disentuh sehingga tidak ada yang melompat. */
.gnx-spin-box{
  position: fixed;
  z-index: 2147483500;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: gnxOkIn 160ms var(--gnx-ease-soft);
}
.gnx-spin-box b{
  display: block;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 2.5px solid currentColor;
  border-top-color: transparent;
  animation: gnxSpin 620ms linear infinite;
}
@keyframes gnxSpin{ to{ transform: rotate(360deg); } }


/* --- 20d. FOTO MEMBESAR DARI THUMBNAIL --- */
.gnx-zoom{
  transition: transform 360ms var(--gnx-ease-soft), opacity 200ms var(--gnx-ease);
  will-change: transform;
}


/* --- 20e. PANAH URUT BERBALIK ---
   Membalik pada sumbu Y lalu kembali ke identitas, jadi tidak pernah
   melenceng dari arah panah yang sebenarnya digambar aplikasi. */
.gnx-arrowflip{ animation: gnxArrowFlip 340ms var(--gnx-ease-soft); }
@keyframes gnxArrowFlip{
  0%  { transform: scaleY(1); }
  50% { transform: scaleY(-1); }
  100%{ transform: scaleY(1); }
}


/* --- 20f. ELEMEN LENGKET SAAT MENEMPEL ---
   Bilah atas / bilah filter mengangkat diri hanya ketika benar-benar
   sedang menempel, bukan asal halaman digulir. */
.gnx-stuck{ box-shadow: 0 6px 18px rgba(15,23,42,.14) !important; }


/* --- 20g. HITUNG MUNDUR TOAST ---
   Garis tipis melayang di tepi bawah toast, menunjukkan sisa waktu
   sebelum pesan hilang sendiri. */
.gnx-toast-bar{
  position: fixed;
  height: 3px;
  z-index: 2147483550;
  pointer-events: none;
  border-radius: 0 0 6px 6px;
  background: currentColor;
  opacity: .55;
  transform-origin: left center;
  animation: gnxCountdown linear forwards;
}
@keyframes gnxCountdown{ from{ transform: scaleX(1); } to{ transform: scaleX(0); } }


/* --- 20h. BARIS YANG BARU TERSIMPAN ---
   Masuk sambil bernapas hijau sebentar, supaya mata langsung menemukan
   data yang barusan disimpan di tengah daftar panjang. */
.gnx-fresh{
  animation: gnxFresh 900ms var(--gnx-ease-soft) forwards;
}
@keyframes gnxFresh{
  0%  { opacity: 0; transform: translateY(9px); background-color: rgba(34,180,120,.30); }
  40% { opacity: 1; transform: none;           background-color: rgba(34,180,120,.26); }
  100%{ opacity: 1; transform: none;           background-color: transparent; }
}


/* --- 20i. MODE HEMAT --- */
html[data-gnx-tier="hemat"] .gnx-zoom{ transition: none; }
html[data-gnx-tier="hemat"] .gnx-toast-bar{ display: none; }
html[data-gnx-tier="hemat"] .gnx-spin-box b{ animation-duration: 900ms; }


/* ============================================================
   21. PENCARIAN & KEMBALI KE ATAS
   ============================================================ */

/* --- 21a. SOROT HASIL PENCARIAN ---
   Memakai CSS Custom Highlight API: sorotan digambar browser di atas
   teks TANPA menyisipkan satu elemen pun ke dalam halaman. Ini penting
   di aplikasi billing — membungkus teks tabel dengan <mark> berisiko
   bentrok dengan kode menu yang membaca atau menggambar ulang isinya.
   Browser yang belum mendukung cukup tidak menampilkan sorotan; tidak
   ada yang rusak. */
::highlight(gnx-cari){
  background-color: rgba(250, 204, 21, .55);
  color: inherit;
}

/* --- 21b. TOMBOL KEMBALI KE ATAS ---
   position:fixed -> di luar alur dokumen, nol dampak tata letak.
   Menghormati area aman iPhone (notch bawah). */
.gnx-top{
  position: fixed;
  right: 18px;
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  width: 44px;
  height: 44px;
  z-index: 2147483400;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  background: var(--gnx-accent);
  color: #fff;
  box-shadow: 0 6px 20px rgba(15,23,42,.28);
  opacity: 0;
  transform: translateY(16px) scale(.9);
  pointer-events: none;
  transition: opacity var(--gnx-t) var(--gnx-ease),
              transform var(--gnx-t) var(--gnx-ease-soft);
}
.gnx-top.on{
  opacity: .93;
  transform: none;
  pointer-events: auto;
}
.gnx-top:active{ transform: scale(.92); }
.gnx-top svg{ width: 20px; height: 20px; }
.gnx-top path{ fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

html[data-gnx-tier="hemat"] .gnx-top{ transition-duration: 120ms; }


/* ============================================================
   22. GELOMBANG KEEMPAT
   ============================================================ */

/* --- 22a. INDIKATOR KONEKSI ---
   Bilah melayang di puncak layar. position:fixed -> nol dampak tata
   letak, dan tidak ada biaya sama sekali saat koneksi normal karena
   hanya bereaksi pada event 'online'/'offline' bawaan browser. */
.gnx-net{
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 2147483580;
  padding: 7px 14px;
  padding-top: calc(7px + env(safe-area-inset-top, 0px));
  text-align: center;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: #fff;
  background: #b91c1c;
  transform: translateY(-100%);
  transition: transform 300ms var(--gnx-ease-soft), background-color 300ms var(--gnx-ease);
  pointer-events: none;
}
.gnx-net.on{ transform: none; }
.gnx-net.pulih{ background: #15803d; }


/* --- 22b. SEDANG MENGUNGGAH ---
   CATATAN JUJUR: aplikasi mengunggah lewat fetch, dan fetch TIDAK bisa
   melaporkan kemajuan unggahan (hanya XMLHttpRequest yang bisa). Jadi
   yang ditampilkan adalah keadaan "sedang berjalan", bukan persentase
   palsu yang mengarang angka. */
.gnx-upload{
  position: fixed;
  z-index: 2147483500;
  pointer-events: none;
  overflow: hidden;
  border-radius: 10px;
  background: rgba(15,23,42,.55);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: gnxFadeIn 200ms var(--gnx-ease);
}
.gnx-upload::after{
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  height: 3px; width: 40%;
  background: linear-gradient(90deg, transparent, #fff, transparent);
  animation: gnxSlideBar 1.1s linear infinite;
}
.gnx-upload b{
  color: #fff;
  font-size: .74rem;
  font-weight: 600;
}
@keyframes gnxSlideBar{
  from{ transform: translateX(-100%); }
  to  { transform: translateX(350%); }
}


/* --- 22c. PERPINDAHAN TAB SESUAI ARAH ---
   Panel masuk dari kanan bila menuju tab berikutnya, dari kiri bila
   menuju tab sebelumnya. Mata jadi tahu arah perpindahan.
   Bobot 0,2,0 sehingga menang atas animasi panel netral di Bagian 5. */
[class*="-panel"].gnx-dari-kanan,
[class*="-pane"].gnx-dari-kanan{ animation: gnxDariKanan 300ms var(--gnx-ease-soft); }
[class*="-panel"].gnx-dari-kiri,
[class*="-pane"].gnx-dari-kiri{ animation: gnxDariKiri 300ms var(--gnx-ease-soft); }
@keyframes gnxDariKanan{
  from{ opacity: 0; transform: translateX(18px); }
  to  { opacity: 1; transform: none; }
}
@keyframes gnxDariKiri{
  from{ opacity: 0; transform: translateX(-18px); }
  to  { opacity: 1; transform: none; }
}


/* --- 22d. ADA PERUBAHAN BELUM DISIMPAN ---
   Cincin lembut berdenyut pada tombol simpan. Hanya box-shadow:
   ukuran dan posisi tombol tidak berubah sedikit pun. */
.gnx-nudge{ animation: gnxNudge 2.2s var(--gnx-ease) infinite; }
@keyframes gnxNudge{
  0%, 100%{ box-shadow: 0 0 0 0 rgba(36,86,179,.42); }
  45%     { box-shadow: 0 0 0 7px rgba(36,86,179,0); }
}
html[data-gnx-tier="hemat"] .gnx-nudge{ animation-duration: 3.2s; }


/* ============================================================
   23. SAKELAR PENGAMAN
   ============================================================ */

/* 11a. Hormati pengaturan aksesibilitas perangkat. Bila pengguna
   memilih "Kurangi Gerakan", semua efek dimatikan — dan yang
   terpenting, konten TETAP TERLIHAT (opacity dipaksa 1). */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .gnx-reveal{ opacity: 1 !important; animation: none !important; }
  .gnx-sr, .gnx-img{ opacity: 1 !important; transform: none !important; }
  html.gnx-leaving body{ opacity: 1 !important; }
  .gnx-progress, .gnx-skel, .gnx-ghost, .gnx-ok-box,
  .gnx-spin-box, .gnx-toast-bar{ display: none !important; }
  .gnx-top{ transition: none !important; }
  .gnx-nudge{ animation: none !important; }
  .gnx-upload::after{ animation: none !important; }
  [class*="overlay"].gnx-closing{ display: none !important; animation: none !important; }
  .gnx-fresh, .gnx-zoom{ opacity: 1 !important; transform: none !important; animation: none !important; }
  body[style*="translate"]{ transform: none !important; }
  .gnx-move{ transition: none !important; transform: none !important; }
  .gnx-highlight{ animation: none !important; }
  ::view-transition-old(root), ::view-transition-new(root){ animation: none !important; }
  html{ scroll-behavior: auto !important; }
}

/* 11b. Saat mencetak / ekspor PDF: nol animasi, nol elemen melayang,
   semua konten terlihat penuh. */
@media print{
  *, *::before, *::after{
    animation: none !important;
    transition: none !important;
  }
  .gnx-reveal, .gnx-sr, .gnx-img{ opacity: 1 !important; transform: none !important; }
  .gnx-progress, .gnx-ripple-box, .gnx-skel, .gnx-ghost, .gnx-ok-box,
  .gnx-spin-box, .gnx-toast-bar, .gnx-top, .gnx-net, .gnx-upload{ display: none !important; }
  .gnx-nudge{ animation: none !important; }
  .gnx-fresh{ opacity: 1 !important; transform: none !important; animation: none !important; }
  body{ transform: none !important; }
  /* Menu Gaji & Tabungan menyembunyikan overlay saat mencetak dengan
     !important; jangan sampai sisa animasi tutup membuatnya ikut tercetak. */
  [class*="overlay"].gnx-closing{ display: none !important; }
  .gnx-move{ transition: none !important; transform: none !important; }
  html.gnx-leaving body{ opacity: 1 !important; }
}
