/* ==========================================================================
   Kcoffee BI Dashboard — design tokens & primitives
   Layered on top of Tailwind. Only holds what utilities cannot express:
   font loading, chart overrides, scrollbar, and motion curves.
   ========================================================================== */

/* Plus Jakarta Sans is loaded from the Google Fonts CDN in views/layouts/main.php */

:root {
  /* Brand — Kcoffee identity upgraded to Tremor-grade tones */
  --k-primary: #1e3a8a;   /* Core Dark Blue  */
  --k-rose:    #f43f5e;   /* Trendy Rose     */
  --k-emerald: #10b981;   /* Emerald Green   */
  --k-amber:   #f59e0b;   /* Amber           */

  --k-canvas:  #f8fafc;
  --k-ink:     #0f172a;
  --k-muted:   #64748b;
  --k-hairline: rgb(15 23 42 / 0.06);

  /* Motion — physical spring, never linear/ease-in-out */
  --k-ease: cubic-bezier(0.32, 0.72, 0, 1);
}

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  font-family: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  background-color: var(--k-canvas);
  color: var(--k-ink);
  font-feature-settings: 'cv11', 'ss01';
}

/* Tabular figures everywhere numbers are compared vertically ------------- */
.k-num {
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* Slim, unobtrusive scrollbars ------------------------------------------ */
.k-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
.k-scroll::-webkit-scrollbar-track { background: transparent; }
.k-scroll::-webkit-scrollbar-thumb {
  background: rgb(148 163 184 / 0.35);
  border-radius: 9999px;
}
.k-scroll::-webkit-scrollbar-thumb:hover { background: rgb(100 116 139 / 0.5); }
.dark .k-scroll::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 0.12); }
.dark .k-scroll::-webkit-scrollbar-thumb:hover { background: rgb(255 255 255 / 0.2); }

/* Scroll-entry choreography --------------------------------------------- */
.k-reveal {
  opacity: 0;
  transform: translateY(14px);
  filter: blur(4px);
  transition:
    opacity 700ms var(--k-ease),
    transform 700ms var(--k-ease),
    filter 700ms var(--k-ease);
  will-change: transform, opacity;
}
.k-reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}
@media (prefers-reduced-motion: reduce) {
  .k-reveal { opacity: 1; transform: none; filter: none; transition: none; }
}

/* ApexCharts overrides — the library's defaults look cheap ---------------
   Note: tooltip.theme:'dark' makes ApexCharts add the class
   .apexcharts-theme-dark to .apexcharts-tooltip — that's the real selector,
   not a custom class we inject ourselves. */
.apexcharts-canvas { font-family: 'Plus Jakarta Sans', system-ui, sans-serif !important; }

.apexcharts-tooltip.apexcharts-theme-dark {
  background: rgb(15 23 42 / 0.96) !important;
  backdrop-filter: blur(10px);
  border: 0 !important;
  border-radius: 12px !important;
  box-shadow: 0 16px 40px -8px rgb(15 23 42 / 0.4), 0 0 0 1px rgb(255 255 255 / 0.06) !important;
  color: #f8fafc;
  overflow: hidden;
}
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-title {
  background: rgb(255 255 255 / 0.07) !important;
  border-bottom: 1px solid rgb(255 255 255 / 0.08) !important;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.02em;
  padding: 7px 12px !important;
  margin: 0 !important;
}
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-series-group {
  padding: 5px 12px 7px !important;
  font-size: 11.5px;
}
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-text-y-label { color: rgb(226 232 240 / 0.75); }
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-text-y-value { font-weight: 600; }
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-marker { margin-right: 8px; }

