select.select-modern option::checkmark {
	content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%230e8bf1' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 12 5 5L20 7'/%3E%3C/svg%3E");
	width: var(--size-icon-sm);
	height: var(--size-icon-sm);
}

	/* Layout Utama: 2 Kolom (Menggunakan Flexbox) */
	.dashboard-wrapper {
		display: flex;
		min-height: 100vh;
		align-items: stretch;
	}

	/* Konten Utama */
	.dashboard-content {
		flex: 1;          /* Mengisi sisa ruang di sebelah sidebar */
		min-width: 0;     /* Mencegah overflow horizontal pada grid */
		padding: var(--size-400); /* Menggunakan token spacing design system kamu */
	}

	/* 	Layout Utama: Mendorong konten agar posisinya di bawah Navbar
		Navbar dashboard sekarang sticky biasa (bukan floating pill),
		jadi jaraknya cukup setinggi navbar itu sendiri (--navbar-height-md). */
	.dashboard-wrapper {
		display: flex;
		min-height: 100vh;
		align-items: stretch;
		/* Jarak atas = tinggi pill + padding atas/bawah navbar
		padding-top: calc(var(--navbar-pill-height) + (var(--size-400) * 2));*/
	}

	/* Penyesuaian Khusus Sidebar di dalam Dashboard */
	.dashboard-wrapper .sidebar {
		position: sticky;
		/* Mendorong sidebar agar menempel tepat di bawah navbar-dashboard */
		top: var(--navbar-height-md);
		/* Tinggi sidebar dikurangi tinggi navbar agar footer tidak terpotong */
		height: calc(100vh - var(--navbar-height-md));
		/* border-right: 1px solid var(--color-border-default); */
		border-right: none;
	}

	.dashboard-content {
		flex: 1;
		min-width: 0;
		padding: var(--size-600); /* Menggunakan spacing 24px dari sistemmu */
	}

	/* Konten dashboard butuh jarak dari navbar sticky biasa — jauh lebih kecil
	dari .page-body milik area public (yang mengasumsikan navbar pill
	floating). Sidebar sudah punya "top" sendiri (lihat bagian SIDEBAR
	DASHBOARD di atas), jadi ini hanya untuk .dashboard-content. */
	.dashboard-content {
		padding-top: var(--size-600);
	}


	/* SIDEBAR DASHBOARD	*/
	/* 1. Kunci posisi ikon di sebelah kiri agar tidak pernah bergeser atau melompat
	(berlaku untuk .sidebar-item biasa DAN .sidebar-item-collapsed-link saat
	EXPANDED — walau collapsed-link sendiri baru tampil saat collapsed,
	rule ini tetap jadi nilai dasarnya sebelum override di 1b). */
	.sidebar-item,
	.sidebar.is-collapsed .sidebar-item {
		justify-content: flex-start !important;
		padding-inline: var(--size-250) !important; /*TODO : tadinya --size-300*/
		gap: var(--size-300) !important;
	}

	/* 1a. Nilai dasar collapsed-link — samakan dengan .sidebar-item biasa.
	TIDAK pakai !important di sini supaya rule 1b (khusus collapsed) bisa
	menimpanya tanpa ambigu soal urutan cascade. */
	.sidebar-item-collapsed-link {
		justify-content: flex-start;
		padding-inline: var(--size-300);
		gap: var(--size-300);
		transition: padding-left var(--duration-moderate) var(--ease-in-out);
	}

	/* 1b. FIX ESTETIKA: icon .sidebar-item-collapsed-link (Keuangan/Customer
	saat collapsed) digeser ke tengah rel 60px — TAPI lewat padding-left
	yang dianimasikan, bukan justify-content: center yang loncat instan.

	PENTING soal perhitungan (bug yang sama seperti .sidebar-user
	sebelumnya): .sidebar-item-collapsed-link berada di dalam
	.sidebar-item-group, yang merupakan child langsung dari .sidebar-nav
	— dan .sidebar-nav PUNYA PADDING SENDIRI (var(--size-300) di kiri
	dan kanan, dari sidebar.css). Jadi ruang yang benar-benar tersedia
	untuk collapsed-link saat sidebar collapsed BUKAN --sidebar-collapsed
	(60px) secara langsung, tapi --sidebar-collapsed dikurangi padding
	kiri+kanan .sidebar-nav dulu (2 x var(--size-300)) — baru sisanya
	dibagi dua untuk padding kiri+kanan icon.

	(Revisi sebelumnya salah menghitung langsung dari 60px tanpa
	mengurangi padding .sidebar-nav, hasilnya padding kebesaran ~20px
	per sisi padahal seharusnya ~8px — makanya icon kelihatan belum
	sampai tengah / masih condong ke kiri.) */
	.sidebar.is-collapsed .sidebar-item-collapsed-link {
		padding-left: calc(
			(var(--sidebar-collapsed) - (var(--size-300) * 2) - var(--size-icon-md)) / 2
		);
		padding-right: calc(
			(var(--sidebar-collapsed) - (var(--size-300) * 2) - var(--size-icon-md)) / 2
		);
	}


	/* 2. Animasikan label teks saja agar menyusut dan menghilang secara halus.

	FIX (revisi): max-width sebelumnya pakai var(--duration-moderate)
	(250ms) — jauh lebih cepat dari var(--duration-expand) (1s) yang
	dipakai sidebar untuk collapse DAN yang dipakai FIX 4b di bawah
	untuk delay visibility pada .sidebar-item-accordion-trigger.
	Akibatnya walau opacity sudah dinetralkan (FIX 4) dan visibility
	trigger sudah didelay 1 detik (FIX 4b), teks "Finance"/"Customer"
	tetap kelihatan hilang duluan — bukan lewat opacity atau
	visibility, tapi lewat max-width:0 yang diam-diam masih 250ms.
	Disamakan ke --duration-expand supaya ketiga mekanisme (opacity,
	visibility trigger, max-width) benar-benar sinkron di 1 detik. */
	.sidebar-item-label {
		transition: opacity var(--duration-fast) var(--ease-default),
					max-width var(--duration-expand) var(--ease-default) !important;
		max-width: 250px; /* Nilai dummy agar bisa dianimasikan ke 0 */
		white-space: nowrap;
		overflow: hidden;
	}

	/* 3. Saat sidebar mengecil, teks dan panah (caret) diciutkan hingga 0px */
	.sidebar.is-collapsed .sidebar-item-label {
		max-width: 0 !important;
		opacity: 0;
		margin: 0;
	}

	/* CATATAN (lihat juga FIX 4b di bagian SINKRONISASI di bawah):
	.sidebar-item-accordion-caret berada DI DALAM
	.sidebar-item-accordion-trigger (<summary>), yang sekarang sudah
	di-delay visibility-nya mengikuti --duration-expand. Karena trigger
	sudah tertutup rapi belakangan, opacity:0 instan di sini (tanpa
	transition) akan bikin caret "berkedip hilang" duluan sebelum
	trigger-nya sendiri sembunyi. opacity dinetralkan ke 1 (biarkan
	visibility parent yang bekerja); max-width/width/margin:0 tetap
	dipertahankan supaya ruang caret tidak makan tempat di rel 60px. */
	.sidebar.is-collapsed .sidebar-item-accordion-caret,
	.sidebar.is-collapsed .sidebar-group-caret {
		opacity: 1;
		max-width: 0;
		width: 0; /* Memastikan ruang panah benar-benar hilang dari flow */
		margin: 0;
		overflow: hidden;
	}

	/* 4. FIX: header sidebar (logo + tombol collapse) — jangan pakai
	justify-content: center saat collapsed. justify-content TIDAK bisa
	dianimasikan (bukan seperti width/opacity/max-width), jadi kalau
	sidebar.css mengubahnya jadi "center" saat class is-collapsed
	ditambahkan, tombol collapse langsung LONCAT ke tengah SEKETIKA —
	padahal sidebar baru MULAI menyempit dari 240px, transisinya belum
	selesai. Efeknya kelihatan seperti tombol "kaget" pindah ke tengah
	duluan, baru menyusul sidebar menutup pelan-pelan.

	Override rule sidebar.css (.sidebar.is-collapsed .sidebar-header)
	supaya justify-content TETAP space-between (sama seperti kondisi
	normal). Posisi tombol jadi stabil di kanan (karena navbar-brand
	sudah di-display:none, jadi space-between otomatis mendorong
	collapse-btn ke satu-satunya sisi yang tersisa) — bukan lompat ke
	tengah. Margin-inline:auto sebagai fallback kalau desainmu tetap
	ingin dia "mengambang" di tengah ruang 60px collapsed, tapi
	transisinya sekarang ikut smooth mengikuti penyempitan width
	sidebar, bukan loncat instan. */
	.sidebar.is-collapsed .sidebar-header {
		justify-content: flex-start !important;
	}
	.sidebar.is-collapsed .sidebar-collapse-btn {
		margin-inline: auto;
	}

	/* 5. FIX: .sidebar-user (avatar + nama di footer) terpotong & tidak
	center saat collapsed.

	AKAR MASALAH: .sidebar-user-info (nama + role) di sidebar.css cuma
	di-opacity:0 saat collapsed — bukan width:0 atau display:none. Jadi
	walau tidak kelihatan, dia MASIH memakan ruang horizontal penuh
	secara layout. Karena .sidebar-user dan .sidebar-footer punya
	overflow:hidden, avatar yang seharusnya utuh di kiri jadi ke-clip
	sebagian — total lebar (avatar + gap + info transparan) lebih besar
	dari 60px collapsed, jadi avatar "kepotong" dari sisi kanan.

	FIX #1 — keluarkan .sidebar-user-info dari flow sepenuhnya saat
	collapsed (bukan cuma opacity), pakai pola yang sama seperti label
	item sidebar lainnya. */
	.sidebar.is-collapsed .sidebar-user-info {
		width: 0;
		max-width: 0;
		margin: 0;
		flex: none;
	}

	/* FIX #2 — .sidebar-user sendiri di-center-kan lewat padding yang
	dianimasikan (bukan justify-content, dengan alasan yang sama seperti
	fix-fix sebelumnya: justify-content tidak bisa dianimasikan dan akan
	loncat instan).

	PENTING soal perhitungan: .sidebar-user berada DI DALAM .sidebar-footer,
	yang sudah punya padding sendiri (var(--size-300) di semua sisi, dari
	sidebar.css). Jadi ruang horizontal yang benar-benar tersedia untuk
	.sidebar-user saat collapsed BUKAN --sidebar-collapsed (60px) secara
	langsung, tapi --sidebar-collapsed dikurangi padding kiri+kanan
	.sidebar-footer dulu — baru sisanya dibagi dua untuk padding kiri+kanan
	avatar. Kalau ini salah dihitung (langsung pakai 60px tanpa
	mengurangi padding footer), avatar akan tetap tidak center / bahkan
	overflow lagi. */
	.sidebar-user {
		transition: padding-left var(--duration-moderate) var(--ease-in-out),
					padding-right var(--duration-moderate) var(--ease-in-out);
	}
	.sidebar.is-collapsed .sidebar-user {
		padding-left: calc(
			(var(--sidebar-collapsed) - (var(--size-300) * 2) - var(--size-avatar-sm)) / 2
		);
		padding-right: calc(
			(var(--sidebar-collapsed) - (var(--size-300) * 2) - var(--size-avatar-sm)) / 2
		);
	}


	/* ============================================================
	SINKRONISASI ANIMASI SIDEBAR DENGAN ACCORDION <details>
	============================================================
	Accordion submenu (.sidebar-item-accordion) sudah pakai animasi
	native details/summary:

		transition: block-size 1s cubic-bezier(0.4, 0, 0.2, 1),
					content-visibility 1s cubic-bezier(0.4, 0, 0.2, 1);
		transition-behavior: allow-discrete;

	Nilai itu sama dengan token design system:
		1s                       = var(--duration-expand)
		cubic-bezier(0.4,0,0.2,1) = var(--ease-default)

	Sementara sidebar.css (design system) mengatur buka/tutup SIDEBAR
	ITU SENDIRI (bukan submenu) pakai token yang BEDA:
		transition: width var(--duration-moderate) var(--ease-in-out);
		(--duration-moderate = 250ms, --ease-in-out = cubic-bezier(0.4,0,0.6,1))

	Akibatnya: buka-tutup submenu terasa "1 detik, smooth khas details",
	sedangkan buka-tutup sidebar collapse terasa jauh lebih cepat &
	dengan lengkung easing berbeda — dua interaksi yang mestinya senada
	malah terasa dua bahasa gerak berbeda.

	FIX: override semua titik yang mengatur animasi collapse SIDEBAR
	(bukan konten submenu-nya) supaya pakai --duration-expand +
	--ease-default juga. Sengaja tidak dipakaikan block-size/
	content-visibility/allow-discrete di sini karena trik itu hanya
	relevan untuk auto-height pada elemen <details> (lewat
	::details-content) — sidebar collapse mengubah WIDTH lewat toggle
	class biasa, dan width sudah bisa dianimasikan langsung tanpa
	trik itu. Yang perlu disamakan hanya DURASI dan EASING-nya saja,
	supaya kedua interaksi terasa satu sistem gerak yang sama. */

	/* 1. Sidebar desktop — lebar 240px <-> 60px */
	.sidebar {
		transition: width var(--duration-expand) var(--ease-default) !important;
	}

	/* 2. Sidebar mobile (<1024px) — slide in/out + lebar.
	Dua properti digabung satu transition declaration supaya keduanya
	start/selesai di waktu yang sama persis (translateX dan width
	tidak boleh punya durasi berbeda, nanti terasa "sempal"). */
	@media (max-width: 1023px) {
		.sidebar {
			transition: transform var(--duration-expand) var(--ease-default),
						width var(--duration-expand) var(--ease-default) !important;
		}
	}

	/* 3. Chevron tombol collapse (bukan caret accordion — ini ikon
	di sidebar-header yang berputar 180deg saat sidebar collapsed) */
	.sidebar-collapse-btn svg {
		transition: transform var(--duration-expand) var(--ease-default) !important;
	}

	/* 4. FIX AKAR MASALAH: design-system_min.css memberi
	.sidebar-brand-name, .sidebar-item-label, dan .sidebar-user-info
	masing-masing transition opacity sendiri dengan --duration-fast
	(80ms) — jauh lebih cepat dari --duration-expand (1s) milik sidebar.
	Pemicu opacity:0 ada di selector gabungan yang lebih spesifik
	(.sidebar.is-collapsed .sidebar-item-label, dst — 0,0,2,1),
	sehingga override transition-duration di selector tunggal
	(.sidebar-item-label — 0,0,1,0) TIDAK PERNAH menang secara
	spesifisitas dan animasi 80ms tetap jalan. Efeknya teks hilang
	duluan sebelum sidebar (1s) selesai menyempit.

	Daripada mengejar-ngejar spesifisitas untuk menyamakan dua timing
	berbeda, solusi paling stabil: MATIKAN transisi opacity-nya sendiri
	pada elemen-elemen ini. Parentnya (.sidebar-item, .sidebar-header,
	.sidebar-footer, dst) sudah overflow:hidden dan menyempit mengikuti
	var(--duration-expand) lewat width sidebar — jadi teks otomatis
	tertutup/terpotong seiring sidebar mengecil, tanpa perlu efek fade
	sendiri yang timing-nya bisa lepas dari sidebar.

	Selector diberi bobot yang sama (.sidebar.is-collapsed ...) supaya
	pasti menang atas rule opacity bawaan design-system, lalu opacity
	dan transition-nya dinetralkan. */
	.sidebar.is-collapsed .sidebar-brand-name,
	.sidebar.is-collapsed .sidebar-section-label,
	.sidebar.is-collapsed .sidebar-item-label,
	.sidebar.is-collapsed .sidebar-item-badge,
	.sidebar.is-collapsed .sidebar-user-info {
		transition: none !important;
		opacity: 1 !important;
	}

	/* 4b. PENYEBAB UTAMA "Finance"/"Customer" hilang instan (chevron ikut
	juga): .sidebar-item-label untuk kedua item ini TIDAK berada
	langsung di .sidebar-item biasa, tapi di dalam
	.sidebar-item-accordion-trigger (elemen <summary> milik
	<details class="sidebar-item-accordion">) — lihat markup:
		<summary class="sidebar-item sidebar-item-accordion-trigger">
			<span class="sidebar-item-label">Finance</span>
			<span class="sidebar-item-accordion-caret">...</span>
		</summary>

	design-system_min.css punya rule TERPISAH yang menyasar seluruh
	<summary> ini (bukan cuma label di dalamnya):

		.sidebar.is-collapsed .sidebar-item-accordion-trigger {
			visibility: hidden;
		}

	visibility:hidden ini TIDAK PUNYA transition sama sekali di
	design-system — jadi begitu class is-collapsed ditambahkan, seluruh
	<summary> (icon + label "Finance"/"Customer" + caret) langsung
	tak-terlihat dalam 0ms, jauh sebelum sidebar (1s) selesai
	menyempit. Fix poin 4 di atas (opacity pada .sidebar-item-label)
	tidak menyentuh masalah ini karena jalurnya beda: bukan opacity,
	tapi visibility pada parent <summary>-nya.

	(Elemen ini memang HARUS akhirnya tersembunyi saat collapsed —
	digantikan oleh .sidebar-item-collapsed-link versi icon-only di
	luar <details> — supaya tidak dobel tampil. Yang salah cuma
	TIMING-nya, bukan perilakunya.)

	FIX: beri transition-delay pada visibility yang sama panjang
	dengan --duration-expand (1s), supaya trigger baru benar-benar
	tersembunyi SETELAH sidebar selesai menyempit ke 60px — bukan
	instan di awal klik. Visibility butuh "transition: visibility ...
	var(--duration-expand)" (bukan cuma transition-delay saja) supaya
	properti visibility ikut dianimasikan/didelay, bukan diterapkan
	langsung. */
	.sidebar.is-collapsed .sidebar-item-accordion-trigger {
		transition: visibility var(--duration-expand) var(--ease-default) !important;
	}

	/* 4c. PENYEBAB "Dashboard" (brand name di header) hilang instan:
	BEDA elemen dan BEDA mekanisme dari fix 4/4b di atas. sidebar.css
	punya rule terpisah yang menyasar seluruh <a class="navbar-brand">
	(bukan cuma span .sidebar-brand-name di dalamnya):

		.sidebar.is-collapsed .sidebar-header .navbar-brand {
			display: none;
		}

	display:none TIDAK BISA dianimasikan atau diberi delay lewat
	transition biasa sama sekali (beda dari opacity/visibility) — begitu
	dia diterapkan, elemen langsung hilang dari render dalam 0ms.

	PERCOBAAN PERTAMA (sudah dicoba, GAGAL — catatan supaya tidak
	diulangi): delay display penuh 1 detik (var(--duration-expand))
	pakai transition-behavior:allow-discrete. Asumsinya
	.sidebar-header yang overflow:hidden akan ikut memotong visual
	.navbar-brand selagi dia tertahan. Realitanya TIDAK — .navbar-brand
	punya flex-shrink:0 (lihat design-system_min.css), jadi box-nya
	tetap dipertahankan selebar konten aslinya (ikon+teks "Dashboard")
	walau parent sudah 60px. overflow:hidden pada .sidebar-header
	ternyata tidak meng-clip box tetangga ini secara visual — hasilnya
	teks "Dashboard" tetap tampil penuh, MELUBER menutupi/tumpang
	tindih dengan konten di sebelah kanan sidebar, selama sisa durasi
	delay. Testing menunjukkan sidebar sudah selesai menyempit ke 60px
	duluan, tapi "Dashboard" masih nongkrong di atas breadcrumb konten
	selama sisa 1 detik — kesannya jadi "telat hilang", bukan lagi
	"instan hilang". Sama-sama salah, cuma pindah arah.

	FIX YANG BENAR: karena overflow:hidden tidak bisa diandalkan
	memotong box ini, JANGAN coba menyamakan durasi delay dengan
	--duration-expand (1s) sama sekali. Sebagai gantinya:
	(1) opacity turun lebih dulu & lebih cepat (15% dari durasi
	collapse, ~150ms) — box mulai transparan hampir seketika, jadi
	tidak ada kesan "menggantung diam";
	(2) display baru benar-benar berpindah ke none belakangan sedikit
	(25% dari durasi collapse, ~250ms) — cukup jeda supaya opacity
	sempat selesai turun ke 0 dulu SEBELUM box dihapus dari flow,
	sehingga box yang masih flex-shrink:0 itu sudah tidak kelihatan
	(transparan) selagi dia masih sempat menempati ruang sesaat.
	Total delay jauh lebih pendek dari 1 detik penuh, jadi navbar-brand
	sudah tuntas hilang sebelum sidebar mendekati lebar akhir — tidak
	sempat kelihatan meluber ke konten kanan seperti percobaan pertama.
	calc() dipakai supaya nilainya tetap terikat ke --duration-expand
	(kalau durasi collapse berubah suatu saat, delay ini otomatis ikut
	proporsional) alih-alih angka mati.

	REVISI: 0.15/0.25 (~150ms/250ms) di atas ternyata masih terasa
	terlalu cepat dibanding icon Finance/Customer yang bertahan hampir
	1 detik penuh — brand name jadi hilang duluan sebelum yang lain.
	Dinaikkan ke 0.45/0.55 (~450ms/550ms): opacity mulai turun di
	pertengahan durasi collapse (bukan di awal), display berpindah ke
	none tak lama setelah opacity tuntas 0. Ini masih menyisakan jeda
	~450ms sebelum sidebar mencapai lebar akhir (1s), cukup untuk
	menghindari box flex-shrink:0 sempat terlihat meluber solid ke
	konten kanan seperti pada percobaan durasi penuh sebelumnya —
	tapi terasa jauh lebih "lambat"/natural dibanding 0.15/0.25. */
	.sidebar.is-collapsed .sidebar-header .navbar-brand {
		transition-property: opacity, display;
		transition-duration: calc(var(--duration-expand) * 0.45), calc(var(--duration-expand) * 0.55);
		transition-timing-function: var(--ease-default);
		transition-behavior: allow-discrete;
		opacity: 0;
	}

	/* 5. Padding icon collapsed-link, padding avatar sidebar-user, dan
	padding-left sidebar-item-collapsed-link (dari rule 1a & FIX #2 di
	atas) — durasinya juga disamakan supaya "pergeseran ke tengah rel
	60px" selesai berbarengan dengan sidebar selesai mengecil. */
	.sidebar-item-collapsed-link,
	.sidebar-user {
		transition-duration: var(--duration-expand) !important;
		transition-timing-function: var(--ease-default) !important;
	}


	/***********/
	.breadcrumb{
		margin-top		: -1.1rem;
		margin-bottom	: 0.8rem;
	}
	.breadcrumb-icon{
		margin-top		: 5px;
	}
	/***********/


	/* data table*/

	.data-table-zebra .data-table-row + .data-table-row td {
		border-top: none;
	}

	.data-table td.data-table-td{
		padding: var(--size-200) var(--size-400);
	}
	/* data table */

	/* === FILE: upload-preview.css — MULAI === */
	/* ==========================================================================
	COMPONENTS / upload-preview / upload-preview.css
	--------------------------------------------------------------------------
	Styling untuk drop-zone upload logo (_form_logo.php) dan seluruh markup
	yang di-GENERATE oleh JS Upload.init() / Upload.sendWithProgress() di
	app-core.js. Class di bawah ini TIDAK BOLEH diganti nama — app-core.js
	membangun HTML-nya lewat string literal, bukan lewat class yang bisa
	dikonfigurasi.

	DEPENDS ON: tokens/_colors.css, tokens/_semantic.css, tokens/_spacing.css,
	tokens/_elevation.css, tokens/_typography.css, tokens/_motion.css

	Dipakai bersama: _form_logo.php, app-core.js (Upload object)
	========================================================================== */

	/* --------------------------------------------------------------------------
	DIALOG BODY LAYOUT — pengganti .form-body/.form-footer lama, cocok
	dengan .dialogBody milik shell dialog-form.css (padding sudah ditangani
	di sana, jadi di sini cukup gap antar field + footer sticky-bottom feel)
	-------------------------------------------------------------------------- */
	.dialogBody-inner {
		display:        flex;
		flex-direction: column;
		gap:            var(--size-600);
	}


	/* --------------------------------------------------------------------------
	LOGO SEKARANG
	-------------------------------------------------------------------------- */
	.current-logo-wrap {
	display:        flex;
	flex-direction: column;
	align-items:    center;
	gap:            var(--size-200);
	}

	.current-logo-img {
	width:            100px;
	height:           100px;
	object-fit:       contain;
	border-radius:    var(--radius-lg);
	border:           1px solid var(--color-border-default);
	background-color: var(--color-bg-sunken);
	padding:          var(--size-200);
	}

	.current-logo-name {
	font-size: var(--text-caption-size);
	color:     var(--color-text-secondary);
	margin:    0;
	}

	.label-hint {
	font-size:  var(--text-caption-size);
	color:      var(--color-text-secondary);
	margin:     0 0 var(--size-200);
	}

	/* --------------------------------------------------------------------------
	DROP ZONE — id #logoDropZone, class .highlight di-toggle oleh Upload.init()
	saat dragenter/dragover (lihat app-core.js baris ~790)
	-------------------------------------------------------------------------- */
	.upload-zone {
	position:       relative; /* jadi anchor untuk .upload-progress-overlay */
	border:         2px dashed var(--color-border-strong);
	border-radius:  var(--radius-xl);
	padding:        var(--size-1000);
	margin-top: 	-10px;
	text-align:     center;
	cursor:         pointer;
	background-color: var(--color-bg-surface);
	transition:
		border-color     var(--duration-slow) var(--ease-in),
		background-color var(--duration-slow) var(--ease-in);
	}

	.upload-zone:hover,
	.upload-zone.highlight {
	border-color:     var(--color-brand);
	background-color: var(--color-brand-subtle);
	}

	.upload-zone-text {
	font-size: var(--text-body-sm-size);
	color:     var(--color-text-secondary);
	margin:    0;
	}

	/* --------------------------------------------------------------------------
	PREVIEW LIST — markup di-generate Upload.render() di app-core.js
	(class: upload-preview-item, preview-thumb[-doc], preview-img,
	preview-doc-ext, preview-info, preview-name, preview-size, preview-remove)
	-------------------------------------------------------------------------- */
	.upload-preview-item {
	display:       flex;
	align-items:   center;
	gap:           var(--size-300);
	padding:       var(--size-300) var(--size-400);
	margin-top:    var(--size-300);
	border:        1px solid var(--color-border-default);
	border-radius: var(--radius-lg);
	background:    var(--color-bg-surface);
	}

	.preview-thumb {
	flex-shrink:      0;
	width:            56px;
	height:           56px;
	border-radius:    var(--radius-md);
	overflow:         hidden;
	background:       var(--color-bg-sunken);
	border:           1px solid var(--color-border-default);
	}

	.preview-img {
	width:      100%;
	height:     100%;
	object-fit: cover;
	display:    block;
	}

	.preview-thumb-doc {
	display:         flex;
	align-items:     center;
	justify-content: center;
	}

	.preview-doc-ext {
	font-size:      var(--text-caption-size);
	font-weight:    var(--font-weight-bold);
	color:          var(--color-brand);
	letter-spacing: 0.05em;
	}

	.preview-info {
	flex:      1;
	display:   flex;
	flex-direction: column;
	gap:       2px;
	min-width: 0; /* wajib supaya ellipsis nama file bekerja */
	}

	.preview-name {
	font-size:     var(--text-body-sm-size);
	font-weight:   var(--font-weight-medium);
	color:         var(--color-text-primary);
	overflow:      hidden;
	text-overflow: ellipsis;
	white-space:   nowrap;
	margin:        0;
	}

	.preview-size {
	font-size: var(--text-caption-size);
	color:     var(--color-text-secondary);
	margin:    0;
	}

	.preview-remove {
	flex-shrink:     0;
	width:           28px;
	height:          28px;
	border-radius:   var(--radius-full);
	border:          none;
	background:      var(--color-bg-sunken);
	color:           var(--color-text-secondary);
	font-size:       var(--text-body-md-size, 1rem);
	line-height:     1;
	cursor:          pointer;
	display:         flex;
	align-items:     center;
	justify-content: center;
	transition:      var(--transition-base);
	}

	.preview-remove:hover {
	background: var(--color-error-subtle);
	color:      var(--color-error);
	}

	/* --------------------------------------------------------------------------
	PROGRESS OVERLAY — di-append ke #logoDropZone oleh
	Upload.sendWithProgress() saat submit (app-core.js baris ~810).
	Sebelumnya TIDAK punya style sama sekali (tampil polos tanpa CSS).
	-------------------------------------------------------------------------- */
	.upload-progress-overlay {
	position:         absolute;
	inset:            0;
	display:          flex;
	flex-direction:   column;
	align-items:      center;
	justify-content:  center;
	gap:              var(--size-200);
	background-color: color-mix(in srgb, var(--color-bg-surface) 90%, transparent);
	border-radius:    var(--radius-xl);
	}

	.upload-progress-ring {
	transform: rotate(0deg);
	}

	.upload-progress-track {
	stroke: var(--color-border-default);
	}

	.upload-progress-fill {
	stroke: var(--color-brand);
	}

	.upload-progress-text {
	font-size:   var(--text-caption-size);
	font-weight: var(--font-weight-semibold);
	color:       var(--color-text-primary);
	}
	/**********************/
	/* === FILE: upload-preview.css — SELESAI === */

	/* === FILE: customer-detail.css — MULAI === */
	/* ==========================================================================
	COMPONENTS / customer-detail / customer-detail.css
	--------------------------------------------------------------------------
	Styling khusus halaman detail.php: hero logo (clickable, ganti logo),
	detail value di dalam .card, dan extension 3-slot untuk .accordion-summary
	(icon+judul+desc di kiri, count+action+arrow di kanan — .accordion-summary
	bawaan cuma 2 slot kiri-kanan).

	DEPENDS ON: molecules/card/card.css, molecules/accordion/accordion.css,
	atoms/button/button.css, tokens/*

	id logoWrapper, customerLogo, contactPersonTambah, contactPersonList,
	totalCustomerContact, data-slug/data-base-url/data-path TIDAK BOLEH
	diganti — dipakai langsung oleh customer.js.
	========================================================================== */

	/* --------------------------------------------------------------------------
	HERO CARD — logo + nama perusahaan
	-------------------------------------------------------------------------- */
	.customer-hero-body {
	display:        flex;
	flex-direction: column;
	align-items:    center;
	justify-content: center;
	gap:            var(--size-300);
	text-align:     center;
	}

	.logo-changeable {
	position:      relative;
	cursor:        pointer;
	width:         96px;
	height:        96px;
	border-radius: var(--radius-lg);
	overflow:      hidden;
	flex-shrink:   0;
	}

	.customer-logo-img {
	width:      100%;
	height:     100%;
	object-fit: contain;
	display:    block;
	background-color: var(--color-bg-sunken);
	}

	/* Overlay "Ganti Logo" — tersembunyi default, muncul saat hover/focus */
	.logo-overlay {
	position:        absolute;
	inset:           0;
	background-color: var(--color-bg-overlay);
	display:         flex;
	align-items:     center;
	justify-content: center;
	opacity:         0;
	transition:      opacity var(--duration-fast) var(--ease-default);
	border-radius:   inherit;
	}

	.logo-changeable:hover .logo-overlay,
	.logo-changeable:focus-visible .logo-overlay {
	opacity: 1;
	}

	.logo-overlay .label-optional {
	color: var(--color-text-inverse, #fff);
	}

	.customer-hero-name {
	font-size:   var(--text-heading-xs-size);
	font-weight: var(--text-heading-xs-weight);
	color:       var(--color-text-primary);
	margin:      0;
	}

	/* --------------------------------------------------------------------------
	DETAIL VALUE CARDS — alamat, NPWP, phone, website
	-------------------------------------------------------------------------- */
	.detail-value {
	font-size: var(--text-body-sm-size);
	color:     var(--color-text-primary);
	margin:    var(--size-200) 0 0;
	line-height: var(--leading-normal);
	}

	/* --------------------------------------------------------------------------
	ACCORDION SUMMARY — extension 3-slot untuk detail.php.
	.accordion-summary bawaan sudah flex space-between 2-slot (kiri/kanan);
	di sini kita override jadi 2 blok besar (header kiri, info kanan), lalu
	.accordion-summary-info sendiri jadi flex-row untuk count+button+arrow.
	-------------------------------------------------------------------------- */
	.accordion-group{
		padding-block: var(--size-500);
		gap: var(--size-500);
	}
	.accordion-container{
		border-radius: var(--radius-xl);
	}
	.accordion-summary-detail {
	align-items: center;
	padding:     var(--size-400) var(--size-500);
	}

	.accordion-title-block {
	display: flex;
	flex-direction: column;
	gap: 2px;
	}

	.accordion-title {
	font-size:   var(--text-heading-xs-size);
	font-weight: var(--text-heading-xs-weight);
	color:       var(--color-text-primary);
	margin:      0;
	}

	.accordion-desc {
	font-size: var(--text-caption-size);
	color:     var(--color-text-secondary);
	margin:    0;
	}

	.accordion-summary-info {
	display:     flex;
	align-items: center;
	gap:         var(--size-400);
	flex-shrink: 0;
	}

	.accordion-count {
	font-size: var(--text-body-sm-size);
	color:     var(--color-text-secondary);
	margin:    0;
	white-space: nowrap;
	}

	/* Arrow bawaan .accordion-arrow sudah handle rotasi via [open] — tidak perlu
	class arrow-down-rotate lagi, cukup pastikan urutan elemen benar di HTML */

	/* --------------------------------------------------------------------------
	CONTACT PERSON GRID — isi #contactPersonList (di-generate customer.js)
	-------------------------------------------------------------------------- */
	.contact-person-grid {
	display:               grid;
	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
	gap:                   var(--size-400);
	padding:               var(--size-500); /* .accordion-content base sudah size-400, disini sedikit dilebarkan */
	}

	/* --------------------------------------------------------------------------
	PROJECT GRID — placeholder card demo
	-------------------------------------------------------------------------- */
	.project-grid {
	display:               grid;
	grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
	gap:                   var(--size-400);
	padding:               var(--size-500);
	}

	.grid-card {
	padding:       var(--size-400);
	border:        1px solid var(--color-border-default);
	border-radius: var(--radius-lg);
	background:    var(--color-bg-sunken);
	font-size:     var(--text-body-sm-size);
	color:         var(--color-text-secondary);
	line-height:   var(--leading-normal);
	}

	/* --------------------------------------------------------------------------
	RESPONSIVE
	-------------------------------------------------------------------------- */
	@media (max-width: 639px) {
	.accordion-summary-detail {
		flex-wrap: wrap;
	}
	.accordion-summary-info {
		width:           100%;
		justify-content: space-between;
	}
	}
	/******************************/
	/* === FILE: customer-detail.css — SELESAI === */

	/* ==========================================================================
	COMPONENTS / form-error-fieldset / form-error-fieldset.css
	--------------------------------------------------------------------------
	Class .form-error-fieldset di-toggle otomatis oleh app-core.js
	(Form.showError / LiveValidation) pada <fieldset> terdekat saat field
	radio/checkbox error — lihat app-core.js baris ~412 & ~436.

	RIWAYAT: dulu .radio-group di file ini juga diberi border/padding/
	transition sendiri (base/netral), supaya <fieldset> punya kotak
	border abu-abu sejak awal render — alasannya waktu itu design system
	BELUM punya komponen <fieldset> resmi, jadi .radio-group "meminjam"
	peran itu. Sejak components/molecules/fieldset/fieldset.css dibuat,
	<fieldset> di form ini sudah pakai class gabungan
	"fieldset radio-group ..." — .fieldset yang sekarang memegang
	border/padding/transition (--duration-slow/--ease-in, satu keluarga
	gerak dgn .input/select-modern/dst). Duplikat .radio-group DIHAPUS
	dari sini (sempat bikin transition-nya jadi --duration-fast/
	--ease-default karena menang cascade atas .fieldset — beda "rasa"
	dari input lain). .radio-group SEKARANG murni tata letak
	(flex+gap, lihat atoms/radio/radio.css) — TIDAK dipakai sendirian
	tanpa .fieldset lagi.

	.form-error-fieldset SEKARANG JUGA dipindah penuh ke fieldset.css
	(lihat blok komentar riwayat di sana) — supaya class yang di-toggle
	app-core.js dan yang mendefinisikan tampilannya berada di satu
	tempat yang sama dengan .fieldset base, bukan terpisah lagi.

	DEPENDS ON: components/molecules/fieldset/fieldset.css,
	atoms/radio/radio.css, tokens/_semantic.css
	========================================================================== */

	/* [DIHAPUS] .form-error-fieldset sekarang didefinisikan SATU-SATUNYA
	   di components/molecules/fieldset/fieldset.css:
	     border-color: var(--color-error);  (samakan .input-error, TANPA
	     background-color — background merah pudar yang sebelumnya ada
	     di sini dihapus)
	   Hover/focus-within DIKECUALIKAN dari state brand/border-focus lewat
	   :not(.form-error-fieldset) di fieldset.css, jadi tetap MERAH saat
	   di-hover/fokus, tidak balik ke warna brand — inilah alasan
	   definisi ini TIDAK BOLEH dobel di sini: kalau file ini dimuat
	   setelah design-system.min.css, warna/background versi lama di
	   sini akan menang lagi dan bug lama (balik ke brand saat hover)
	   akan muncul kembali persis seperti transition .radio-group
	   sebelumnya.

	   .form-error-fieldset .radio-group-label { color: var(--color-error-text); }
	   TETAP di fieldset.css juga (via .form-error-fieldset .fieldset-legend,
	   .form-error-fieldset .radio-group-label — dua selector supaya
	   berlaku baik untuk <legend class="fieldset-legend"> maupun
	   <legend class="radio-group-label"> yang sudah dipakai form ini). */
	/* === FILE: form-error-fieldset.css — SELESAI === */
	/* ==========================================================================
	CUSTOMER HERO — header gabungan logo + data perusahaan (VERSI FINAL)
	--------------------------------------------------------------------------
	Menggantikan <section class="grid"> lama yang terdiri dari 5 <div class="card">
	terpisah (logo, alamat, NPWP, phone, website) dengan SATU card gabungan.

	Rasional desain:
	- Logo + nama jadi identity panel di kiri (bg-sunken) — recessed sedikit
		dari card utama supaya logo (background putih) tetap kontras tanpa
		border kotak yang "mengambang" sendiri seperti layout lama.
	- Logo bulat (radius-full), center-aligned — diambil dari eksplorasi
		Alternatif B, terasa lebih personal/identitas dibanding kotak persegi.
	- Alamat/NPWP/Phone/Website jadi satu grid 2 kolom di kanan. TANPA
		divider garis — hanya whitespace (gap) yang memisahkan tiap item,
		jadi kesannya lebih ringan & "airy" dibanding versi awal yang pakai
		hairline border.
	- Icon dari sprite yang sudah ada (icon-map-pin, icon-receipt-tax,
		icon-phone, icon-world-pin) supaya bisa di-scan cepat, konsisten
		dengan pola icon+title+desc yang sudah dipakai di Accordion di bawahnya.

	Semua nilai warna/spacing/radius/shadow/font mengacu ke token —
	tidak ada hardcoded value.
	========================================================================== */

	.customer-hero {
	display: grid;
	grid-template-columns: 280px 1fr;
	background-color: var(--color-bg-surface);
	border: 1px solid var(--color-border-default);
	border-radius: var(--radius-xl);
	box-shadow: var(--shadow-sm);
	overflow: hidden;
	}

	@media (max-width: 767px) {
	.customer-hero {
		grid-template-columns: 1fr;
	}
	}

	/* --- Panel kiri: identitas (logo + nama) --- */
	.customer-hero-identity {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	gap: var(--size-500);
	padding: var(--size-800) var(--size-600);
	background-color: var(--color-bg-sunken);
	border-right: 1px solid var(--color-border-default);
	}

	@media (max-width: 767px) {
	.customer-hero-identity {
		border-right: none;
		border-bottom: 1px solid var(--color-border-default);
	}
	}

	.customer-hero-logo-wrap {
	position: relative;
	width: 112px;
	height: 112px;
	border-radius: var(--radius-full);
	background-color: var(--color-bg-surface);
	border: 1px solid var(--color-border-default);
	box-shadow: var(--shadow-xs);
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	cursor: pointer;
	}

	.customer-hero-logo-wrap img {
	width: 100%;
	height: 100%;
	object-fit: contain;
	padding: var(--size-250);
	}

	.customer-hero-logo-overlay {
	position: absolute;
	inset: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: var(--color-bg-overlay);
	opacity: 0;
	transition: opacity var(--duration-fast) var(--ease-default);
	}
	.customer-hero-logo-wrap:hover .customer-hero-logo-overlay {
	opacity: 1;
	}

	.customer-hero-logo-overlay span {
	font-size: var(--text-micro-size);
	font-weight: var(--text-label-weight);
	color: var(--color-text-inverse);
	letter-spacing: var(--tracking-wide);
	}

	.customer-hero-name-block {
	display: flex;
	flex-direction: column;
	gap: var(--size-100);
	}

	.customer-hero-name {
	font-size: var(--text-heading-sm-size);
	font-weight: var(--text-heading-sm-weight);
	line-height: var(--text-heading-sm-leading);
	color: var(--color-text-primary);
	margin: 0;
	}

	.customer-hero-badge {
	display: inline-flex;
	align-items: center;
	gap: var(--size-100);
	align-self: center;
	padding: var(--size-100) var(--size-300);
	border-radius: var(--radius-full);
	background-color: var(--color-success-subtle);
	color: var(--color-success-text);
	font-size: var(--text-micro-size);
	font-weight: var(--text-label-weight);
	}

	.customer-hero-badge .icon {
	width: 12px;
	height: 12px;
	}

	/* --- Panel kanan: data terstruktur --- */
	/* Tanpa divider garis — item dipisah murni oleh gap (row + column), jadi
	kesan visualnya lebih ringan/airy dibanding hairline border. */
	.customer-hero-data {
	display: grid;
	grid-template-columns: 1fr 1fr;
	column-gap: var(--size-800);
	row-gap: var(--size-600);
	padding: var(--size-800);
	}

	@media (max-width: 900px) {
	.customer-hero-data {
		grid-template-columns: 1fr;
		padding: var(--size-600);
		row-gap: var(--size-500);
	}
	}

	.data-item {
	display: flex;
	gap: var(--size-400);
	}

	.data-item-icon {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	border-radius: var(--radius-md);
	background-color: var(--color-brand-subtle);
	color: var(--color-brand-text);
	}
	.data-item-icon svg {
	width: var(--size-icon-md);
	height: var(--size-icon-md);
	}

	.data-item-content {
	display: flex;
	flex-direction: column;
	gap: var(--size-100);
	min-width: 0;
	}

	.data-item-label {
	font-size: var(--text-caption-size);
	color: var(--color-text-secondary);
	margin: 0;
	text-transform: uppercase;
	letter-spacing: var(--tracking-wider);
	font-weight: var(--text-label-weight);
	}

	.data-item-value {
	font-size: var(--text-body-sm-size);
	line-height: var(--leading-snug);
	color: var(--color-text-primary);
	margin: 0;
	word-break: break-word;
	}

	.data-item-value a {
	color: var(--color-text-link);
	text-decoration: none;
	}
	.data-item-value a:hover {
	color: var(--color-text-link-hover);
	text-decoration: underline;
	}
	/* === FILE: customer-detail.css — SELESAI === */

	/* === FILE: textarea-icon-floating.css — MULAI === */
	/* ==========================================================================
	COMPONENTS / textarea-icon-floating / textarea-icon-floating.css
	--------------------------------------------------------------------------
	Adopsi pola icon + floating-label dari _form.php versi 1 (textarea lama)
	ke atoms/textarea.css versi design-system. TIDAK mengedit textarea.css
	ataupun form-field-floating.css langsung — keduanya belum stabil untuk
	textarea, jadi extension dulu di sini (lihat alasan di bawah), baru
	dipindah ke design-system/ kalau sudah teruji.

	KENAPA TIDAK REUSE form-field-floating.css APA ADANYA UNTUK TEXTAREA:
	Mekanisme .form-field-floating .label mengasumsikan tinggi kontainer
	TETAP (top:50%; transform:translate(x,-50%) — persis cara .input-icon
	di-center, lihat komentar di form-field-floating.css baris 78-89).
	Itu valid untuk <input> (tinggi fixed ~44px), tapi SALAH untuk
	<textarea> (min-height 96px, resizable) — kalau dipakai apa adanya,
	label "Address" akan nempel di TENGAH VERTIKAL textarea (~48px dari
	atas), bukan di baris atas dekat teks pertama seperti pola v1 lama.

	Solusi: label & icon textarea DIPAKU ke atas (top: var(--size-300),
	bukan top:50%), meniru .icon-textarea { top: 1rem } dari v1. Baris
	float-nya (saat focus/terisi) reuse translateY yang sama dengan .label
	biasa supaya animasinya tetap terasa satu keluarga dengan input.

	HTML PATTERN:
		<div class="form-field form-field-floating">
		<div class="input-wrapper">
			<textarea class="textarea has-icon-left" placeholder=" "></textarea>
			<label class="label" for="...">Address</label>
			<span class="input-icon input-icon-left" aria-hidden="true"><svg>...</svg></span>
		</div>
		<p class="label-error form-error-message" id="error_..."></p>
		</div>
	Urutan DOM: textarea → label → icon (sama seperti pola input floating).

	DEPENDS ON: atoms/textarea/textarea.css, atoms/input/input.css (utk
	.input-icon/.input-wrapper/.has-icon-left), molecules/form-field-floating.css
	========================================================================== */

	/* Padding kiri utk ruang icon — pola sama dgn .input.has-icon-left */
	.textarea.has-icon-left {
	padding-left: calc(var(--size-300) + var(--size-icon-md) + var(--size-200));
	}

	/* --------------------------------------------------------------------------
	ICON — dipaku ke ATAS (bukan center vertikal seperti .input-icon biasa),
	supaya tetap sejajar baris pertama teks walau textarea di-resize lebih
	tinggi. Ini yang meniru .icon-textarea { top: 1rem } di v1.

	transform DIISI translateY(0) sebagai base, BUKAN `none` — supaya icon
	tetap bisa geser horizontal (translateX) saat focus/terisi (lihat rule
	di bawah), konsisten dengan .input-icon-left yang juga geser 4px ke
	kanan. Sebelumnya transform:none bikin transform TIDAK PUNYA nilai awal
	utk di-transition, jadi translateX saat focus tidak pernah terlihat
	bergerak (langsung "loncat" tanpa animasi, atau malah tertahan oleh
	transform bawaan .input-icon yang masih translateY(-50%) dari input.css
	— override top di sini tidak menghapus transform lamanya).
	-------------------------------------------------------------------------- */
	.form-field-floating .textarea ~ .input-icon-left {
	top:        var(--size-300);
	transform:  translateY(0);
	transition: transform var(--duration-slow) var(--ease-in), color var(--duration-fast) var(--ease-default);
	}

	/* Geser 4px ke kanan saat focus ATAU sudah terisi — translateY tetap 0
	(bukan -50% seperti input, karena posisi vertikal sudah dipaku via top,
	bukan via transform), pola sama dgn .form-field-floating .input-icon-left
	di form-field-floating.css (translateX var(--size-100)). */
	.form-field-floating .textarea:focus ~ .input-icon-left,
	.form-field-floating .textarea:not(:placeholder-shown) ~ .input-icon-left {
	transform: translateY(0) translateX(var(--size-100, 4px));
	}

	/* --------------------------------------------------------------------------
	LABEL — resting state: sejajar baris pertama teks (bukan center 50%
	seperti label input). top dipaku var(--size-300), HANYA transform yang
	beranimasi saat float (konsisten dgn prinsip single-transform-property
	di form-field-floating.css supaya lintasan tetap lurus, bukan melengkung).
	-------------------------------------------------------------------------- */
	.form-field-floating .textarea ~ .label {
	top:       var(--size-300);
	left:      0;
	transform: translate(var(--size-400), 0);
	}

	.form-field-floating .textarea.has-icon-left ~ .label {
	transform: translate(calc(var(--size-300) + var(--size-icon-md) + var(--size-200)), 0);
	}

	/* Float ke atas saat fokus ATAU sudah terisi — translate Y saja yang
	berubah (dari 0 ke -100% dikurangi jarak keluar box), X tetap sama
	perhitungannya dengan resting supaya tidak "loncat" horizontal. */
	.form-field-floating .textarea:focus ~ .label,
	.form-field-floating .textarea:not(:placeholder-shown) ~ .label {
	transform:   translate(var(--size-100), calc(-100% - var(--size-300)));
	font-size:   var(--text-label-sm-size);
	font-weight: var(--font-weight-regular);
	color:       var(--color-text-primary);

	}

	.form-field-floating .textarea:focus ~ .label {
	color: var(--color-brand-text);
	}

	/* --------------------------------------------------------------------------
	ICON — ganti warna saat fokus (sama filosofi dgn input, tanpa translateX
	karena icon textarea tidak ikut geser horizontal — hanya warna).
	-------------------------------------------------------------------------- */
	.form-field-floating .textarea:focus ~ .input-icon {
	color: var(--color-brand-text);
	}

	/* --------------------------------------------------------------------------
	ERROR STATE — sinkron dgn bridge .input-error-textarea yang sudah ada
	(lihat blok BRIDGE di atas). Icon jadi merah saat error+focus, label
	tetap abu (konsisten dgn perilaku input-error di form-field-floating.css).
	-------------------------------------------------------------------------- */
	.form-field-floating .input-error-textarea:focus ~ .label,
	.form-field-floating .textarea[aria-invalid="true"]:focus ~ .label {
	color: var(--color-text-disabled);
	}

	.form-field-floating .input-error-textarea:focus ~ .input-icon,
	.form-field-floating .textarea[aria-invalid="true"]:focus ~ .input-icon {
	color: var(--color-error);
	}

	.form-field-floating .input-error-textarea:hover:not(:focus):not(:disabled):not([readonly]) {
	border-color: var(--color-error);
	}
	/* === FILE: textarea-icon-floating.css — SELESAI === */

	/* === FILE: textarea-border-no-focus.css — MULAI === */
	/* ==========================================================================
	COMPONENTS / textarea-border-no-focus / textarea-border-no-focus.css
	--------------------------------------------------------------------------
	Netralkan border-color + box-shadow saat :focus pada .textarea (bawaan
	atoms/textarea.css baris 42-46 set border-color: var(--color-border-focus)
	+ box-shadow: var(--shadow-focus-ring) — sama persis pola .input).

	Permintaan: saat textarea diklik/difokus, border TIDAK berubah warna
	sama sekali (kembali ke warna default/hover biasa) — tapi hover (saat
	TIDAK fokus) tetap biru seperti sebelumnya, TIDAK disentuh di sini
	karena rule .textarea:not(:focus):hover bawaan sudah benar.

	.textarea base sudah punya transition border-color/box-shadow/
	background-color (lihat textarea.css baris 31-34) — jadi begitu
	border-color di-reset ke initial, TRANSISInya ikut jalan otomatis,
	tidak perlu transition tambahan di sini.

	TIDAK mengedit atoms/textarea.css langsung — staging dulu, pola sama
	dengan blok lain.
	========================================================================== */

	/* --------------------------------------------------------------------------
	TRANSISI HOVER — samakan durasi & easing dengan .input. Sebelumnya
	.textarea base (atoms/textarea.css) pakai border-color var(--duration-fast)
	var(--ease-default) — 80ms, kedip cepat. .input pakai border-color
	var(--duration-slow) var(--ease-in) — 300ms, terasa jauh lebih halus.
	Textarea disamakan ke durasi/easing .input di sini supaya rasa transisi
	hover-nya identik (permintaan: "animasi hover sama seperti input").
	-------------------------------------------------------------------------- */
	.textarea {
	transition:
		border-color     var(--duration-slow) var(--ease-in),
		box-shadow        var(--duration-fast) var(--ease-default),
		background-color  var(--duration-fast) var(--ease-default);
	}

	.textarea:focus {
	/* border-color: var(--color-border-default); */
	border-color: var(--color-brand);
	box-shadow:   none;
	}

	/* Textarea yang sedang hover DAN focus bersamaan (mouse tetap di atas
	sambil mengetik) — tetap pertahankan warna hover biru, bukan balik ke
	abu-abu netral, supaya kursor mouse yang masih di atas elemen tidak
	terasa "salah" warnanya. */
	.textarea:focus:hover:not(:disabled):not([readonly]) {
	/* border-color: var(--color-border-strong); */
	border-color: var(--color-brand);
	}

	/* --------------------------------------------------------------------------
	FOCUS-VISIBLE OUTLINE — base/reset.css punya rule GLOBAL
	":focus-visible { outline: 2px solid var(--color-border-focus) }"
	(warna brand/biru, lihat tokens/_semantic.css). Rule ini match SEMUA
	elemen fokus di halaman, termasuk .textarea — jadi saat textarea error
	(border merah) difokuskan via keyboard/klik, outline biru 2px dari
	reset.css tetap tampil MENUMPUK di luar border merah, membuatnya
	terlihat "campur ungu" (lihat screenshot: border textarea Address
	terlihat gradasi merah-ungu-biru, bukan merah solid).
	Dinetralkan KHUSUS untuk textarea di sini (bukan global — elemen lain
	seperti link/button tetap butuh outline itu untuk aksesibilitas
	keyboard, textarea aman dilepas karena border-color sendiri sudah
	cukup jadi indikator fokus visual).
	-------------------------------------------------------------------------- */
	.textarea:focus-visible {
	outline: none;
	}

	/* Error tetap menang — kalau textarea sedang error DAN difokuskan, border
	TETAP MERAH TANPA box-shadow/ring (permintaan: "cukup border merah
	saja"), BAIK sedang di-hover ATAU TIDAK. box-shadow:none dari
	.textarea:focus generik di atas menghapus shadow, dan
	.textarea:focus-visible { outline:none } di atas menghapus outline
	biru dari base/reset.css — hasilnya bersih satu warna merah tanpa
	ring/outline apapun. */
	.input-error-textarea:focus,
	.textarea[aria-invalid="true"]:focus {
	border-color: var(--color-error);
	box-shadow:   none;
	}

	/* PENTING — kasus hover+focus+error bersamaan (mouse masih di atas
	textarea yang error sambil diketik/difokus, persis situasi di
	screenshot bug): rule .textarea:focus:hover:not(:disabled):not(
	[readonly]) di atas (baris ~1478) punya SPESIFISITAS LEBIH TINGGI
	(0,5,0 — lima pseudo-class/class) daripada rule error polos di atas
	(0,2,0 / 0,3,0), jadi TANPA rule ini, hover selalu menang dan border
	balik jadi biru walau sedang error — itu penyebab bug di screenshot.
	Rule ini menggabungkan kondisi hover KE DALAM selector error supaya
	spesifisitasnya ikut naik setara/lebih tinggi, sehingga error selalu
	menang mutlak, hover atau tidak. */
	.input-error-textarea:focus:hover:not(:disabled):not([readonly]),
	.textarea[aria-invalid="true"]:focus:hover:not(:disabled):not([readonly]) {
	border-color: var(--color-error);
	}
	/* === FILE: textarea-border-no-focus.css — SELESAI === */

	/* === FILE: radio-border-neutral.css — MULAI === */
	/* ==========================================================================
	COMPONENTS / radio-border-neutral / radio-border-neutral.css
	--------------------------------------------------------------------------
	Ubah warna border DEFAULT (state netral, belum di-hover/checked) dari
	var(--color-border-strong) — yang di token _semantic.css di-alias ke
	warna BRAND/biru (--brand-700 / --brand-400, lihat tokens/_semantic.css
	baris 65 & 144), bukan abu-abu seperti namanya mengisyaratkan — menjadi
	var(--color-border-default) yang benar-benar abu-abu netral (--gray-200
	/ --gray-800).

	Efeknya: radio saat form baru dibuka (belum di-apa-apakan) terlihat
	abu-abu netral, biru HANYA muncul saat :hover (rule hover bawaan
	atoms/radio/radio.css sudah pakai --color-brand, TIDAK disentuh di
	sini — cukup override base state-nya saja).

	TIDAK mengedit atoms/radio/radio.css langsung (staging dulu di sini,
	pola sama dgn blok-blok lain). Kalau nanti dianggap ini memang perilaku
	yang benar utk SEMUA radio di seluruh sistem (bukan cuma form contact),
	perubahan ini layak dipindah jadi base default di radio.css itu sendiri
	— bukan modifier tambahan.
	========================================================================== */

	.radio-mark {
	border-color: var(--color-border-default);
	}
	/* === FILE: radio-border-neutral.css — SELESAI === */

	/* === FILE: radio-tick.css — MULAI === */
	/* ==========================================================================
	COMPONENTS / radio-tick / radio-tick.css
	--------------------------------------------------------------------------
	Modifikasi visual .radio-mark::after: dari dot-fill bulat (bawaan
	atoms/radio/radio.css) jadi tick/centang miring — meniru efek checked
	pada .radiomark di _form.php v1 lama (05-form.css, @keyframes tick),
	TAPI bentuk luar TETAP lingkaran penuh (radius-full, sudah bawaan
	.radio-mark, tidak diubah) — sesuai permintaan: efek tick checkbox v1,
	dibungkus bentuk radio (bulat), bukan kotak.

	TIDAK mengedit atoms/radio/radio.css langsung — staging dulu di sini,
	pola sama seperti textarea-icon-floating.css & customer-detail.css
	sebelumnya. .radio-mark dasar (ukuran, border, warna checked, hover,
	focus) TIDAK disentuh — hanya ::after yang di-override.

	BEDA DARI v1:
	- v1 pakai @keyframes tick terpisah (scale 0→1.4→100%, 180ms ease-in)
		buat efek "overshoot". Di sini CUKUP ganti target transform akhir
		(dot-scale jadi tick-rotate) dan REUSE transition var(--ease-spring)
		yang sudah ada di atoms/radio/radio.css bawaan — cubic-bezier(0.34,
		1.56, 0.64, 1) itu sendiri SUDAH overshoot (nilai kedua > 1), jadi
		hasilnya setara tanpa perlu keyframe terpisah.
	- Warna tick putih di atas .radio-mark yang jadi brand-color saat
		checked — sama logika dengan v1 (background lingkaran jadi warna
		brand, tick kontras putih di atasnya), TIDAK perlu diubah karena
		.radio-mark sudah begitu bawaan (background-color: var(--color-brand)
		saat checked).

	DEPENDS ON: atoms/radio/radio.css
	========================================================================== */

	.radio-mark::after {
	/* override bentuk dot bulat jadi tick miring — ukuran & posisi
		disesuaikan supaya center di lingkaran 18px (radio-mark) */
	width:         5px;
	height:        9px;
	background:    transparent;
	border-radius: 0;
	border:        2px solid white;
	border-width:  0 2px 2px 0;
	transform:     rotate(45deg) translateY(-1px) scale(0);
	opacity:       0;
	transition:
		opacity   var(--duration-fast) var(--ease-default),
		transform var(--duration-fast) var(--ease-spring);
	}

	/* CHECKED — tick muncul (opacity+scale), DAN .radio-mark sendiri ikut
	"pop" membesar-mengecil via @keyframes radio-pop (lihat di bawah).
	Ini yang kemarin belum ada — sebelumnya cuma tick::after yang
	bertransisi, .radio-mark-nya sendiri diam saja. Sekarang disamakan
	persis dengan pola checkbox-pop di checkbox.css. */
	.radio-input:checked ~ .radio-mark {
	animation: radio-pop var(--duration-normal) var(--ease-in-out);
	}
	.radio-input:checked ~ .radio-mark::after {
	opacity:   1;
	transform: rotate(45deg) translateY(-1px) scale(1);
	}

	/* POP ANIMATION — membesar 0 → 1.4 → 1 saat radio di-check, PERSIS pola
	@keyframes checkbox-pop di checkbox.css (lihat komentar lengkap di
	sana). animation dipasang langsung di rule :checked ~ .radio-mark di
	atas, otomatis jalan begitu :checked mulai match — tidak perlu JS.
	Trade-off sama dengan checkbox: radio yang sudah checked saat render
	(mis. atribut `checked` dari PHP) ikut animasi ini saat page load. */
	@keyframes radio-pop {
	0%   { transform: scale(0); }
	90%  { transform: scale(1.4); }
	100% { transform: scale(1); }
	}

	@media (prefers-reduced-motion: reduce) {
	.radio-input:checked ~ .radio-mark {
		animation: none;
	}
	}
	/* === FILE: radio-tick.css — SELESAI === */
/* ==========================================================================
   CONTACT PERSON CARD  (v2 — refresh visual)
   --------------------------------------------------------------------------
   Menggantikan card lama yang pakai utility class legacy (bgc-grey-blue-cool,
   pad-500, fs-300, fc-grey50, dst — class ini tidak ada di design system
   baru, sisa dari sistem lama).

   Rasional desain v2:
   - Avatar inisial pakai gradient tipis brand (brand-subtle → transparent)
     dengan border brand redup, bukan flat-fill — kesan lebih "hidup" tanpa
     melanggar aturan no-flat-gradient-di-komponen-bisnis (ini masih dalam
     batas wajar karena sangat subtle, 2 stop brand hue yang sama).
   - Accent bar 3px di tepi atas card, brand → brand-hover, muncul di hover/
     focus-within. Menjadi penanda "kartu ini sedang aktif" tanpa mengubah
     ukuran/posisi konten lain (posisinya absolute, tidak ikut layout).
   - Card hover: naik tipis (translateY -2px) + shadow-sm → shadow-md,
     dipadukan dengan border yang berubah ke --color-border-strong. Pola ini
     konsisten dengan .card-clickable di card.css supaya "bahasa hover" sama
     di seluruh design system.
   - Action button (edit/delete) tetap muncul hanya saat hover/focus-within,
     tapi sekarang juga sedikit scale-in (bukan cuma opacity) supaya terasa
     lebih "muncul" bukan cuma "menyala".
   - Delete button dapat hover state warna --color-error supaya destructive
     action punya sinyal visual jelas sebelum diklik.
   - Detail kontak (phone, email) pakai pola icon+text yang sama dengan
     komponen data-item di hero header, jadi konsisten visual di seluruh
     halaman detail customer. Icon sekarang duduk di dalam chip bulat kecil
     (bg-sunken) alih-alih icon polos, senada dengan avatar di atasnya.
   - Empty state (.contact-card-empty) ditambahkan supaya "Belum ada Contact
     Person" tidak polos teks — dibungkus dashed-border placeholder card.

   Semua nilai mengacu ke token — tidak ada hardcoded value warna/spacing.
   ========================================================================== */

/* --- Grid container (dipakai di #contactPersonList) --- */
.contact-person-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--size-400);
}

