/* =========================================================================
   nsh_app.css — SISTEM DESAIN KERANGKA APLIKASI (Native Shell v1.0.0)
   -------------------------------------------------------------------------
   Dibaca bersama app/nativeshell/nsh_theme.php, yang menyuntikkan token warna
   (--nsh-*) berdasarkan warna merek toko. Berkas ini TIDAK PERNAH memuat nilai
   warna merek secara langsung; semuanya lewat token, sehingga satu berkas CSS
   melayani ribuan toko dengan identitas berbeda.

   ATURAN TEMA (tidak boleh dilanggar)
   Terang adalah default. Tidak ada satu pun @media (prefers-color-scheme) yang
   MEWARNAI sesuatu di berkas ini. Gelap hanya lewat html[data-theme="dark"],
   yang dipasang JavaScript bila pengunjung memilihnya.

   ATURAN KOMPATIBILITAS
   - Tidak ada :has(), tidak ada container query, tidak ada nesting CSS.
     Semua fitur di sini berjalan pada Chrome 80+, Safari 13.4+, Firefox 78+,
     Samsung Internet 12+, dan browser bawaan HarmonyOS.
   - Setiap penggunaan color-mix()/backdrop-filter punya nilai cadangan yang
     ditulis LEBIH DULU, jadi browser lama tetap melihat tampilan utuh.
   - Semua ukuran memakai satuan relatif atau dvh dengan cadangan vh.
   ========================================================================= */

/* ---------- 1. HURUF (swadaya, nol permintaan pihak ketiga) ------------- */
/* Huruf ditumpangkan sendiri, bukan dari Google Fonts: aplikasi harus tampil
   identik di Windows, macOS, iOS, Android, HarmonyOS, dan Linux, harus tetap
   utuh saat offline, dan tidak boleh membocorkan alamat IP pembeli ke pihak
   ketiga. Dua berkas, 49 KB total, dan hanya rentang aksara yang dipakai. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/plus-jakarta-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/plus-jakarta-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------- 2. TOKEN GERAK & UKURAN ------------------------------------ */
:root {
  --nsh-font: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
              'Helvetica Neue', Arial, 'Noto Sans', sans-serif;

  /* Kurva gerak. "Standard" dipakai untuk hampir semua hal; "emphasized"
     untuk perpindahan layar; "spring" untuk umpan balik sentuhan. */
  --nsh-ease: cubic-bezier(.2, 0, 0, 1);
  --nsh-ease-out: cubic-bezier(.05, .7, .1, 1);
  --nsh-ease-in: cubic-bezier(.3, 0, .8, .15);
  --nsh-spring: cubic-bezier(.34, 1.56, .64, 1);
  /* Kurva luncur perpindahan layar. Hampir seluruh gerakannya terjadi di awal
     lalu MENGENDAP panjang tanpa berhenti mendadak — itulah yang membuat
     transisi terasa seperti benda bermassa (air/karet), bukan animasi linear
     yang dipaksa berhenti. Titik kendali kedua di x=0 adalah kuncinya:
     kecepatan akhirnya nyaris nol tanpa perlu durasi panjang. */
  --nsh-ease-glide: cubic-bezier(.32, .72, 0, 1);

  /* Durasi. Angka ini diambil dari pedoman gerak iOS/Material: di bawah 90 ms
     terasa patah, di atas 320 ms terasa lambat. */
  --nsh-t-fast: 120ms;
  --nsh-t: 220ms;
  --nsh-t-slow: 320ms;

  /* Perpindahan layar punya durasinya SENDIRI, tidak menumpang --nsh-t.
     Layar yang masuk sengaja lebih panjang daripada yang keluar: mata
     mengikuti yang datang, dan yang pergi hanya perlu menyingkir. Menyamakan
     keduanya membuat transisi terasa berat di kedua arah. */
  --nsh-t-screen-in: 300ms;
  --nsh-t-screen-out: 150ms;

  --nsh-r-sm: 10px;
  --nsh-r: 14px;
  --nsh-r-lg: 20px;
  --nsh-r-xl: 26px;
  --nsh-r-full: 999px;

  --nsh-safe-t: env(safe-area-inset-top, 0px);
  --nsh-safe-b: env(safe-area-inset-bottom, 0px);
  --nsh-safe-l: env(safe-area-inset-left, 0px);
  --nsh-safe-r: env(safe-area-inset-right, 0px);

  /* Tinggi bilah menu bawah. Dipakai untuk padding isi supaya tidak ada
     tombol yang tertimbun bilah — dihitung sekali di sini, dipakai di mana pun. */
  --nsh-tabbar-h: 60px;
  --nsh-tabbar-gap: 10px;

  /* DUA ANGKA, BUKAN SATU. Perbedaannya menutup satu bug nyata.
     ------------------------------------------------------------------------
     --nsh-dock-base  = ruang yang ditempati BILAH MENU saja. Inilah yang boleh
                        dipakai elemen mengambang lain (ajakan pasang aplikasi,
                        toast) untuk menentukan posisi `bottom` mereka sendiri.

     --nsh-content-bottom = ruang yang harus DIKOSONGKAN oleh isi halaman. Ia
                        mencakup bilah menu DAN setiap lapisan mengambang lain
                        yang sedang tampil. tk_fx.js mengukurnya dari layar
                        sungguhan dan menimpanya di sini.

     Kalau keduanya disatukan (seperti sebelumnya), ajakan pasang aplikasi
     yang memakai angka itu untuk posisinya sendiri akan mendorong dirinya
     naik setiap kali angkanya bertambah — umpan balik yang tidak pernah
     berhenti. Dipisah, isi halaman aman dan lapisan mengambang tetap diam.

     Nilai di bawah adalah CADANGAN statis: bila tk_fx.js tidak dimuat,
     perilakunya sama persis seperti sebelum modul itu ada. */
  --nsh-dock-base: calc(var(--nsh-tabbar-h) + var(--nsh-tabbar-gap) * 2 + var(--nsh-safe-b));
  --nsh-content-bottom: var(--nsh-dock-base);
}