.apexcharts-xaxistooltip, .apexcharts-yaxistooltip { display: none !important; }
.apexcharts-gridline { stroke: rgb(15 23 42 / 0.05); }
.apexcharts-legend-text { color: var(--k-muted) !important; font-size: 11.5px !important; font-weight: 500; }
.apexcharts-xaxis-label, .apexcharts-yaxis-label { fill: #94a3b8; }
.dark .apexcharts-legend-text { color: #94a3b8 !important; }
.dark .apexcharts-xaxis-label, .dark .apexcharts-yaxis-label { fill: #cbd5e1; }

/* Donut/pie slices "pop" outward from the ring's center on hover — ApexCharts
   sets transform-origin on each slice path for its own entry animation, so a
   plain CSS scale hover reads as a clean radial explode, no JS needed. */
.apexcharts-pie-area {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 220ms var(--k-ease), filter 220ms var(--k-ease);
  cursor: pointer;
}
.apexcharts-pie-area:hover {
  transform: scale(1.07);
  filter: drop-shadow(0 6px 14px rgb(15 23 42 / 0.25));
}

/* Heatmap cells read better with a hairline separation ------------------- */
.apexcharts-heatmap-rect { stroke: #ffffff; stroke-width: 3px; }
.dark .apexcharts-heatmap-rect { stroke: #0b1324; }

/* Fixed film-grain texture — adds physical depth to the canvas.
   Only ever on a fixed, pointer-events-none layer; never on scroll content.
   Multiply reads as texture on a light canvas; overlay does the same on dark. */
.k-grain {
  position: fixed;
  inset: 0;
  z-index: -5;
  pointer-events: none;
  opacity: 0.025;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.dark .k-grain { opacity: 0.05; mix-blend-mode: overlay; }

/* Glass card shell shared by KPI tiles and chart cards -------------------- */
.k-card {
  background: rgb(255 255 255 / 0.78);
  backdrop-filter: blur(16px);
  box-shadow:
    0 1px 2px rgb(15 23 42 / 0.04),
    0 12px 32px -12px rgb(15 23 42 / 0.10);
  transition: background 300ms var(--k-ease), box-shadow 400ms var(--k-ease), transform 400ms var(--k-ease);
}
.k-card:hover {
  box-shadow:
    0 1px 2px rgb(15 23 42 / 0.04),
    0 20px 44px -14px rgb(15 23 42 / 0.16);
  transform: translateY(-2px);
}
.dark .k-card {
  background: rgb(255 255 255 / 0.045);
  box-shadow:
    0 1px 2px rgb(0 0 0 / 0.2),
    0 12px 32px -12px rgb(0 0 0 / 0.4);
}
.dark .k-card:hover {
  box-shadow:
    0 1px 2px rgb(0 0 0 / 0.2),
    0 20px 44px -14px rgb(0 0 0 / 0.5);
}

/* Icon badge glow — only reads as "glow" against a dark surface; on a light
   card the same blurred color just looks like a smudge, so it's dark-mode only. */
.k-icon-badge {
  position: relative;
  isolation: isolate;
}
.k-icon-badge::before {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: inherit;
  background: inherit;
  filter: blur(8px);
  opacity: 0;
  z-index: -1;
  transition: opacity 300ms var(--k-ease);
}
.dark .k-icon-badge::before {
  opacity: 0.55;
}

/* Mục đang chọn bo góc TRÁI, cạnh phải tràn hết ra mép panel — trông như một
   cái tab hàn vào sidebar, không phải viên thuốc trôi lơ lửng. -0.875rem triệt
   đúng phần đệm `px-3.5` của khung nav.

   Vẫn đúng với sidebar dạng thẻ nổi bo góc: panel khai `overflow-hidden` nên
   phần tràn bị chính góc bo của panel cắt theo, không lòi ra ngoài. */
.k-nav-item.is-active {
  margin-right: -0.875rem;
  padding-right: calc(0.75rem + 0.875rem);
}

/* Collapsible sidebar rail -------------------------------------------------
   Desktop-only (mobile always uses the full-width overlay drawer). Driven by
   a single `sb-collapsed` class on <html>, set pre-paint from localStorage
   in main.php so there is no flash of the wrong width on load. */
/* --k-pull: a back-out curve that overshoots slightly past the target width
   before settling, so opening reads as the button physically yanking the
   panel out rather than a flat linear resize. */
:root {
  --k-pull: cubic-bezier(0.34, 1.45, 0.64, 1);

  /* Single knob for the whole grab-and-pull sequence. Tuned deliberately
     slower than a typical UI transition so the grab reads as a physical
     gesture rather than a flicker — the panel slide and the hand animation
     are kept in step, with the hand trailing slightly so it releases last. */
  --k-pull-dur: 850ms;
  --k-grab-dur: 1000ms;
}

#sidebarPanel {
  transition: width var(--k-pull-dur) var(--k-pull);
}
.k-content-shift {
  padding-left: 0;
  transition: padding-left var(--k-pull-dur) var(--k-pull);
}
/* `button.` chứ không chỉ `.k-sidebar-toggle`: lớp `top-24` của Tailwind cũng là
   một lớp đơn, muốn chắc chắn thắng thì phải nhỉnh hơn về độ ưu tiên chứ không
   trông vào thứ tự nạp tệp. */
button.k-sidebar-toggle {
  /* Ngang tầm MỤC ĐẦU TIÊN của menu, không phải một con số tròn.
     16px mép trên thẻ + 64px chiều cao khối logo + 20px đệm trên của khung nav
     = mục đầu bắt đầu ở 100px; mục cao 48px nên tâm nó ở 124px; nút cao 32px
     nên mép trên nút = 124 − 16 = 108px. */
  top: 6.75rem;
  left: calc(18rem - 16px);
  transition: left var(--k-pull-dur) var(--k-pull), transform 200ms var(--k-ease);
  /* Native grab cursors — no JS or library needed for the hand affordance. */
  cursor: grab;
  /* The hand is larger than the 32px button and must not be clipped. */
  overflow: visible;
}
.k-sidebar-toggle:active {
  cursor: grabbing;
}

/* The blue disc gets out of the way entirely during the drag — otherwise it
   sits on top of the hand and hides the grab. It shrinks away as the hand
   arrives, then springs back once the pull is done. */
.k-sidebar-toggle.is-pulling .k-toggle-disc {
  animation: k-disc-yield var(--k-grab-dur) var(--k-pull);
}
html.sb-collapsed .k-sidebar-toggle.is-pulling .k-toggle-disc {
  animation: k-disc-yield var(--k-grab-dur) var(--k-pull);
}
@keyframes k-disc-yield {
  0%   { opacity: 1; transform: scale(1); }
  15%  { opacity: 0; transform: scale(0.3); }
  75%  { opacity: 0; transform: scale(0.3); }
  100% { opacity: 1; transform: scale(1); }
}

/* Hand: sits above the disc and overhangs it, so its fingers visibly close
   over the sidebar's edge. Idle-hidden; drives the whole grab beat. */
.k-toggle-fist {
  opacity: 0;
  z-index: 3;
  pointer-events: none;
  /* Starts off to the RIGHT of the edge — the hand reaches in from the content
     side, clamps the border, then drags leftward/rightward with the panel. */
  transform: translate(10%, -50%) scale(0.6);
  /* Heavier stroke + a light rim keeps the hand legible over both the light
     canvas and the dark sidebar it straddles. */
  stroke-width: 2;
  filter:
    drop-shadow(0 0 3px rgb(255 255 255 / 0.9))
    drop-shadow(0 4px 10px rgb(15 23 42 / 0.4));
}
.dark .k-toggle-fist {
  filter:
    drop-shadow(0 0 3px rgb(11 19 36 / 0.9))
    drop-shadow(0 4px 10px rgb(0 0 0 / 0.5));
}
.k-toggle-fist svg {
  stroke-width: 2;
}
.k-sidebar-toggle.is-pulling .k-toggle-fist {
  animation: k-fist-grab var(--k-grab-dur) var(--k-pull) forwards;
}
html.sb-collapsed .k-sidebar-toggle.is-pulling .k-toggle-fist {
  animation: k-fist-grab-rev var(--k-grab-dur) var(--k-pull) forwards;
}

/* EXPANDING: hand reaches in from the right, clamps the edge, hauls it right.
   The fist already points left, so no base rotation is needed — only a small
   wrist tilt for life. Horizontal travel is what sells the drag. */
@keyframes k-fist-grab {
  0%   { opacity: 0; transform: translate(20%, -50%)  scale(0.6)  rotate(-8deg); }
  20%  { opacity: 1; transform: translate(-28%, -50%) scale(1.25) rotate(-4deg); }
  40%  { opacity: 1; transform: translate(-38%, -50%) scale(0.95) rotate(0deg); }
  72%  { opacity: 1; transform: translate(18%, -50%)  scale(1)    rotate(4deg); }
  100% { opacity: 0; transform: translate(38%, -50%)  scale(0.85) rotate(6deg); }
}
/* COLLAPSING: mirrored horizontally so the fingers face right, and the haul
   runs the other way. */
@keyframes k-fist-grab-rev {
  0%   { opacity: 0; transform: translate(-140%, -50%) scale(0.6)  rotate(8deg)  scaleX(-1); }
  20%  { opacity: 1; transform: translate(-92%, -50%)  scale(1.25) rotate(4deg)  scaleX(-1); }
  40%  { opacity: 1; transform: translate(-82%, -50%)  scale(0.95) rotate(0deg)  scaleX(-1); }
  72%  { opacity: 1; transform: translate(-138%, -50%) scale(1)    rotate(-4deg) scaleX(-1); }
  100% { opacity: 0; transform: translate(-158%, -50%) scale(0.85) rotate(-6deg) scaleX(-1); }
}

/* Edge grip bar: pinches vertically at the exact moment the fist clamps. */
.k-toggle-grip {
  opacity: 0;
  transform: translateX(-50%) scaleY(1);
}
.k-sidebar-toggle.is-pulling .k-toggle-grip {
  animation: k-grip-pinch var(--k-grab-dur) var(--k-pull);
}
@keyframes k-grip-pinch {
  0%   { opacity: 0;    transform: translateX(-50%) scaleY(1); }
  25%  { opacity: 0.85; transform: translateX(-50%) scaleY(1.25); }
  45%  { opacity: 1;    transform: translateX(-50%) scaleY(0.45); }
  100% { opacity: 0;    transform: translateX(-50%) scaleY(1); }
}

/* Halo pings outward once at the moment of the pull ----------------------- */
.k-sidebar-toggle.is-pulling .k-toggle-halo {
  animation: k-halo-ping var(--k-pull-dur) var(--k-ease);
}
@keyframes k-halo-ping {
  0%   { opacity: 0.9; transform: scale(0.7); }
  100% { opacity: 0;   transform: scale(2.4); }
}

@media (prefers-reduced-motion: reduce) {
  #sidebarPanel, .k-content-shift, .k-sidebar-toggle { transition-duration: 1ms; }
  .k-sidebar-toggle.is-pulling .k-toggle-disc,
  .k-sidebar-toggle.is-pulling .k-toggle-halo,
  .k-sidebar-toggle.is-pulling .k-toggle-fist { animation: none; }
}

/* Core disc: lifts and brightens on hover, presses in on click ------------- */
.k-toggle-disc {
  transition: transform 350ms var(--k-ease), box-shadow 350ms var(--k-ease);
}
.k-sidebar-toggle:hover .k-toggle-disc {
  transform: scale(1.12);
  box-shadow: 0 10px 24px -6px rgb(30 58 138 / 0.6);
}

/* Halo: idle-invisible, blooms outward on hover --------------------------- */
.k-toggle-halo {
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 400ms var(--k-ease), transform 400ms var(--k-ease);
}
.k-sidebar-toggle:hover .k-toggle-halo {
  opacity: 1;
  transform: scale(1.85);
}

/* Chevron: rotates a full 180° between states, with a slight nudge on hover */
#sidebarCollapseChevron {
  transition: transform 400ms var(--k-ease);
}
.k-sidebar-toggle:hover #sidebarCollapseChevron {
  transform: translateX(-2px);
}
@media (min-width: 1024px) {
  /* ------------------------------------------------------------------------
     Sidebar là THẺ NỔI, cao đúng bằng nội dung.

     Trước đây panel tràn từ đỉnh xuống đáy màn hình. Mảng nhiều tab (F&B 13
     mục) thì đầy đẹp, mảng ít tab (Live 5, Ecommerce 6) thì chừa một mảng
     trắng dài dưới menu — mà khoảng trống nằm ở CUỐI thì đọc thành "chưa làm
     xong". Không có cách nào kéo 5 mục thành 13 mà không bịa thêm tab, nên
     thay vì lấp khoảng trống thì bỏ hẳn cái panel ở chỗ đó.

     `inset-y-0` của Tailwind đặt cả top lẫn bottom = 0. Trả `bottom: auto` là
     chiều cao quay về đúng nội dung; `max-height` giữ cho mảng nhiều tab không
     tràn khỏi màn hình, lúc đó `nav` tự cuộn bên trong.

     Chỉ áp từ 1024px. Dưới ngưỡng đó sidebar vẫn là ngăn kéo phủ toàn màn hình,
     đụng vào là hỏng thao tác trên điện thoại.
     --------------------------------------------------------------------- */
  #sidebarPanel {
    top: 1rem;
    left: 1rem;
    bottom: auto;
    max-height: calc(100vh - 2rem);
    border-radius: 1.25rem;
    /* Viền chạy quanh cả bốn cạnh, không chỉ cạnh phải như panel dán mép. */
    border: 1px solid rgb(15 23 42 / 0.06);
    box-shadow:
      0 1px 2px rgb(15 23 42 / 0.04),
      0 18px 44px -16px rgb(15 23 42 / 0.18);
    transition: width var(--k-pull-dur) var(--k-pull);
  }
  .dark #sidebarPanel {
    border-color: rgb(255 255 255 / 0.06);
    box-shadow:
      0 1px 2px rgb(0 0 0 / 0.25),
      0 18px 44px -16px rgb(0 0 0 / 0.55);
  }

  /* Thanh đầu trang cũng phải là THẺ NỔI, nếu không hai khối chửi nhau: sidebar
     bắt đầu ở 16px còn header dán sát mép trên 0px, nhìn như hai mảnh ghép lệch.
     Cùng bo góc, cùng viền, cùng mốc trên 16px thì chúng thành một bộ.

     Mép trái/phải thẻ header khớp đúng mép nội dung bên dưới (main có
     `lg:px-8`), nên header và các thẻ trong trang thẳng một đường dọc. */
  #kHeader {
    top: 1rem;
    margin: 1rem 2rem 0;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    border-radius: 1.25rem;
    border: 1px solid rgb(15 23 42 / 0.06);
    box-shadow:
      0 1px 2px rgb(15 23 42 / 0.04),
      0 18px 44px -16px rgb(15 23 42 / 0.18);
  }
  .dark #kHeader {
    border-color: rgb(255 255 255 / 0.06);
    box-shadow:
      0 1px 2px rgb(0 0 0 / 0.25),
      0 18px 44px -16px rgb(0 0 0 / 0.55);
  }

  /* 18,5rem = 1rem lề + 18rem panel + 1,5rem khoảng thở, TRỪ đi 2rem padding
     của main. Cộng lại thì nội dung cách mép phải sidebar đúng 24px. */
  .k-content-shift { padding-left: 18.5rem; }
  /* Phải giữ `button.` như quy tắc gốc: media query KHÔNG cộng thêm độ ưu tiên,
     để `.k-sidebar-toggle` trơn thì quy tắc gốc thắng và nút đứng sai chỗ. */
  button.k-sidebar-toggle { left: calc(18rem + 1rem - 16px); }

  /* Collapse only ever applies at the desktop breakpoint — on mobile the
     sidebar is always a full-width overlay drawer regardless of this class. */
  html.sb-collapsed #sidebarPanel { width: 5rem; }
  html.sb-collapsed .k-content-shift { padding-left: 5.5rem; }
  html.sb-collapsed button.k-sidebar-toggle { left: calc(5rem + 1rem - 16px); }
}
@media (min-width: 1024px) {
  html.sb-collapsed #sidebarCollapseChevron { transform: rotate(180deg); }
  html.sb-collapsed .k-sidebar-toggle:hover #sidebarCollapseChevron { transform: rotate(180deg) translateX(-2px); }

  html.sb-collapsed .k-sb-label {
    display: none;
  }
  html.sb-collapsed .k-sb-header {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }
  html.sb-collapsed .k-nav-item {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
    gap: 0;
  }
  html.sb-collapsed .k-sb-footer > div {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
    gap: 0;
  }
}