.contact-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--size-400);
  padding: var(--size-500);
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border-default);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
  transition:
    box-shadow   var(--duration-normal) var(--ease-out),
    border-color var(--duration-sluggish)   var(--ease-default),
    transform    var(--duration-normal) var(--ease-out);
}

/* Accent bar atas — absolute, tidak mengambil ruang layout */
.contact-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-brand), var(--color-brand-hover));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--duration-expand) var(--ease-out);
}

.contact-card:hover,
.contact-card:focus-within {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.contact-card:hover::before,
.contact-card:focus-within::before {
  transform: scaleX(1);
}

/* --- Header: avatar + nama + jabatan + actions --- */
.contact-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--size-300);
}

.contact-card-identity {
  display: flex;
  align-items: center;
  gap: var(--size-300);
  min-width: 0;
}

.contact-card-avatar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size-1000);
  height: var(--size-1000);
  border-radius: var(--radius-full);
  background: linear-gradient(135deg, var(--color-brand-subtle), transparent);
  border: 1px solid var(--color-brand-subtle);
  color: var(--color-brand-text);
  font-size: var(--text-label-size);
  font-weight: var(--text-heading-xs-weight);
  transition: border-color var(--duration-sluggish) var(--ease-default);
}

.contact-card:hover .contact-card-avatar,
.contact-card:focus-within .contact-card-avatar {
  border-color: var(--color-brand);
}