/* ---------- 3. DASAR --------------------------------------------------- */
html.nsh-on {
  background: var(--nsh-bg, #f6f7fb);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Tarik-lebih (overscroll) di akar dimatikan supaya tarikan ke bawah tidak
     memuat ulang halaman — di dalam aplikasi, itu tugas "tarik untuk
     menyegarkan" milik kita, yang tidak membuang keadaan layar. */
  overscroll-behavior-y: none;
}

body.nsh-app {
  font-family: var(--nsh-font);
  background: var(--nsh-bg, #f6f7fb);
  color: var(--nsh-text, #111827);
  /* Menyatakan warna sendiri, bukan meminjam dari peramban: tanpa ini scrollbar
     dan pemilih tanggal bawaan tetap gelap di atas halaman terang. */
  color-scheme: light;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  padding-bottom: var(--nsh-content-bottom);
  padding-left: max(1rem, var(--nsh-safe-l));
  padding-right: max(1rem, var(--nsh-safe-r));
  overscroll-behavior-y: none;
}
html[data-theme="dark"] body.nsh-app { color-scheme: dark; }

/* Layar tanpa bilah menu (live chat) tidak boleh menyisakan ruang kosong. */
body.nsh-app.nsh-nochrome { padding-bottom: 0; }

/* LANTAI RUANG BILAH MENU (5 Okt 2026).
   Keluhan penjual: di APLIKASI TERPASANG bagian bawah setiap halaman tertutup
   sebagian oleh bilah menu, padahal di Chrome aman. Penyebabnya modul PWA
   menyuntikkan `.tkpwa-safe body{padding-bottom:env(...)}` (kekhususan sama,
   dimuat belakangan) sehingga ruang bilah menu hilang. Modul itu sudah
   diperbaiki, tetapi aturan di bawah memastikan hal serupa tidak terulang:
   - kekhususan lebih tinggi (html.nsh-on body.nsh-app), dan
   - padding TIDAK PERNAH lebih kecil dari tinggi dok bawaan, walau hasil
     ukur tk_fx.js keliru (misal diukur saat bilah sedang beranimasi).
   Baris pertama = cadangan untuk peramban tanpa max(). */
html.nsh-on body.nsh-app {
  padding-bottom: var(--nsh-content-bottom);
  padding-bottom: max(var(--nsh-content-bottom), var(--nsh-dock-base));
}
html.nsh-on body.nsh-app.nsh-nochrome { padding-bottom: 0; }

body.nsh-app *,
body.nsh-app *::before,
body.nsh-app *::after { -webkit-tap-highlight-color: transparent; }

/* Ikon garis: satu aturan untuk semua SVG kerangka supaya ketebalan garisnya
   tidak pernah berbeda antar layar. */
.nsh-i { width: 22px; height: 22px; flex: none; display: block; }
.nsh-i path, .nsh-i circle, .nsh-i rect, .nsh-i line, .nsh-i polyline {
  vector-effect: non-scaling-stroke;
}

/* ---------- 4. WADAH ISI & PERPINDAHAN LAYAR --------------------------- */
/* Wadah ditumpuk (grid satu sel) supaya layar lama dan layar baru bisa
   dianimasikan BERSAMAAN tanpa satu pun melompat — yang membedakan transisi
   aplikasi dari "halaman berganti". */
.nsh-main {
  display: grid;
  grid-template-columns: 100%;
  position: relative;
  min-height: 40vh;
}
.nsh-main > * { grid-area: 1 / 1; min-width: 0; }

/* LEBAR EKSPLISIT 100% — bukan hiasan, ini penjaga anti-melebar.
   -------------------------------------------------------------------------
   Pembungkus isi etalase memakai `max-w-6xl mx-auto`. Di dalam GRID, sebuah
   item yang punya margin kiri/kanan `auto` TIDAK lagi diregangkan: margin
   otomatis menyerap ruang sisa, dan ukurannya jatuh ke `fit-content` —
   yaitu min(max-content, max(min-content, lebar wadah)).

   Selama satu bingkai pertama (sebelum Tailwind Play CDN selesai
   mengompilasi), `min-content` seluruh katalog jauh lebih besar daripada
   layar karena kelas seperti `truncate`, `overflow-x-auto`, dan `line-clamp`
   belum berlaku. Akibatnya pembungkus mengambil 1008px di layar 375px, dan
   SELURUH halaman ikut melebar — persis keluhan "tampilannya melebar" saat
   halaman baru mulai memuat.

   `width:100%` menyelesaikannya di kedua keadaan sekaligus: di ponsel lebar
   terkunci ke lebar wadah (margin auto jadi nol), di layar lebar `max-width`
   tetap membatasi dan margin auto tetap memusatkan. Tidak ada perilaku lama
   yang berubah. */
.nsh-main > .main-content-wrapper,
.nsh-main > .nsh-screen,
.nsh-main > .nsh-adopted { width: 100%; }

/* SENGAJA TIDAK ADA isolation/contain/overflow/transform DI SINI.
   Ketiganya membuat konteks penumpukan atau blok penampung baru, dan itu akan
   menjebak setiap modal `position:fixed` milik etalase lama di bawah bilah menu
   — tombol "Beli" pada modal varian akan tertutup dan tidak bisa disentuh.
   Penumpukan dua layar saat transisi sudah ditangani grid satu sel di atas,
   dan kelas animasinya DILEPAS setelah selesai supaya tidak ada konteks
   penumpukan yang tertinggal permanen. */

/* Layar yang sedang keluar dibekukan: interaksi dimatikan supaya tidak ada
   klik nyasar ke layar yang sudah "pergi". */
.nsh-leaving { pointer-events: none; }
.nsh-screen { min-width: 0; }

/* Halaman etalase berdiri sendiri yang diangkat ke dalam kerangka. Bilah menu
   miliknya sendiri disembunyikan supaya tidak ada dua bilah bertumpuk. */
.nsh-adopted { min-width: 0; }
.nsh-adopted .bottom-nav,
.nsh-adopted .sf-nav,
.nsh-adopted #sfBottomNav { display: none !important; }
.nsh-adopted > .gelombang-full:first-child { margin-top: 0; }

/* SKALA IKUT BERGERAK, BUKAN HANYA GESER.
   -------------------------------------------------------------------------
   Layar yang datang mulai sedikit lebih kecil dan menyusul ke ukuran penuh;
   yang pergi menyusut sedikit. Selisihnya hanya 1,5% — tidak terbaca sebagai
   "zoom", tetapi itulah yang memberi kesan kedalaman dan membuat perpindahan
   terasa lentur, bukan kertas yang digeser.
   Opasitas layar yang pergi diselesaikan lebih awal (offset .7) supaya tidak
   ada momen dua layar sama-sama pekat yang terbaca sebagai "kedip". */
@keyframes nshPushIn  {
  from { opacity: 0; transform: translate3d(30px, 0, 0) scale(.985); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
@keyframes nshPushOut {
  from { opacity: 1; transform: none; }
  70%  { opacity: 0; }
  to   { opacity: 0; transform: translate3d(-18px, 0, 0) scale(.99); }
}
@keyframes nshPopIn   {
  from { opacity: 0; transform: translate3d(-30px, 0, 0) scale(.985); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
@keyframes nshPopOut  {
  from { opacity: 1; transform: none; }
  70%  { opacity: 0; }
  to   { opacity: 0; transform: translate3d(18px, 0, 0) scale(.99); }
}
@keyframes nshFadeIn  {
  from { opacity: 0; transform: translate3d(0, 12px, 0) scale(.988); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
@keyframes nshFadeOut { from { opacity: 1; } to { opacity: 0; } }

.nsh-anim-push-in  { animation: nshPushIn  var(--nsh-t-screen-in)  var(--nsh-ease-glide) both; }
.nsh-anim-push-out { animation: nshPushOut var(--nsh-t-screen-out) var(--nsh-ease-in) both; }
.nsh-anim-pop-in   { animation: nshPopIn   var(--nsh-t-screen-in)  var(--nsh-ease-glide) both; }
.nsh-anim-pop-out  { animation: nshPopOut  var(--nsh-t-screen-out) var(--nsh-ease-in) both; }
.nsh-anim-fade-in  { animation: nshFadeIn  var(--nsh-t-screen-in)  var(--nsh-ease-glide) both; }
.nsh-anim-fade-out { animation: nshFadeOut var(--nsh-t-screen-out) linear both; }

/* Petunjuk compositor HANYA selama animasi berlangsung. Kelasnya dilepas
   setelah selesai (lihat cleanup di nsh_app.js), jadi tidak ada lapisan GPU
   yang tertinggal permanen — itu justru memakan memori di ponsel kelas bawah. */
.nsh-anim-push-in, .nsh-anim-pop-in, .nsh-anim-fade-in,
.nsh-anim-push-out, .nsh-anim-pop-out, .nsh-anim-fade-out {
  will-change: transform, opacity;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* ---------- 5. BATANG KEMAJUAN (atas layar) ---------------------------- */
/* Muncul hanya bila pengambilan data melebihi ~140 ms. Di bawah itu, batang
   yang berkedip justru membuat aplikasi terasa lebih lambat daripada diam. */
.nsh-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2.5px;
  z-index: 2147483000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 160ms linear;
  margin-top: var(--nsh-safe-t);
}
.nsh-progress.is-on { opacity: 1; }
.nsh-progress i {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: 0 var(--nsh-r-full) var(--nsh-r-full) 0;
  background: var(--nsh-accent, #10b981);
  box-shadow: 0 0 10px rgba(var(--nsh-accent-rgb, 16,185,129), .55);
  transition: width 200ms var(--nsh-ease-out);
}

/* ---------- 6. RANGKA MUAT (skeleton) ---------------------------------- */
/* Rangka mengikuti BENTUK layar tujuan, bukan kotak abu-abu generik. Itulah
   yang membuat jeda muat terasa seperti "layar sedang datang" dan bukan
   "aplikasi sedang menggantung". */
.nsh-skel { padding: 4px 0 8px; }
.nsh-sk {
  position: relative;
  overflow: hidden;
  background: var(--nsh-surface-3, #eef1f6);
  border-radius: var(--nsh-r-sm);
}
.nsh-sk::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  animation: nshSheen 1.25s infinite;
}
html[data-theme="dark"] .nsh-sk::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .07), transparent);
}
@keyframes nshSheen { to { transform: translateX(100%); } }

.nsh-sk-row { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; }
.nsh-sk-card {
  background: var(--nsh-surface, #fff);
  border: 1px solid var(--nsh-line-soft, #eef1f6);
  border-radius: var(--nsh-r-lg);
  padding: 14px;
  margin-bottom: 12px;
  box-shadow: var(--nsh-shadow-1);
}
.nsh-sk-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
@media (min-width: 700px)  { .nsh-sk-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1080px) { .nsh-sk-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ---------- 7. BILAH MENU BAWAH (tab bar) ------------------------------ */
/* Ini elemen yang paling menentukan "rasa aplikasi". Ukurannya mengikuti
   pedoman platform: target sentuh >= 44 px, ikon 24 px, label 10-11 px, dan
   jarak aman bawah perangkat ditambahkan, bukan ditebak. */
body.nsh-app .bottom-nav,
body.nsh-app .sf-nav {
  position: fixed;
  left: max(10px, var(--nsh-safe-l));
  right: max(10px, var(--nsh-safe-r));
  bottom: calc(var(--nsh-tabbar-gap) + var(--nsh-safe-b));
  margin: 0 auto;
  max-width: 560px;
  min-height: var(--nsh-tabbar-h);
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 2px;
  padding: 6px;
  border-radius: var(--nsh-r-xl);
  z-index: 60;
  /* Cadangan pekat lebih dulu untuk browser tanpa backdrop-filter. */
  background: var(--nsh-chrome-solid, #fff);
  border: 1px solid var(--nsh-chrome-line, rgba(15, 23, 42, .07));
  box-shadow: var(--nsh-shadow-3);
  /* Melindungi dari relayout saat isi halaman berubah tinggi.
     'paint' dihapus agar z-index dan compositing tetap benar saat
     transisi layar berjalan. */
  contain: layout;
  transition: transform var(--nsh-t) var(--nsh-ease), opacity var(--nsh-t) linear;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  body.nsh-app .bottom-nav,
  body.nsh-app .sf-nav {
    background: var(--nsh-chrome, rgba(255, 255, 255, .82));
    -webkit-backdrop-filter: blur(22px) saturate(180%);
    backdrop-filter: blur(22px) saturate(180%);
  }
}

/* Bilah disembunyikan HANYA saat papan ketik naik. Digeser, bukan display:none,
   supaya kembalinya tetap beranimasi.

   CATATAN PENTING: layar penuh (live chat) SENGAJA tidak termasuk. Live chat
   memakai seluruh tinggi jendela, tetapi bilah menu adalah satu-satunya jalan
   pembeli keluar dari sana — menyembunyikannya berarti menjebak orang di dalam
   percakapan sampai mereka menekan tombol kembali peramban. */
body.nsh-app.nsh-kbd .bottom-nav {
  transform: translate3d(0, calc(100% + var(--nsh-safe-b) + 24px), 0);
  opacity: 0;
  pointer-events: none;
}

body.nsh-app .bottom-nav a,
body.nsh-app .sf-nav a {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 6px 2px 5px;
  border-radius: var(--nsh-r-lg);
  text-decoration: none;
  font-size: 10.5px;
  font-weight: 650;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--nsh-text-3, #94a3b8);
  background: transparent;
  /* Warna dan latar dianimasikan; transform ditangani anak <span> supaya
     lencana keranjang tidak ikut membesar. */
  transition: color var(--nsh-t-fast) var(--nsh-ease),
              background var(--nsh-t) var(--nsh-ease);
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
body.nsh-app .bottom-nav a > span,
body.nsh-app .sf-nav a > span { max-width: 100%; }
/* Label harus boleh terpotong dengan elipsis. Anak flex menyusut sampai isi
   terpotong kalau min-width tidak dinolkan lebih dulu. */
body.nsh-app .bottom-nav a > span:last-child,
body.nsh-app .sf-nav a > span:last-child {
  display: block;
  min-width: 0;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}
body.nsh-app .bottom-nav a svg,
body.nsh-app .sf-nav a svg {
  width: 23px;
  height: 23px;
  stroke-width: 1.75;
  transition: transform var(--nsh-t) var(--nsh-spring);
}
body.nsh-app .bottom-nav a.active,
body.nsh-app .bottom-nav a.is-active,
body.nsh-app .sf-nav a.is-active {
  color: var(--nsh-accent, #10b981);
  background: var(--nsh-accent-soft, rgba(16, 185, 129, .1));
  font-weight: 800;
}
body.nsh-app .bottom-nav a.active svg,
body.nsh-app .bottom-nav a.is-active svg,
body.nsh-app .sf-nav a.is-active svg {
  stroke-width: 2.15;
  transform: translateY(-1px) scale(1.06);
}
/* Umpan balik tekan: menekan langsung, melepas memantul. Ini yang membuat
   tombol terasa "fisik" seperti aplikasi asli. */
body.nsh-app .bottom-nav a:active svg,
body.nsh-app .sf-nav a:active svg { transform: scale(.86); }
body.nsh-app .bottom-nav a:focus-visible,
body.nsh-app .sf-nav a:focus-visible {
  outline: 2px solid var(--nsh-accent, #10b981);
  outline-offset: -2px;
}

/* Titik aktif kecil di bawah tab — penanda posisi yang terbaca sekilas,
   bahkan bagi pengguna yang sulit membedakan warna. */
body.nsh-app .bottom-nav a.active::after,
body.nsh-app .bottom-nav a.is-active::after,
body.nsh-app .sf-nav a.is-active::after {
  content: '';
  position: absolute;
  bottom: 2px;
  left: 50%;
  width: 14px;
  height: 2.5px;
  margin-left: -7px;
  border-radius: var(--nsh-r-full);
  background: currentColor;
  animation: nshTabDot var(--nsh-t) var(--nsh-spring) both;
}
@keyframes nshTabDot { from { transform: scaleX(0); opacity: 0; } to { transform: none; opacity: .9; } }

body.nsh-app .cart-badge,
body.nsh-app .sf-nav .sf-badge {
  position: absolute;
  top: -5px;
  right: -10px;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--nsh-r-full);
  background: var(--nsh-danger, #be123c);
  color: #fff;
  font-size: 9.5px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0;
  box-shadow: 0 0 0 2px var(--nsh-chrome-solid, #fff);
}
.nsh-badge-pop { animation: nshBadgePop 420ms var(--nsh-spring); }
@keyframes nshBadgePop {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.45); }
  100% { transform: scale(1); }
}

/* ---------- 8. TABLET & DESKTOP: bilah jadi dok ------------------------ */
/* Di layar lebar, bilah tab ponsel terlihat aneh menempel di bawah. Dijadikan
   dok mengambang bergaya aplikasi desktop: ikon dan label sebaris, lebih
   ramping, tetap berada di jangkauan kursor. */
@media (min-width: 900px) {
  :root { --nsh-tabbar-h: 54px; }
  body.nsh-app .bottom-nav,
  body.nsh-app .sf-nav {
    max-width: max-content;
    left: 0;
    right: 0;
    gap: 4px;
    padding: 7px 9px;
  }
  body.nsh-app .bottom-nav a,
  body.nsh-app .sf-nav a {
    flex: 0 0 auto;
    flex-direction: row;
    gap: 8px;
    padding: 8px 16px;
    font-size: 12.5px;
  }
  body.nsh-app .bottom-nav a > span:last-child,
  body.nsh-app .sf-nav a > span:last-child { width: auto; }
  body.nsh-app .bottom-nav a.active::after,
  body.nsh-app .bottom-nav a.is-active::after,
  body.nsh-app .sf-nav a.is-active::after { display: none; }
  body.nsh-app .bottom-nav a:hover,
  body.nsh-app .sf-nav a:hover {
    color: var(--nsh-text, #111827);
    background: var(--nsh-surface-3, #f1f5f9);
  }
  body.nsh-app .bottom-nav a.active:hover,
  body.nsh-app .bottom-nav a.is-active:hover { color: var(--nsh-accent); background: var(--nsh-accent-soft-2); }
}

/* ---------- 9. PENANDA OFFLINE ----------------------------------------- */
.nsh-net {
  position: fixed;
  left: 50%;
  top: calc(var(--nsh-safe-t) + 10px);
  transform: translate(-50%, -140%);
  z-index: 2147482000;
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: calc(100vw - 24px);
  padding: 9px 15px;
  border-radius: var(--nsh-r-full);
  background: var(--nsh-text, #111827);
  color: #fff;
  font-size: 12.5px;
  font-weight: 650;
  box-shadow: var(--nsh-shadow-3);
  transition: transform var(--nsh-t-slow) var(--nsh-spring);
  pointer-events: none;
}
.nsh-net.is-on { transform: translate(-50%, 0); }
.nsh-net.is-ok { background: var(--nsh-ok, #15803d); }
.nsh-net .nsh-i { width: 16px; height: 16px; }

/* ---------- 10. TARIK UNTUK MENYEGARKAN -------------------------------- */
.nsh-ptr {
  position: fixed;
  left: 50%;
  top: calc(var(--nsh-safe-t) + 4px);
  width: 34px;
  height: 34px;
  margin-left: -17px;
  border-radius: var(--nsh-r-full);
  background: var(--nsh-surface, #fff);
  box-shadow: var(--nsh-shadow-2);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2147481000;
  opacity: 0;
  pointer-events: none;
  transform: translate3d(0, -46px, 0);
  color: var(--nsh-accent, #10b981);
}
.nsh-ptr svg { width: 19px; height: 19px; }
.nsh-ptr.is-spin svg { animation: nshSpin .85s linear infinite; }
@keyframes nshSpin { to { transform: rotate(360deg); } }

/* ---------- 11. HALAMAN GAGAL / OFFLINE DALAM KERANGKA ----------------- */
.nsh-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
  padding: 54px 22px;
  min-height: 46vh;
}
.nsh-state-ico {
  width: 62px;
  height: 62px;
  border-radius: var(--nsh-r-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--nsh-surface-3, #eef1f6);
  color: var(--nsh-text-3, #94a3b8);
  animation: nshFloat 2.8s ease-in-out infinite;
}
.nsh-state-ico svg { width: 28px; height: 28px; }
@keyframes nshFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
.nsh-state h3 { margin: 2px 0 0; font-size: 16px; font-weight: 800; color: var(--nsh-text); letter-spacing: -.02em; }
.nsh-state p  { margin: 0; font-size: 13px; line-height: 1.6; color: var(--nsh-text-2); max-width: 300px; }
.nsh-btn {
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 44px;
  padding: 0 22px;
  border-radius: var(--nsh-r);
  background: var(--nsh-accent, #10b981);
  color: var(--nsh-accent-on, #fff);
  font-size: 13.5px;
  font-weight: 750;
  letter-spacing: -.01em;
  box-shadow: var(--nsh-shadow-2);
  transition: transform var(--nsh-t-fast) var(--nsh-ease), filter var(--nsh-t-fast) linear;
  touch-action: manipulation;
}
.nsh-btn:hover { filter: brightness(1.06); }
.nsh-btn:active { transform: scale(.96); }
.nsh-btn-ghost {
  background: var(--nsh-surface-3, #eef1f6);
  color: var(--nsh-text, #111827);
  box-shadow: none;
}

/* ---------- 12. PENYELARASAN PERMUKAAN LAMA ---------------------------- */
/* Etalase lama memakai warna Tailwind yang dipatok. Yang diselaraskan di sini
   hanya PERMUKAAN (latar, garis, bayangan, sudut) — bukan tata letak — supaya
   seluruh aplikasi terlihat satu keluarga tanpa satu pun elemen bergeser. */
body.nsh-app .card {
  background: var(--nsh-surface, #fff);
  border: 1px solid var(--nsh-line-soft, #eef1f6);
  border-radius: var(--nsh-r-lg);
  box-shadow: var(--nsh-shadow-1);
  transition: box-shadow var(--nsh-t) var(--nsh-ease), transform var(--nsh-t) var(--nsh-ease);
}
body.nsh-app .app-toast {
  /* dock-base, BUKAN content-bottom: content-bottom sudah memuat tinggi
     lapisan mengambang lain, jadi memakainya di sini membuat toast
     mendorong dirinya sendiri naik setiap kali diukur ulang. */
  bottom: calc(var(--nsh-dock-base) + 12px);
  background: var(--nsh-text, #111827);
  border-radius: var(--nsh-r);
  font-size: 13px;
  font-weight: 650;
  box-shadow: var(--nsh-shadow-3);
  padding: 12px 18px;
  z-index: 70;
}
/* Bilah pencarian lengket harus ikut memperhitungkan area aman atas, kalau
   tidak ia bersembunyi di bawah takik pada iPhone yang dipasang layar penuh. */
body.nsh-app header.sticky { top: var(--nsh-safe-t); }

/* Ornamen gelombang kepala halaman: bagian kerangka permanen. Disembunyikan
   pada layar yang mengatur tingginya sendiri. */
body.nsh-app.nsh-nochrome .gelombang-full { display: none; }

/* Kolom isian: satu tinggi minimum, satu radius, satu cincin fokus. */
body.nsh-app input:focus-visible,
body.nsh-app select:focus-visible,
body.nsh-app textarea:focus-visible {
  outline: 2px solid var(--nsh-accent, #10b981);
  outline-offset: 1px;
}

/* ---------- 13. GERAK BERKURANG, CETAK, KONTRAS TINGGI ----------------- */
@media (prefers-reduced-motion: reduce) {
  .nsh-anim-push-in, .nsh-anim-push-out, .nsh-anim-pop-in,
  .nsh-anim-pop-out, .nsh-anim-fade-in, .nsh-anim-fade-out {
    animation-duration: 1ms !important;
  }
  .nsh-sk::after, .nsh-state-ico, .nsh-badge-pop { animation: none !important; }
  body.nsh-app .bottom-nav a svg,
  body.nsh-app .sf-nav a svg { transition: none; }
  .nsh-ptr.is-spin svg { animation-duration: 1.6s; }
}
@media (prefers-contrast: more) {
  body.nsh-app .bottom-nav,
  body.nsh-app .sf-nav { border-width: 2px; border-color: var(--nsh-text-2); }
  body.nsh-app .card { border-color: var(--nsh-text-3); }
}
@media print {
  .nsh-progress, .nsh-net, .nsh-ptr,
  body.nsh-app .bottom-nav, body.nsh-app .sf-nav { display: none !important; }
  body.nsh-app, html.nsh-on body.nsh-app { padding-bottom: 0; }
}

/* ==========================================================================
   14. PEMILIH CABANG — VARIAN RAMPING DI DALAM BILAH LENGKET
   --------------------------------------------------------------------------
   Modul Multi Cabang merender kartunya sendiri (namespace .mcbsf-). Di
   beranda etalase kartu itu kini tinggal DI DALAM bilah pencarian yang
   lengket, jadi ia harus setipis satu baris daftar aplikasi native — tanpa
   kehilangan satu pun informasi yang mengubah keputusan pembeli.

   Semua aturan di bawah dibatasi .nsh-mcb-slim. Kartu cabang di halaman
   keranjang, detail produk, dan panel admin memakai markup yang sama persis
   dan TIDAK tersentuh sama sekali.
   ========================================================================== */
.nsh-mcb-slim { margin: 6px -2px 0; }

/* Kartu jadi baris: tanpa bingkai, tanpa bayangan, tanpa sudut membulat —
   bingkainya sudah disediakan bilah lengket yang memuatnya. */
.nsh-mcb-slim .mcbsf-wrap { margin: 0; }
.nsh-mcb-slim .mcbsf-card2 {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
}
.nsh-mcb-slim .mcbsf-bar {
  gap: 8px;
  padding: 2px 2px 0;
  min-height: 34px;
}

/* Ikon dikecilkan tapi tetap berwarna merek: itu penanda tercepat bahwa baris
   ini soal LOKASI, bukan sekadar teks tambahan di bawah kolom pencarian. */
.nsh-mcb-slim .mcbsf-ico {
  width: 26px;
  height: 26px;
  border-radius: 8px;
}
.nsh-mcb-slim .mcbsf-ico svg { width: 15px; height: 15px; }

/* Label "CABANG" dan nama disatukan pada SATU baris. Label tetap ada karena
   tanpanya sebuah nama tempat di bawah kolom pencarian mudah disalahartikan
   sebagai kata kunci pencarian terakhir. */
.nsh-mcb-slim .mcbsf-txt { gap: 5px; padding: 3px 2px; }
.nsh-mcb-slim .mcbsf-txtin {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 2px 7px;
  min-width: 0;
}
.nsh-mcb-slim .mcbsf-lbl {
  font-size: 9px;
  letter-spacing: .1em;
  line-height: 1.2;
  flex: none;
}
.nsh-mcb-slim .mcbsf-name {
  font-size: 12.5px;
  line-height: 1.25;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nsh-mcb-slim .mcbsf-sub {
  margin-top: 0;
  font-size: 10.5px;
  line-height: 1.25;
  gap: 4px;
  min-width: 0;
}
.nsh-mcb-slim .mcbsf-subt {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nsh-mcb-slim .mcbsf-go svg { width: 15px; height: 15px; }

/* Tombol aksi dikecilkan, tetapi target sentuhnya DIPERTAHANKAN 34px —
   di bawah itu tombol "gunakan lokasi saya" mulai sulit dikenai ibu jari. */
.nsh-mcb-slim .mcbsf-ib {
  width: 30px;
  height: 30px;
  border-radius: 9px;
}
.nsh-mcb-slim .mcbsf-ib svg { width: 15px; height: 15px; }
.nsh-mcb-slim .mcbsf-act { gap: 4px; }

/* Bagian detail (alamat, telepon, peta, chip filter) disembunyikan dari baris
   ramping — semuanya tetap tersedia di dalam sheet pemilih cabang. Tombol
   ciutkan ikut disembunyikan karena tidak ada lagi yang bisa diciutkan. */
.nsh-mcb-slim .mcbsf-body,
.nsh-mcb-slim .mcbsf-ib[data-mcbsf-toggle] { display: none; }

/* Di layar sangat sempit, kota & jarak mengalah demi nama cabang. */
@media (max-width: 359px) {
  .nsh-mcb-slim .mcbsf-sub { display: none; }
}

/* Tablet & desktop punya ruang: label dan nama diberi sedikit napas. */
@media (min-width: 768px) {
  .nsh-mcb-slim .mcbsf-name { font-size: 13.5px; }
  .nsh-mcb-slim .mcbsf-sub { font-size: 11px; }
}

/* ==========================================================================
   15. AJAKAN PASANG APLIKASI — URUTAN TUMPUKAN DI DALAM ETALASE
   --------------------------------------------------------------------------
   Modul PWA memberi bilah ajakannya z-index tertinggi yang mungkin, karena ia
   dipakai juga di halaman yang tidak punya lapisan dialog sendiri. Di dalam
   etalase itu keliru: bilah promosi menutupi sheet pemilih cabang, modal
   varian, dan lembar data pemesan — pembeli tidak bisa menyelesaikan
   pesanannya sampai ia menutup iklan.

   Di sini tuan rumahlah yang memutuskan urutannya: ajakan berada DI ATAS isi
   halaman, tetapi DI BAWAH setiap dialog (60+) dan di atas bilah menu tanpa
   pernah menutupinya. Aturan dibatasi body.nsh-app, jadi aplikasi penjual
   dan halaman lain yang memakai modul yang sama tidak ikut berubah.
   ========================================================================== */
body.nsh-app #tkpwa-banner:not(.tkpwa-as-modal) {
  z-index: 45;
  /* dock-base: lihat catatan pada .app-toast. */
  bottom: calc(var(--nsh-dock-base) + 8px);
}
body.nsh-app #tkpwa-sticky { z-index: 45; }

/* Saat sebuah dialog terbuka, halaman dikunci (html{overflow:hidden}).
   Ajakan ikut menyingkir supaya tidak ada dua lapisan mengambang bersamaan. */
html[style*="overflow: hidden"] body.nsh-app #tkpwa-banner:not(.tkpwa-as-modal) {
  opacity: 0;
  pointer-events: none;
  transform: translate3d(0, 12px, 0);
  transition: opacity .18s linear, transform .18s var(--nsh-ease);
}

/* ==========================================================================
   16. DUA LAPIS BILAH LENGKET
   --------------------------------------------------------------------------
   Baris filter kategori (user_produkreguler.php) menempel di bawah bilah
   pencarian. Offsetnya dulu ditulis tetap `top-[50px]`; begitu isi bilah atas
   berubah — misalnya pemilih cabang ikut masuk ke sana — angka itu salah dan
   kedua lapisan saling menimpa saat halaman digulir.

   --nsh-header-h diukur pemandu aplikasi setiap layar dipasang, saat jendela
   diubah ukurannya, dan setelah huruf swadaya selesai dimuat. Nilai cadangan
   50px dipakai bila pemandu tidak aktif, sehingga perilaku lama tetap utuh.
   ========================================================================== */
body.nsh-app .sticky.top-\[50px\] {
  top: calc(var(--nsh-header-h, 50px) + var(--nsh-safe-t)) !important;
}

/* ==========================================================================
   17. BARIS BERGULIR MENDATAR — PENANDA "MASIH ADA LAGI"
   --------------------------------------------------------------------------
   Baris chip koleksi dan kategori lebih lebar dari layar (1284px pada layar
   347px), tetapi ujungnya terpotong rata tanpa tanda apa pun. Di layar sentuh
   itu terbaca sebagai teks terpotong, bukan sebagai daftar yang bisa digeser —
   dan pembeli tidak pernah menemukan sisa kategorinya.

   Gradien tipis di tepi kanan adalah isyarat yang dipakai aplikasi native:
   memberi tahu ada isi lagi tanpa memakan ruang atau menambah tombol.
   Memakai mask, jadi ia bekerja di atas latar warna apa pun — termasuk saat
   pengunjung memilih tema gelap.
   ========================================================================== */
@supports ((-webkit-mask-image: linear-gradient(#000, #000)) or (mask-image: linear-gradient(#000, #000))) {
  body.nsh-app .tx-home-row {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
  }
  /* Sudah tergulir sampai ujung: penanda dilepas supaya chip terakhir tidak
     terlihat pudar seolah dinonaktifkan. */
  body.nsh-app .tx-home-row[data-scroll-end="1"] {
    -webkit-mask-image: none;
    mask-image: none;
  }
}

/* Pemutar tunggu untuk tombol yang sedang memproses. Menggantikan emoji jam
   pasir: mewarisi warna tombol, sama di semua platform, dan berhenti berputar
   bila perangkat disetel "kurangi gerak". */
.nsh-spin { animation: nshSpin .85s linear infinite; }
@media (prefers-reduced-motion: reduce) { .nsh-spin { animation-duration: 2.4s; } }