/* ==========================================================================
   Khối có chiều sâu — dùng cho hàng KPI, bục xếp hạng và cột sản phẩm.

   Nguyên tắc: không dùng bóng đen thuần và không dùng gradient bão hoà. Chiều
   sâu đến từ ba lớp chồng nhau — một vệt sáng ở mép trên, một vệt tối ở mép
   dưới, và một bóng đổ nhuộm theo đúng màu của vật thể. Đó là cách vật thể
   thật phản ứng với ánh sáng, nên mắt đọc ra "dày" chứ không đọc ra "hiệu ứng".
   ========================================================================== */

/* Chip nổi — nền cho icon. Thay cho ô màu phẳng trước đây. */
.k-chip-3d {
  position: relative;
  isolation: isolate;
  background-image: linear-gradient(158deg, rgb(255 255 255 / 0.30), rgb(255 255 255 / 0) 48%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.45),
    inset 0 -1px 0 rgb(0 0 0 / 0.14),
    0 5px 12px -4px rgb(15 23 42 / 0.35);
}

/* Chip trung tính trong tiêu đề thẻ: không có màu nhấn nên vệt sáng phải nhẹ
   hơn, nếu không nó trông như một nút bấm bị hỏng. */
.k-chip-soft {
  background-image: linear-gradient(158deg, rgb(255 255 255 / 0.9), rgb(255 255 255 / 0) 60%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.9),
    0 2px 6px -2px rgb(15 23 42 / 0.18);
}
.dark .k-chip-soft {
  background-image: linear-gradient(158deg, rgb(255 255 255 / 0.12), rgb(255 255 255 / 0) 60%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.14),
    0 2px 6px -2px rgb(0 0 0 / 0.5);
}