/* --------------------------------------------------------------------
   Warna avatar deterministik (avatar-color-0 s/d avatar-color-7) —
   kelas yang sama dipasang oleh Tools.avatarColorClass()/
   Helpers::avatarColorClass() di tempat lain (navbar, tabel user,
   detail user) supaya kontak yang sama selalu tampil warna yang sama
   di manapun. .contact-card-avatar TIDAK pakai struktur .avatar/
   .avatar-initials dari avatar.css (dia elemen mandiri dengan
   gradient+border sendiri), jadi warnanya didefinisikan ulang di
   sini — hex SAMA dengan light-mode avatar.css supaya identik secara
   visual, hanya beda cara pakainya (linear-gradient, bukan flat).

   State hover TIDAK diubah cara kerjanya (transition timing & trigger
   .contact-card:hover tetap sama) — hanya NILAI border-color saat
   hover yang sekarang ikut warna avatar masing-masing kartu, bukan
   lagi selalu var(--color-brand) generik untuk semua kartu.
   -------------------------------------------------------------------- */
.contact-card-avatar.avatar-color-0 {
  background: linear-gradient(135deg, var(--color-brand-subtle), transparent);
  border-color: var(--color-brand-subtle);
  color: var(--color-brand-text);
}
.contact-card-avatar.avatar-color-1 {
  background: linear-gradient(135deg, #fde8d7, transparent);
  border-color: #fde8d7;
  color: #9a4a12;
}
.contact-card-avatar.avatar-color-2 {
  background: linear-gradient(135deg, #fdeacb, transparent);
  border-color: #fdeacb;
  color: #92600a;
}
.contact-card-avatar.avatar-color-3 {
  background: linear-gradient(135deg, #dcf2e3, transparent);
  border-color: #dcf2e3;
  color: #1c6b3f;
}
.contact-card-avatar.avatar-color-4 {
  background: linear-gradient(135deg, #d8f0ee, transparent);
  border-color: #d8f0ee;
  color: #0e6b64;
}
.contact-card-avatar.avatar-color-5 {
  background: linear-gradient(135deg, #e0e8fb, transparent);
  border-color: #e0e8fb;
  color: #2c4ba8;
}
.contact-card-avatar.avatar-color-6 {
  background: linear-gradient(135deg, #ece0fb, transparent);
  border-color: #ece0fb;
  color: #5b2ba8;
}
.contact-card-avatar.avatar-color-7 {
  background: linear-gradient(135deg, #fbe0ef, transparent);
  border-color: #fbe0ef;
  color: #a8266f;
}

[data-theme="dark"] .contact-card-avatar.avatar-color-0 {
  background: linear-gradient(135deg, var(--color-brand-subtle), transparent);
  border-color: var(--color-brand-subtle);
  color: var(--color-brand-text);
}
[data-theme="dark"] .contact-card-avatar.avatar-color-1 {
  background: linear-gradient(135deg, #4a2c14, transparent);
  border-color: #4a2c14;
  color: #f7c191;
}
[data-theme="dark"] .contact-card-avatar.avatar-color-2 {
  background: linear-gradient(135deg, #47350d, transparent);
  border-color: #47350d;
  color: #f3d27f;
}
[data-theme="dark"] .contact-card-avatar.avatar-color-3 {
  background: linear-gradient(135deg, #163d26, transparent);
  border-color: #163d26;
  color: #8fdcae;
}
[data-theme="dark"] .contact-card-avatar.avatar-color-4 {
  background: linear-gradient(135deg, #123c38, transparent);
  border-color: #123c38;
  color: #7fd9d0;
}
[data-theme="dark"] .contact-card-avatar.avatar-color-5 {
  background: linear-gradient(135deg, #1c2a54, transparent);
  border-color: #1c2a54;
  color: #a9bdf5;
}
[data-theme="dark"] .contact-card-avatar.avatar-color-6 {
  background: linear-gradient(135deg, #2c1a54, transparent);
  border-color: #2c1a54;
  color: #c6a9f5;
}
[data-theme="dark"] .contact-card-avatar.avatar-color-7 {
  background: linear-gradient(135deg, #4a1836, transparent);
  border-color: #4a1836;
  color: #f2a9d3;
}

/* Hover/focus — border avatar mengikuti warna avatar-nya sendiri
   (bukan lagi var(--color-brand) generik). avatar-color-0 dikecualikan
   karena sudah brand-subtle sejak state normal, jadi tetap pakai
   --color-brand penuh saat hover seperti perilaku ASLI sebelum
   perubahan ini (supaya avatar tanpa warna khusus / bucket 0 tidak
   berubah rasanya). */
.contact-card:hover .contact-card-avatar.avatar-color-0,
.contact-card:focus-within .contact-card-avatar.avatar-color-0 {
  border-color: var(--color-brand);
}
.contact-card:hover .contact-card-avatar.avatar-color-1,
.contact-card:focus-within .contact-card-avatar.avatar-color-1 { border-color: #9a4a12; }
.contact-card:hover .contact-card-avatar.avatar-color-2,
.contact-card:focus-within .contact-card-avatar.avatar-color-2 { border-color: #92600a; }
.contact-card:hover .contact-card-avatar.avatar-color-3,
.contact-card:focus-within .contact-card-avatar.avatar-color-3 { border-color: #1c6b3f; }
.contact-card:hover .contact-card-avatar.avatar-color-4,
.contact-card:focus-within .contact-card-avatar.avatar-color-4 { border-color: #0e6b64; }
.contact-card:hover .contact-card-avatar.avatar-color-5,
.contact-card:focus-within .contact-card-avatar.avatar-color-5 { border-color: #2c4ba8; }
.contact-card:hover .contact-card-avatar.avatar-color-6,
.contact-card:focus-within .contact-card-avatar.avatar-color-6 { border-color: #5b2ba8; }
.contact-card:hover .contact-card-avatar.avatar-color-7,
.contact-card:focus-within .contact-card-avatar.avatar-color-7 { border-color: #a8266f; }

[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-1,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-1 { border-color: #f7c191; }
[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-2,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-2 { border-color: #f3d27f; }
[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-3,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-3 { border-color: #8fdcae; }
[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-4,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-4 { border-color: #7fd9d0; }
[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-5,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-5 { border-color: #a9bdf5; }
[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-6,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-6 { border-color: #c6a9f5; }
[data-theme="dark"] .contact-card:hover .contact-card-avatar.avatar-color-7,
[data-theme="dark"] .contact-card:focus-within .contact-card-avatar.avatar-color-7 { border-color: #f2a9d3; }

.contact-card-name-block {
  display: flex;
  flex-direction: column;
  gap: var(--size-50);
  min-width: 0;
}

.contact-card-name {
  font-size: var(--text-label-size);
  font-weight: var(--text-heading-xs-weight);
  line-height: var(--leading-snug);
  color: var(--color-text-primary);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.contact-card-role {
  font-size: var(--text-caption-size);
  line-height: var(--leading-snug);
  color: var(--color-text-secondary);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Actions: edit / delete --- */
.contact-card-actions {
  display: flex;
  align-items: center;
  gap: var(--size-100);
  flex-shrink: 0;
  opacity: 0;
  transform: translateX(4px);
  transition:
    opacity   var(--duration-sluggish) var(--ease-default),
    transform var(--duration-sluggish) var(--ease-out);
}

.contact-card:hover .contact-card-actions,
.contact-card:focus-within .contact-card-actions {
  opacity: 1;
  transform: translateX(0);
}

.contact-card-action-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size-700);
  height: var(--size-700);
  border-radius: var(--radius-md);
  color: var(--color-text-secondary);
  cursor: pointer;
  transition:
    background-color var(--duration-sluggish) var(--ease-default),
    color            var(--duration-sluggish) var(--ease-default),
    transform        var(--duration-sluggish) var(--ease-out);
}

.contact-card-action-btn svg {
  width: var(--size-icon-sm);
  height: var(--size-icon-sm);
}

.contact-card-action-btn:hover {
  background-color: var(--color-bg-sunken);
  color: var(--color-text-primary);
  transform: translateY(-1px);
}

.contact-card-action-btn:active {
  transform: translateY(0);
}

.contact-card-action-btn.is-destructive:hover {
  background-color: var(--color-error-subtle);
  color: var(--color-error-text);
}

/* --- Detail: phone, email --- */
.contact-card-details {
  display: flex;
  flex-direction: column;
  gap: var(--size-300);
  padding-top: var(--size-400);
  border-top: 1px solid var(--color-border-default);
}

.contact-card-detail-row {
  display: flex;
  align-items: center;
  gap: var(--size-300);
  min-width: 0;
}

.contact-card-detail-row .icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size-700);
  height: var(--size-700);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-sunken);
  color: var(--color-text-secondary);
}

.contact-card-detail-row .icon svg {
  width: var(--size-icon-sm);
  height: var(--size-icon-sm);
}

.contact-card-detail-text {
  font-size: var(--text-caption-size);
  line-height: var(--leading-snug);
  color: var(--color-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- Empty state --- */
.contact-card-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--size-300);
  padding: var(--size-800) var(--size-500);
  grid-column: 1 / -1;
  border: 1.5px dashed var(--color-border-default);
  border-radius: var(--radius-lg);
  color: var(--color-text-secondary);
  text-align: center;
}

.contact-card-empty .icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size-1000);
  height: var(--size-1000);
  border-radius: var(--radius-full);
  background-color: var(--color-bg-sunken);
  color: var(--color-text-disabled);
}

.contact-card-empty .icon svg {
  width: var(--size-icon-lg);
  height: var(--size-icon-lg);
}

.contact-card-empty-text {
  font-size: var(--text-body-sm-size);
  color: var(--color-text-secondary);
  margin: 0;
}

/* --- Loading state (dipakai saat refreshContactList sedang fetch) --- */
.contact-card-skeleton {
  height: 172px;
  border-radius: var(--radius-lg);
  background: linear-gradient(
    100deg,
    var(--color-bg-sunken) 30%,
    var(--color-bg-surface) 50%,
    var(--color-bg-sunken) 70%
  );
  background-size: 200% 100%;
  animation: ds-shimmer var(--duration-sluggish) ease-in-out infinite;
  border: 1px solid var(--color-border-default);
}

@media (prefers-reduced-motion: reduce) {
  .contact-card,
  .contact-card::before,
  .contact-card-avatar,
  .contact-card-actions,
  .contact-card-action-btn {
    transition: none;
  }
  .contact-card-skeleton {
    animation: none;
  }
}
/* ============================================================
   quotation-form.css
   Layout khusus section "Item Quotation" di _form.php — tabel
   horizontal (CSS Grid) supaya kolom antar baris & header sejajar
   presisi, dan beberapa item mudah dibandingkan sekilas.

   REVISI: sebelumnya sempat ada draft CSS terpisah (file baru,
   custom-quotation-form.css) yang menduplikasi blok ini dengan
   pendekatan sedikit beda — draft itu DIBUANG, semua perubahan
   digabung ke sini supaya tetap satu sumber kebenaran untuk
   .quotation-items-*. Perubahan dari revisi ini:
     1. Semua fallback hex hardcode (#e5e7eb, #dc2626, dst) diganti
        token semantik murni (--color-*) — supaya dark theme ikut
        otomatis benar tanpa override tema terpisah.
     2. Lebar dialog quotation di-set eksplisit (800px, scoped lewat
        #dialogForm[data-dialog="quotation"]) — lihat bagian LEBAR
        DIALOG di bawah, plus catatan di paling akhir file ini soal
        1 baris kecil yang perlu ditempel di quotation.js.
     3. .cb__input (kotak cari combobox customer) dilepas focus
        ring-nya (box-shadow saat fokus normal), border tetap
        berubah warna sebagai affordance. State error TETAP pakai
        ring (aksesibilitas: error harus tetap paling mencolok).

   Fix teknis yang SUDAH ADA sebelumnya dan TETAP dipertahankan
   apa adanya di revisi ini (jangan dihapus lagi ke depannya):
     - Fix spesifisitas .input-error (harus 3-class selector,
       lihat catatan di bagian GRID ITEM di bawah)
     - Sembunyikan pesan error bawaan combobox.js (dobel dengan
       #error_id_customer dari server)

   UPDATE (2026-09-03) — 2 item di atas SUDAH DIHAPUS, JANGAN
   ditambahkan lagi:
     - "Simetri baris Customer + Expires" (padding-top:26px spacer)
       — dulu menyeimbangkan label BLOCK combobox (.cb__label) yang
       sekarang tidak pernah dirender lagi (quotation.js berhenti
       set opts.label). Kedua kolom sekarang simetri SECARA ALAMI
       lewat <label class="label label-sm"> yang sama di _form.php,
       tanpa spacer buatan. Menambahkan spacer lagi akan MERUSAK
       simetri ini, bukan memperbaikinya.
     - Override ".cb__label supaya sama gaya dengan .label" — dead
       code dengan alasan yang sama: .cb__label tidak pernah ada
       di DOM combobox ini lagi.

   Ditaruh terpisah dari design-system utama (bukan menimpa
   .form-field/.input global) — cukup include file ini di halaman
   yang memuat modal quotation.
   ============================================================ */


/* ── LEBAR DIALOG — quotation butuh lebih lega dari .dialog-form-lg
   bawaan (720px) supaya kolom Unit Price tidak sempit, tapi TIDAK
   sampai .dialog-form-xl (960px) — untuk 5 kolom + 2 field header,
   960px menyisakan whitespace mubazir di kiri-kanan.

   Discoped lewat attribute data-dialog, BUKAN override
   .dialog-form-lg secara global (form lain yang pakai size:'lg'
   jangan ikut melebar). Attribute ini perlu ditempel manual dari
   quotation.js — lihat catatan implementasi di paling akhir file
   ini kalau belum ditempel. Kalau belum sempat diubah, dialog
   quotation tetap jalan normal di lebar 'lg' bawaan (720px), cuma
   rule di bawah ini idle (tidak match apa pun). ────────────────── */
#dialogForm[data-dialog="quotation"] {
	max-width: 800px;
}

@media (max-width: 639px) {
	#dialogForm[data-dialog="quotation"] {
		max-width: var(--dialog-width-full, 100vw);
	}
}


/* ── Simetri baris Customer + Expires ────────────────────────────
   FIX (2026-09-03): blok ini SEBELUMNYA menambahkan padding-top:26px
   ke field Expires sebagai "spacer" penyeimbang, dengan asumsi
   combobox Customer merender label BLOCK sendiri (.cb__label,
   uppercase, di ATAS field) yang menambah tinggi total combobox
   dibanding field date biasa.

   Asumsi itu SUDAH TIDAK BERLAKU sejak quotation.js berhenti set
   opts.label (lihat QuotationCustomerCombobox.init() di quotation.js,
   dan blok pembersihan .cb__label dead code di bawah file ini):
   combobox.js SEKARANG TIDAK PERNAH merender .cb__label untuk field
   ini. Label "Customer" ditulis manual di _form.php sebagai
   <label class="label label-sm">, PERSIS struktur yang sama dengan
   <label class="label">Expires</label> di sebelahnya — dua kolom
   sekarang SUDAH otomatis simetri tanpa spacer buatan apa pun.

   padding-top:26px yang dulu jadi "penyeimbang" itu sekarang justru
   MERUSAK — menciptakan jarak kosong palsu di atas kotak input
   Expires (lihat screenshot: kotak "10/09/2026" turun sendiri,
   terpisah dari label "Expires" di atasnya) karena sekarang
   menyeimbangkan sesuatu yang sudah tidak ada. Dihapus. */
#formQuotation .form-group-row {
	align-items: flex-start;
}

/* Combobox wrapper butuh flex:1 yang sama seperti .form-field lain
   di form-group-row — sebelumnya cuma .form-field langsung yang kena
   aturan itu, sedangkan .cb-auto ada 1 level lebih dalam. */
#formQuotation .form-group-row > .form-field {
	flex: 1;
	min-width: 0;
}

#formQuotation .cb-auto {
	width: 100%;
}

/* Fix font-size label "Customer" vs "Expires" TERNYATA SUDAH ADA lebih
   dulu di combobox.css (blok "Alignment: label Customer vs Expires",
   paling akhir file, class .label-sm) — baru ketahuan saat menelusuri
   root cause pesan error tidak sejajar (2026-09-03). Rule saya di sini
   DUPLIKAT persis (sama-sama set font-size ke --text-label-sm-size)
   dan dihapus supaya cuma ada SATU sumber kebenaran. Lihat combobox.css
   untuk penjelasan lengkap & rule .label-sm serta .form-field:has(.cb)
   yang menanganinya (termasuk posisi absolute label & fix margin-top
   pesan error yang jadi tidak sejajar akibat gap:0 di rule yang sama). */

.quotation-items-section {
	padding: var(--size-400);
	border: 1px solid var(--color-border-default);
	border-radius: var(--radius-lg);
	background-color: var(--color-bg-sunken);
}

.quotation-items-header {
	display: flex;
	justify-content: space-between;
	align-items: flex-start;
	gap: var(--size-300);
	margin-bottom: var(--size-350, 14px);
}

.quotation-items-header h4 {
	margin: 0 0 2px 0;
	font-size: var(--text-body-sm-size, 14px);
	font-weight: var(--font-weight-semibold);
	color: var(--color-text-primary);
}

.quotation-items-subtitle {
	margin: 0;
	color: var(--color-text-secondary);
}

/* Grid kolom disesuaikan dgn field yang aktif di form saat ini
   (sku & unit sudah di-comment/dihapus dari _form.php): Product
   diberi ruang paling besar supaya nama produk panjang tetap
   kebaca penuh, sisanya proporsional utk angka. */
/* Wrapper terluar per baris item — dari sebelumnya #quotationItemsBody
   langsung berisi .quotation-items-row (grid), sekarang dibungkus
   .quotation-item-wrap supaya pesan error per baris (.item-error-message)
   bisa ditampilkan TANPA ikut jadi kolom ke-7 di grid-template-columns
   milik .quotation-items-row (yang tetap 6 kolom persis seperti
   sebelumnya, TIDAK diubah sama sekali) — lihat _form.php. Flex column
   sederhana: grid row di atas, pesan error (kalau ada) di bawahnya. */
.quotation-item-wrap {
	display: flex;
	flex-direction: column;
}

/* Border pemisah antar baris & padding vertikal PINDAH ke sini (dari
   sebelumnya nempel di .quotation-items-row) — supaya border-nya
   membungkus SELURUH baris TERMASUK pesan error di bawahnya, bukan
   cuma grid-nya saja (kalau tetap di .quotation-items-row, border akan
   muncul DI ATAS pesan error, bukan di bawahnya — pesan error jadi
   terlihat "milik" baris berikutnya). */
#quotationItemsBody .quotation-item-wrap {
	border-bottom: 1px dashed var(--color-border-default);
	padding-block: var(--size-100, 4px);
}

#quotationItemsBody .quotation-item-wrap:last-child {
	border-bottom: none;
}

/* .quotation-items-row DI DALAM wrapper tidak lagi butuh padding
   vertikal sendiri (sudah dipindah ke wrapper di atas) — cukup grid
   layout & gap-nya saja. Padding lama var(--size-250) 4px diganti 0
   4px (horizontal tetap, vertikal 0) supaya tidak dobel dengan padding
   wrapper. */
.quotation-items-row {
	display: grid;
	grid-template-columns: 2.2fr 0.7fr 1.2fr 0.35fr 1.1fr 28px;
	gap: var(--size-250, 10px);
	align-items: center;
	padding: 0 0 0 4px;
}

.quotation-items-row-header {
	padding: 0 4px var(--size-200) 4px;
	border-bottom: 1px solid var(--color-border-default);
	font-size: var(--text-label-xs-size, 11px);
	font-weight: var(--font-weight-semibold);
	text-transform: uppercase;
	letter-spacing: var(--tracking-wide, 0.4px);
	color: var(--color-text-secondary);
	margin-bottom: var(--size-300);
}

/* Pesan error per baris — reuse .label-error (label.css) apa adanya
   (warna merah, icon "!", :empty otomatis disembunyikan saat kosong),
   cuma menambah margin-left supaya sejajar horizontal dengan kolom
   Product (padding-left: 4px yang sama dengan .quotation-items-row di
   atas), BUKAN mulai dari tepi wrapper. */
.item-error-message {
	margin-left: 4px;
	margin-top: var(--size-100, 4px);
}

.quotation-items-row .input {
	width: 100%;
	padding: var(--size-200) var(--size-250, 10px);
	font-size: var(--text-body-sm-size, 13px);
	border: 1px solid var(--color-border-default);
	border-radius: var(--radius-md);
	background-color: var(--color-bg-surface);
	color: var(--color-text-primary);
}

.quotation-items-row .input:focus {
	outline: none;
	border-color: var(--color-border-focus, var(--color-brand-500));
	box-shadow: 0 0 0 3px var(--shadow-focus-ring);
}

/* FIX — ERROR STATE tertimpa oleh aturan di atas.
   .quotation-items-row .input punya spesifisitas (0,2,0) — 2 class —
   sedangkan .input-error bawaan atoms/input/input.css cuma (0,1,0),
   jadi border merah dari Form.showError() (app-core.js) tidak pernah
   terlihat di baris item quotation, walau class "input-error" dan
   attr aria-invalid="true" sudah benar ter-pasang ke elemen (pesan
   teks di #error_items tetap muncul karena itu jalur terpisah).
   Selector di bawah SENGAJA ditulis .quotation-items-row .input.input-error
   (3 class, spesifisitas (0,3,0)) supaya menang telak di atas blok
   sebelumnya, tanpa perlu !important dan tanpa mengubah input.css
   yang dipakai bersama oleh form lain (customer, dll). */
.quotation-items-row .input.input-error {
	border-color: var(--color-error);
	/* background-color: var(--color-error-subtle); */
}

.quotation-items-row .input.input-error:focus {
	outline: none;
	border-color: var(--color-error);
	/* Sengaja TANPA box-shadow — user minta cukup border merah polos
	   saat error, tanpa ring/shadow di sekelilingnya (beda dari state
	   focus normal .input:focus yang memang punya
	   var(--shadow-focus-ring), lihat input.css). */
}

.item-subtotal-display {
	font-size: var(--text-body-sm-size, 13px);
	font-weight: var(--font-weight-semibold);
	text-align: right;
	white-space: nowrap;
	color: var(--color-text-primary);
}

.quotation-items-row .button-hapus-item {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	border: none;
	background: transparent;
	color: var(--color-error);
	cursor: pointer;
	border-radius: var(--radius-sm);
}

.quotation-items-row .button-hapus-item:hover {
	background-color: var(--color-error-subtle);
}

/* Tombol delete saat baris cuma tersisa 1 (di-disable via JS
   QuotationItemsForm.updateDeleteButtonsState()) — abu-abu, cursor
   not-allowed, hover TIDAK memicu background merah muda seperti state
   aktif, supaya user langsung paham dari tampilan bahwa baris terakhir
   tidak bisa dihapus (bukan baru tahu setelah klik lalu muncul toast). */
.quotation-items-row .button-hapus-item:disabled {
	color: var(--color-text-disabled, var(--color-text-secondary));
	cursor: not-allowed;
	opacity: 0.5;
}

.quotation-items-row .button-hapus-item:disabled:hover {
	background-color: transparent;
}

.quotation-items-footer {
	display: flex;
	justify-content: flex-end;
	align-items: baseline;
	gap: var(--size-200);
	margin-top: var(--size-300);
	padding-top: var(--size-300);
	border-top: 1px solid var(--color-border-default);
	font-size: var(--text-body-sm-size, 13.5px);
	color: var(--color-text-secondary);
}

.quotation-items-footer strong {
	font-size: var(--text-heading-xs-size, 16px);
	color: var(--color-brand-text);
}

/* ── Responsive: di layar sempit, jatuhkan ke layout stacked
   supaya input tidak terlalu kecil untuk diketik ────────────── */
@media (max-width: 720px) {
	.quotation-items-row-header {
		display: none;
	}

	/* border-bottom pemisah antar baris tetap di .quotation-item-wrap
	   (sudah didefinisikan di atas, berlaku juga di mobile) — DIHAPUS
	   dari .quotation-items-row di sini supaya tidak dobel garis
	   (dulu border-nya ada di sini, sekarang cukup satu tempat). */
	.quotation-items-row {
		grid-template-columns: 1fr 1fr;
		grid-template-areas:
			"name   name"
			"qty    price"
			"disc   subtotal"
			"delete delete";
		gap: var(--size-150, 6px);
		padding: var(--size-300) 4px;
	}

	/* AKTIFKAN grid-template-areas di atas — SEBELUMNYA tidak
	   berfungsi sama sekali karena tidak ada elemen anak yang
	   di-assign grid-area, jadi browser mengabaikan seluruh
	   grid-template-areas dan elemen tersusun otomatis mengikuti
	   urutan DOM saja (itulah kenapa Subtotal sempat terlihat rata
	   kiri, bukan di posisi yang "seharusnya"). */
	.quotation-items-row .item-product-name     { grid-area: name; }
	.quotation-items-row .item-quantity         { grid-area: qty; }
	.quotation-items-row .item-unit-price       { grid-area: price; }
	.quotation-items-row .item-discount         { grid-area: disc; }
	.quotation-items-row .item-subtotal-display { grid-area: subtotal; }
	.quotation-items-row .button-hapus-item     { grid-area: delete; }

	/* Subtotal ada di kolom KANAN (area "subtotal", baris sama dengan
	   Discount di kolom kiri) — sejajar tepat di bawah Unit Price yang
	   juga kolom kanan. text-align: right supaya digitnya sejajar rata
	   kanan dengan Unit Price di atasnya. */
	.item-subtotal-display {
		text-align: right;
		align-self: center;
		padding-right: var(--size-250, 10px);
	}

	/* Tombol delete TETAP di alur grid (bukan position:absolute —
	   pendekatan itu pernah dicoba tapi bikin tombol "lepas" dari
	   row-nya sendiri saat tinggi row bervariasi). Area sentuh 44x44px
	   (standar touch-target minimum), rata kanan supaya sejajar visual
	   dengan Subtotal & Unit Price di atasnya — BUKAN full-width. */
	.quotation-items-row .button-hapus-item {
		width: 44px;
		height: 44px;
		justify-self: end;
		justify-content: center;
		align-items: center;
		gap: 0;
	}

	/* Pesan error per baris di mobile — margin-left dikembalikan ke 0
	   (beda dari desktop yang 4px) karena padding kiri row di mobile
	   sudah 4px lewat .quotation-items-row padding di atas, ditambah
	   margin-left akan dobel-indent dibanding kolom Product. */
	.item-error-message {
		margin-left: 0;
	}
}

/* ============================================================
   Sisa 2 rule dulu ("samakan .cb__label dengan .label") SUDAH
   DIHAPUS (2026-09-03) — jadi dead code sejak quotation.js sengaja
   berhenti set opts.label untuk combobox ini (lihat komentar di
   QuotationCustomerCombobox.init()): combobox.js hanya merender
   <div class="cb__label"> KALAU opts.label di-set, jadi selector
   "#cb-id_customer .cb__label" itu tidak pernah match elemen apa
   pun di DOM saat ini. Label "Customer" sekarang murni ditulis
   manual di _form.php sebagai <label class="label label-sm">, di
   LUAR #cb-id_customer — satu sistem label yang sama dengan field
   Expires di sebelahnya (lihat blok "Simetri baris Customer +
   Expires" di atas untuk penyelarasan tingginya).

   2 rule LAIN di bagian ini (di bawah) MASIH DIPERTAHANKAN, masih
   relevan:
   ============================================================ */

/* 3. Sembunyikan pesan error BAWAAN combobox.js (.cb__error-msg,
      teks default "Pilihan tidak boleh kosong" — lihat combobox.js
      opts.errorMsg). Combobox punya sistem error visualnya SENDIRI:
      class "cb--error" pada .cb otomatis menampilkan .cb__error-msg
      lewat combobox.css (.cb.cb--error .cb__error-msg { display:block }).

      Sejak app-core.js (Form.showError/resetError/LiveValidation)
      diperluas agar toggle "cb--error" pada combobox saat validasi
      SERVER gagal, class itu SEKARANG ikut memicu .cb__error-msg
      bawaan SEKALIGUS DENGAN <p id="error_id_customer"> yang sudah
      diisi pesan dari server — dua pesan tampil bersamaan untuk
      satu field yang sama.

      Quotation (dan pola Customer sebelumnya) SENGAJA validasi murni
      di server — satu sumber kebenaran, satu pesan per field, sama
      seperti field <input>/<textarea> lain. #error_id_customer yang
      dipertahankan sebagai satu-satunya tempat pesan tampil; pesan
      bawaan combobox.js (client-side, generik, tidak tahu bahasa
      pesan Validator.php) disembunyikan di sini.

      Border merah TIDAK terpengaruh — itu diatur selector terpisah
      (.cb.cb--error .cb__input) yang tidak disentuh aturan ini.

      Di-scope ke #cb-id_customer (pakai ID, bukan !important) supaya
      combobox LAIN yang mungkin masih andalkan cb.validate() +
      .cb__error-msg bawaannya (client-side, belum tentu server-only)
      tidak ikut terdampak. */
#cb-id_customer.cb--error .cb__error-msg {
  display: none;
}

/* 4. FOCUS RING .cb__input dilepas (kotak cari combobox customer) —
      border tetap berubah warna saat fokus (affordance tetap ada),
      cuma box-shadow ring-nya yang dihapus. State ERROR JUGA tanpa
      ring (disamakan dengan keputusan yang sama di quotation-items
      .input.input-error:focus, lihat custom-style.css) — cukup border
      merah dari .cb--error, tanpa ring/shadow tambahan saat fokus. */
.cb__input:focus {
  box-shadow: none;
}

.cb.cb--error .cb__input:focus {
  box-shadow: none;
}
/* ==========================================================================
   CUSTOM OVERRIDES
   --------------------------------------------------------------------------
   Load file ini SETELAH design-system.min.css (dan custom.min.css bila ada).
   Isi: 2 perbaikan yang diminta —

   1. TEKS TERPOTONG di .input / .select / .select-modern
      Akar masalah: kita sebelumnya mengunci line-height ke 1 (var(--leading-none))
      supaya tinggi box presisi 34px/40px/48px. Itu benar untuk tinggi kotak,
      tapi terlalu ketat untuk font Inter — descender huruf seperti y, p, g, j
      butuh ruang vertikal lebih dari 1.0x font-size, jadi bagian bawahnya
      kepotong (lihat "All Type" pada screenshot).
      Fix: naikkan line-height ke nilai yang lebih longgar (1.4) TANPA
      mengubah `height` box — karena flex + align-items:center yang bikin teks
      tetap presisi center secara vertikal, bukan line-height. Ini aman untuk
      tinggi manapun (34/40/48px).

   2. HILANGKAN BLUR SHADOW SAAT FOKUS
      Token --shadow-focus-ring (dan varian -error/-success/-warning) memakai
      box-shadow blur 3px di sekitar border saat fokus. Kita ganti jadi solid
      ring tipis (spread tanpa blur) supaya tetap ada indikator fokus yang
      jelas (aksesibilitas), tapi tanpa efek "glow" blur.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. FIX TEKS TERPOTONG (descender y/p/g/j kepotong)
   -------------------------------------------------------------------------- */

/* .input — pastikan teks center vertikal via flex, line-height dilonggarkan */
.input,
.textarea {
  line-height: 1.4;
}

/* .select (atom biasa) */
.select {
  line-height: 1.4;
}

/* .select-wrapper perlu jadi flex supaya teks select tetap center vertikal
   walau line-height sudah tidak lagi 1 (mencegah teks naik/turun dari tengah) */
.select-wrapper .select {
  display:     flex;
  align-items: center;
}

/* select-modern — base-select mode & fallback mode, sudah flex secara default
   di select.css asli, cukup longgarkan line-height-nya saja */
select.select-modern,
.select-modern-fallback-trigger {
  line-height: 1.4;
}

/* input dengan icon/wrapper (search-bar dll) ikut kena, disamakan juga */
.search-bar-input {
  line-height: 1.4;
}


/* --------------------------------------------------------------------------
   2. HILANGKAN BLUR PADA FOCUS RING — solid ring, tanpa blur
   -------------------------------------------------------------------------- */



/* Kalau kamu maunya BENAR-BENAR tanpa ring warna sama sekali (cuma border
   biasa berubah warna, tanpa box-shadow tambahan apapun saat fokus),
   hapus blok :root di atas dan pakai blok di bawah ini sebagai gantinya
   (uncomment baris-baris berikut, hapus komentar):*/

   .input:focus,
   .select:focus,
   .textarea:focus,
   select.select-modern:focus-visible,
   select.select-modern:open,
   .select-modern-fallback-trigger:focus-visible {
     box-shadow: none !important;
   }


   /* UNTUK PLACEHOLDER BRANDS-LOGO */
.logo-placeholder {
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--color-bg-sunken);
	border-radius: inherit;
}