/* Đường nhịp trong bảng xếp hạng ---------------------------------------- */
/* Đường tự vẽ từ trái sang khi trang mở. Mục đích
   là dẫn mắt theo chiều thời gian đúng chiều đọc, chạy đúng một lần rồi thôi. */
@media (prefers-reduced-motion: no-preference) {
  .k-spark-line {
    stroke-dasharray: 200;
    stroke-dashoffset: 200;
    animation: k-spark-draw 900ms var(--k-ease) forwards;
    animation-delay: var(--k-spark-delay, 0ms);
  }
}

@keyframes k-spark-draw { to { stroke-dashoffset: 0; } }

/* Cột "Sản phẩm bán chạy" nhấc lên khi rê chuột ------------------------- */
/* Giới hạn trong đúng biểu đồ này bằng id, không áp cho mọi biểu đồ cột trên
   dashboard: ở biểu đồ cột chồng, nhấc một đoạn lên sẽ làm hở khe giữa các
   đoạn, nhìn như vẽ sai.

   transform-box: fill-box để gốc toạ độ tính theo chính hình chữ nhật của cột,
   không phải theo cả khung SVG — thiếu dòng này cột sẽ bay lung tung. */
#chartTopProducts .apexcharts-bar-area {
  transform-box: fill-box;
  transform-origin: bottom center;
  transition: transform 240ms var(--k-ease), filter 240ms var(--k-ease);
  cursor: pointer;
}

@media (prefers-reduced-motion: no-preference) {
  #chartTopProducts .apexcharts-bar-area:hover {
    transform: translateY(-7px);
    filter: brightness(1.12) drop-shadow(0 8px 12px rgb(15 23 42 / 0.22));
  }
}

/* Người tắt hiệu ứng vẫn cần biết đang chỉ vào cột nào, chỉ là không nhấc. */
@media (prefers-reduced-motion: reduce) {
  #chartTopProducts .apexcharts-bar-area:hover { filter: brightness(1.15); }
}

/* Trang 404 --------------------------------------------------------------- */
/* Cỡ hình động chỉnh bằng ĐÚNG MỘT biến dưới đây, không sửa chỗ nào khác. */
:root { --k-404-art: 420px; }

/* Trang 404 phải gọn trong đúng một khung hình, không đẻ ra thanh cuộn. Chiều
   cao khoá cứng bằng phần màn hình còn lại sau thanh đầu trang, phần đệm của
   vùng nội dung và dải chân trang (~13rem). */
.k-404 {
  height: calc(100dvh - 13rem);
  min-height: 26rem;
  overflow: hidden;
}

.k-404-art {
  /* Vừa chặn theo bề ngang, vừa chặn theo chiều cao màn hình: màn thấp thì hình
     tự nhỏ lại nhường chỗ cho chữ, thay vì đẩy nút xuống dưới mép. */
  width: min(100%, var(--k-404-art));
  max-height: 34dvh;
  height: auto;
  /* <div> rỗng không có kích thước nội tại như <img>, nên bề ngang ở trên cộng
     với aspect-ratio (đặt inline theo đúng w/h đọc từ file Lottie) mới suy ra
     được chiều cao — thiếu một trong hai là thẻ nhảy lúc ảnh động vẽ xong. */
  opacity: 0;
  transition: opacity 500ms var(--k-ease);
}

/* Không có file Lottie thì view dựng icon tĩnh với cùng lớp này — nó phải hiện
   ngay, vì chẳng có sự kiện DOMLoaded nào để bật .is-ready cả. */
.k-404-art:not([data-src]) { opacity: 1; }
.k-404-art.is-ready { opacity: 1; }
/* Chỉ nhắm khung Lottie: icon dự phòng đã có cỡ riêng, ép 100% là nó phình ra. */
.k-404-art[data-src] svg { display: block; width: 100%; height: auto; }


/* Đổi trang không tải lại ------------------------------------------------- */
/* Trong lúc chờ nội dung mới về, vùng nội dung mờ nhẹ đi. Chỉ mờ chứ không ẩn:
   người xem vẫn thấy trang cũ nên không có cảm giác trắng màn hình. */
.k-swap {
  transition: opacity 200ms var(--k-ease);
}
.k-swap.is-swapping {
  opacity: 0.45;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .k-swap { transition: none; }
}

/* Hàng bảng: rê chuột vào phải thấy rõ đang trỏ dòng nào ------------------ */
/* Bản cũ tô nền quá nhạt (slate-50/70, white 3%) nên gần như không thấy. Đậm
   hơn, thêm một vệt màu thương hiệu ở cạnh trái, và nhích nhẹ sang phải cho
   biết dòng đang "sống". */
/* Vệt màu cạnh trái vẽ bằng box-shadow inset, KHÔNG dùng ::before định vị tuyệt
   đối trên <tr>. Đặt position:relative lên <tr> là không chuẩn — Safari và một
   số bản Firefox render lệch nội dung cả dòng sang cột khác vì điều đó. Dùng
   box-shadow thì không đụng gì tới luồng bố cục nên không thể làm lệch. */
.k-row {
  transition: background-color 180ms var(--k-ease), box-shadow 180ms var(--k-ease);
}
.k-row:hover {
  background: rgb(30 58 138 / 0.05);
  box-shadow: inset 3px 0 0 0 var(--k-primary);
}
.dark .k-row:hover {
  background: rgb(255 255 255 / 0.055);
}

/* Ô đầu tiên nhích vào một chút để nhường chỗ cho vệt màu, không đè lên chữ. */
.k-row:hover > td:first-child { padding-left: 0.75rem; }
.k-row > td:first-child { transition: padding-left 180ms var(--k-ease); }

@media (prefers-reduced-motion: reduce) {
  .k-row, .k-row > td:first-child { transition: none; }
}

/* Bộ đổi kiểu biểu đồ (segmented) — nút đang chọn nổi lên nền trắng, phẳng,
   không dùng đổ khối 3D. JS chỉ việc bật/tắt lớp .k-ct-on. */
.k-ct-btn.k-ct-on {
  background: #fff;
  color: var(--k-primary);
  box-shadow: 0 1px 2px rgb(15 23 42 / 0.06);
}
.dark .k-ct-btn.k-ct-on {
  background: rgb(255 255 255 / 0.10);
  color: #fff;
  box-shadow: none;
}

/* Loading + trạng thái rỗng ---------------------------------------------- */
.k-loader {
  /* fixed, không phải absolute: neo theo MÀN HÌNH nhìn thấy chứ không theo chiều
     cao nội dung. Nội dung dài cả nghìn pixel mà absolute + căn giữa thì ảnh
     loading rơi xuống giữa vùng dài đó, khuất khỏi màn hình. Cạnh trái lùi vào
     bằng bề rộng thanh bên (media query bên dưới) để chỉ phủ phần nội dung. */
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(248 250 252 / 0.72);
  backdrop-filter: blur(3px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms var(--k-ease);
}
.dark .k-loader { background: rgb(11 19 36 / 0.74); }

/* Lần tải/F5: phủ cả màn hình, gồm cả thanh bên. */
.k-loader.is-full { position: fixed; z-index: 60; left: 0; }

/* Cột nội dung trải rộng cả màn hình (chỉ đẩy chữ bằng padding-left), nên loader
   absolute inset:0 sẽ phủ cả vùng dưới thanh bên. Ở desktop lùi cạnh trái vào
   đúng bề rộng thanh bên để nó chỉ phủ phần nội dung. is-full (F5) thì bỏ qua. */
@media (min-width: 1024px) {
  .k-loader:not(.is-full) { left: 18rem; }
  html.sb-collapsed .k-loader:not(.is-full) { left: 5rem; }
}
.k-loader.is-on { opacity: 1; pointer-events: auto; }

.k-loader-art { width: 210px; height: auto; }
.k-loader-art.is-failed { display: none; }

/* Vòng xoay dự phòng khi chưa thả tệp Lottie loading vào. */
.k-spinner {
  display: block;
  width: 40px; height: 40px;
  border-radius: 9999px;
  border: 3px solid rgb(30 58 138 / 0.15);
  border-top-color: var(--k-primary);
  animation: k-spin 720ms linear infinite;
}
.dark .k-spinner { border-color: rgb(255 255 255 / 0.14); border-top-color: #93b4ff; }
@keyframes k-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .k-spinner { animation-duration: 1800ms; } }

/* Ảnh động trạng thái rỗng: giữ chỗ, mờ vào khi vẽ xong. */
.k-empty-lottie {
  width: min(200px, 60%);
  height: auto;
  opacity: 0;
  transition: opacity 400ms var(--k-ease);
}
.k-empty-lottie.is-ready { opacity: 1; }
.k-empty-lottie.is-failed { display: none; }

/* ----------------------------------------------------------------------------
   Thông báo nổi khi dữ liệu đổi theo thời gian thực.

   Đặt ở góc dưới bên phải chứ không phải giữa màn hình: nó chỉ giải thích vì
   sao biểu đồ vừa nhảy số, không được che mất chính cái biểu đồ đó.
---------------------------------------------------------------------------- */
.k-toast {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  max-width: min(22rem, calc(100vw - 3rem));
  padding: 0.75rem 1.125rem;
  border-radius: 0.875rem;
  background: #fff;
  color: #334155;
  font-size: 13.5px;
  font-weight: 500;
  box-shadow: 0 16px 40px -12px rgb(15 23 42 / 0.28);
  outline: 1px solid rgb(15 23 42 / 0.06);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 350ms var(--k-ease), transform 350ms var(--k-ease);
}
.k-toast.is-on { opacity: 1; transform: translateY(0); }
.dark .k-toast {
  background: #111c33;
  color: #e2e8f0;
  outline-color: rgb(255 255 255 / 0.10);
}

/* Chấm nhịp: xanh lá vì đây luôn là tin "dữ liệu vừa về", không phải cảnh báo. */
.k-toast-dot {
  flex: none;
  width: 0.5rem; height: 0.5rem;
  border-radius: 9999px;
  background: var(--k-emerald);
  box-shadow: 0 0 0 0 rgb(16 185 129 / 0.55);
  animation: k-toast-pulse 1600ms ease-out infinite;
}
@keyframes k-toast-pulse {
  70%  { box-shadow: 0 0 0 7px rgb(16 185 129 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(16 185 129 / 0); }
}
@media (prefers-reduced-motion: reduce) { .k-toast-dot { animation: none; } }


/* ----------------------------------------------------------------------------
   Dải sáng quét ngang con số vừa đổi giá trị khi dữ liệu mới về.

   Làm bằng NỀN chuyển sắc chạy ngang, không dùng phần tử giả và không đổi
   display: mấy con số này khi thì nằm trong <p> khối, khi thì trong <span> nội
   dòng giữa một ô bảng. Đụng vào display là chữ nhích một nhịp — mà lớp này
   sinh ra chính là để không có cú nhích nào.

   Màu xám trung tính, KHÔNG dùng xanh/hồng: trong dự án hai màu đó nghĩa là
   tốt lên / kém đi. Một con số vừa giảm mà quét màu xanh là nói sai.
---------------------------------------------------------------------------- */
:root {
  /* Núm chỉnh duy nhất cho dải sáng. Chậm hơn một hiệu ứng giao diện thường
     thấy, vì nó phải kịp lọt vào mắt người đang nhìn chỗ khác trên màn hình.
     JavaScript đọc lại đúng biến này nên đổi ở đây là đủ, không phải sửa hai nơi. */
  --k-shimmer-dur: 1150ms;
}

.k-shimmer {
  border-radius: 5px;
  background-repeat: no-repeat;
  background-size: 220% 100%;
  background-image: linear-gradient(
    100deg,
    rgb(148 163 184 / 0)    18%,
    rgb(148 163 184 / 0.26) 42%,
    rgb(148 163 184 / 0.32) 50%,
    rgb(148 163 184 / 0.26) 58%,
    rgb(148 163 184 / 0)    82%
  );
  animation: k-shimmer var(--k-shimmer-dur) var(--k-ease);
}

/* Đi từ ngoài mép trái sang hẳn ngoài mép phải, nên dải sáng vào và ra trọn
   vẹn chứ không hiện lên rồi tắt lửng giữa con số. */
@keyframes k-shimmer {
  from { background-position: -90% 0; }
  to   { background-position: 190% 0; }
}

.dark .k-shimmer {
  background-image: linear-gradient(
    100deg,
    rgb(186 210 255 / 0)    18%,
    rgb(186 210 255 / 0.20) 42%,
    rgb(186 210 255 / 0.26) 50%,
    rgb(186 210 255 / 0.20) 58%,
    rgb(186 210 255 / 0)    82%
  );
}

/* Người tắt hiệu ứng chuyển động thì thay bằng một lần sáng lên rồi tắt. */
@media (prefers-reduced-motion: reduce) {
  .k-shimmer {
    animation: k-shimmer-still 500ms ease-out;
    background-position: 50% 0;
  }
  @keyframes k-shimmer-still { from { opacity: 0.55; } to { opacity: 1; } }
}

/* ----------------------------------------------------------------------------
   Bản đồ phân bổ theo khu vực (tab Cửa hàng).

   Đường bao sinh từ dữ liệu biên giới thật rồi dựng thẳng ở máy chủ, nên nó
   hiện ngay trong lượt tải đầu — không phải đợi JavaScript, không gọi thêm
   thư viện bản đồ nào.
---------------------------------------------------------------------------- */
.k-vnmap {
  height: 330px;
  /* aspect-ratio khai thẳng tỉ lệ của khung nhìn. Chỉ để width:auto thì trình
     duyệt phải tự suy ra bề ngang từ viewBox — Chrome/Firefox làm đúng, nhưng
     Safari có lúc cho ra 0 hoặc 100%, tức là bản đồ biến mất hoặc phình to.
     Khai sẵn thì mọi trình duyệt tính ra cùng một con số. */
  aspect-ratio: 400 / 827;
  width: auto;
  flex: none;
  overflow: visible;
}

/* Đất liền phải đủ đậm để tách khỏi nền thẻ. Bản trước lấy màu quá nhạt: trên
   nền trắng ngà của thẻ thì mờ, còn ở chế độ tối (7% trắng trên nền navy) thì
   gần như không nhìn thấy gì — dễ tưởng là bản đồ không hiện. */
.k-vnmap-land {
  fill: rgb(203 213 225 / 0.75);
  stroke: rgb(100 116 139 / 0.45);
  stroke-width: 1.2;
  stroke-linejoin: round;
}
.dark .k-vnmap-land {
  fill: rgb(255 255 255 / 0.14);
  stroke: rgb(255 255 255 / 0.30);
}

/* Hai vòng tròn lồng nhau: quầng mờ nói độ lớn, lõi đặc đánh dấu vị trí. Một
   chấm đặc cỡ lớn sẽ nuốt mất phần đất liền quanh nó. */
.k-vnmap-halo {
  fill: var(--k-primary);
  opacity: 0.16;
  transition: opacity 220ms var(--k-ease);
}
.k-vnmap-core { fill: var(--k-primary); }
.dark .k-vnmap-halo { fill: #93b4ff; opacity: 0.22; }
.dark .k-vnmap-core { fill: #93b4ff; }

.k-vnmap-dot { cursor: pointer; }
.k-vnmap-dot:hover .k-vnmap-halo { opacity: 0.34; }
.dark .k-vnmap-dot:hover .k-vnmap-halo { opacity: 0.42; }

/* Màn hình hẹp: bản đồ nhỏ lại để danh sách bên cạnh còn chỗ thở. Việt Nam cao
   gấp đôi bề ngang nên hạ chiều cao là bề ngang co theo rất nhanh — 210px cao
   chỉ chiếm khoảng 100px ngang, chừa gần 200px cho tên vùng và con số. */
@media (max-width: 1279px) { .k-vnmap { height: 260px; } }
@media (max-width: 639px)  { .k-vnmap { height: 210px; } }

/* ----------------------------------------------------------------------------
   Chốt chặn cuối cho biểu đồ đo nhầm bề rộng.

   ApexCharts dựng một khung bọc có bề rộng ghi cứng bằng pixel. Đo nhầm lúc
   kiểu chưa áp xong là con số đó lớn hơn thẻ chứa, và hình lòi hẳn ra ngoài.
   Lớp hoãn vẽ trong kChart lo phần gốc, min-w-0 trên thẻ lo phần bố cục; dòng
   này là lớp thứ ba, chặn đúng phần nhìn thấy trong khoảnh khắc trước khi hai
   lớp kia kịp xử lý.
---------------------------------------------------------------------------- */
.apexcharts-canvas { max-width: 100%; }

/* ----------------------------------------------------------------------------
   Khối gấp mở <details>. Dùng thẻ gốc của trình duyệt chứ không tự viết bằng
   JavaScript: mở/đóng chạy được cả khi script chưa tải xong, và Ctrl+F của
   trình duyệt vẫn tìm thấy chữ bên trong phần đang đóng.

   Bỏ tam giác mặc định (mỗi trình duyệt vẽ một kiểu) rồi tự đặt mũi tên riêng
   ở cuối dòng tiêu đề — quay 180 độ khi mở.
---------------------------------------------------------------------------- */
.k-fold > summary { list-style: none; cursor: pointer; }
.k-fold > summary::-webkit-details-marker { display: none; }
.k-fold > summary::marker { content: ''; }
/* inline-flex chứ không để mặc định: <span> là phần tử nội tuyến, mà transform
   KHÔNG áp lên phần tử nội tuyến — để nguyên thì mũi tên đứng im khi mở ra. */
.k-fold-chev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1), background-color 0.2s, color 0.2s;
}
.k-fold[open] > summary .k-fold-chev { transform: rotate(180deg); }
.k-fold > summary:hover .k-fold-chev { background: rgba(15, 23, 42, 0.06); color: #334155; }
.dark .k-fold > summary:hover .k-fold-chev { background: rgba(255, 255, 255, 0.10); color: #e2e8f0; }

/* Thanh xếp hạng lãi/lỗ trong tab Tài chính. Chạy từ 0 ra chiều dài thật khi
   thẻ hiện lên, cùng nhịp với các thanh tiến độ khác của dự án — phẳng, không
   bóng, không nổi khối. */
.k-rank-fill {
  transform-origin: left center;
  animation: k-rank-grow 0.7s cubic-bezier(0.32, 0.72, 0, 1) both;
}
@keyframes k-rank-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) { .k-rank-fill { animation: none; } }

/* Dòng bị phân trang ở phía trình duyệt giấu đi (xem kInitLocalPagers trong
   main.php). Dùng lớp chứ không đặt style nội tuyến: morphdom lúc cập nhật
   sống đồng bộ cả thuộc tính style từ HTML máy chủ về, mà HTML đó không hề
   biết trang nào đang mở — đặt nội tuyến là mọi dòng bật lại hết một lượt.
   `!important` vì dòng bảng và ô lưới mang display riêng của chúng. */
.k-lp-hide { display: none !important; }

/* Dải phân trang ----------------------------------------------------------
   Viết hẳn thành component CSS chứ KHÔNG ghép từ lớp tiện ích Tailwind. Lý do
   là chuyện đã gặp: Tailwind ở đây nạp bản CDN biên dịch ngay trong trình
   duyệt, nó dò tên lớp trong tài liệu rồi mới sinh CSS. Nút phân trang lại
   được JavaScript dựng lúc chạy và dùng lớp giá trị tự do (`min-w-[38px]`,
   `bg-slate-900/[0.04]`) — dò không ra lớp nào là mất đúng thuộc tính đó, và
   hàng nút so le cao thấp rộng hẹp. Ở đây kích thước khai bằng pixel nên nó
   luôn ra đúng, không phụ thuộc thư viện.

   Cùng lúc giải luôn chuyện máy chủ và JavaScript phải nhất trí từng lớp một:
   cả hai chỉ còn viết `k-pg`, `k-pg is-on`, `k-pg is-off`. */
.k-pg-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.875rem 1.25rem;
  border-top: 1px solid rgb(241 245 249);
}
.dark .k-pg-bar { border-top-color: rgb(255 255 255 / 0.06); }

.k-pg-info { font-size: 13px; color: #94a3b8; }
.dark .k-pg-info { color: #64748b; }

.k-pg-nav { display: flex; align-items: center; gap: 0.375rem; }

/* min-width 38px là vừa đủ BA chữ số ở cỡ 13px: nút "1", nút "180" và hai nút
   mũi tên ra đúng cùng một bề ngang. Để nút tự co theo nội dung thì "180" nong
   rộng hơn "1" vài pixel — các nút đứng sát nhau nên mắt bắt được ngay. */
.k-pg {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 38px;
  height: 32px;
  padding: 0 8px;
  margin: 0;
  border: 0;
  border-radius: 10px;
  background: rgb(15 23 42 / 0.04);
  color: #64748b;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  /* line-height: 1 để chữ số nằm đúng tâm hộp 32px — font Plus Jakarta Sans có
     phần trên/dưới lệch nhau, thừa kế line-height là số bị đẩy nhích xuống so
     với nút mũi tên (mũi tên là SVG, không mang metric chữ). */
  line-height: 1;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  transition: background-color 200ms var(--k-ease), color 200ms var(--k-ease);
}
.k-pg svg { display: block; width: 16px; height: 16px; }
.k-pg:hover { color: #1e293b; }
.dark .k-pg { background: rgb(255 255 255 / 0.06); color: #94a3b8; }
.dark .k-pg:hover { color: #ffffff; }

.k-pg.is-on,
.k-pg.is-on:hover { background: var(--k-primary); color: #ffffff; font-weight: 600; }

/* Mũi tên khi đã đứng ở trang đầu/cuối: thấy nhưng bấm không được. */
.k-pg.is-off,
.k-pg.is-off:hover { background: rgb(15 23 42 / 0.02); color: #cbd5e1; cursor: not-allowed; }
.dark .k-pg.is-off,
.dark .k-pg.is-off:hover { background: rgb(255 255 255 / 0.03); color: #334155; }

/* Dấu ba chấm mang ĐÚNG chiều cao của nút, chỉ bỏ nền: glyph "…" nằm sát đáy
   dòng chữ, để nó trong một hộp thấp hơn là ba chấm rơi xuống dưới tâm hàng. */
.k-pg-gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  padding: 0 4px;
  font-size: 13px;
  line-height: 1;
  color: #cbd5e1;
  user-select: none;
}
.dark .k-pg-gap { color: #475569; }

/* Vạch mốc "thời gian đã trôi" trên thanh nhịp mục tiêu ở đầu tab Tổng quan.
   Viền quanh vạch phải cùng màu NỀN TRANG để vạch tách khỏi phần đã tô — dùng
   lớp tiện ích giá trị tự do (`ring-[#f8fafc]`) thì phụ thuộc việc Tailwind bản
   CDN có dò ra lớp đó hay không, đúng chỗ đã làm dải phân trang lệch. */
.k-pace-mark {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 16px;
  border-radius: 9999px;
  transform: translate(-50%, -50%);
  background: #64748b;
  box-shadow: 0 0 0 2px var(--k-canvas);
}
.dark .k-pace-mark { background: #cbd5e1; box-shadow: 0 0 0 2px #0b1324; }
