/* Stok Habibti (Sistem Karlas) v1.1 design system.
   Habibti's studio colours on a quiet working surface: neutral warm-grey ground, white cards,
   espresso ink and buttons, ONE cocoa accent (current page, links, focus, selection).
   System sans with tabular figures; the Habibti wordmark in Gilda Display (self-hosted, OFL).
   Phone first (390), a persistent sidebar from 1024px, dark mode from the phone setting.
   No inline style anywhere (strict CSP): icons in CSS are data: masks, charts are SVG + classes. */

@font-face {
  font-family: "Gilda Display";
  src: url("/fonts/gilda-display-latin-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */
:root {
  color-scheme: light;
  --bg: #f4f2ef;
  --surface: #ffffff;
  --surface-2: #faf9f7;
  --surface-3: #efebe7;
  --ink: #2b211e;
  --ink-2: #66534b;
  --ink-3: #7d6d65;
  --line: #e6e1dc;
  --line-2: #d3cbc4;
  --accent: #8a5a44;
  --accent-strong: #714634;
  --accent-soft: #f4ebe6;
  --accent-ink: #6b4333;
  --ring: rgb(138 90 68 / 0.28);
  --primary: #2b211e;
  --primary-hover: #46352f;
  --on-primary: #fbfaf8;
  --ok: #2f6b4f;
  --ok-soft: #e5f1ea;
  --ok-line: #b9d9c6;
  --warn: #8a4b0f;
  --warn-soft: #fbefd6;
  --warn-line: #eccf98;
  --neg: #b42318;
  --neg-ink: #8f1c13;
  --neg-soft: #fcebe8;
  --neg-line: #f0b6ae;
  --info: #3b5366;
  --info-soft: #e8eef3;
  --teal: #1f5654;
  --teal-soft: #e1f0ef;
  --bar: #dcc8bd;
  --top-bg: rgb(244 242 239 / 0.86);
  --scrim: rgb(23 17 15 / 0.45);
  --shadow-1: 0 1px 2px rgb(43 33 30 / 0.05);
  --shadow-2: 0 6px 18px rgb(43 33 30 / 0.09), 0 1px 3px rgb(43 33 30 / 0.06);
  --shadow-3: 0 18px 48px rgb(43 33 30 / 0.2), 0 4px 12px rgb(43 33 30 / 0.08);
  --radius-sm: 10px;
  --radius: 12px;
  --radius-lg: 18px;
  --pill: 999px;
  --tap: 52px;
  --side-w: 248px;
  --top-h: 56px;
  /* v1.2: the bottom bar below 1024px, and the phone's safe areas (notch, home indicator). */
  --tab-h: 64px;
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-brand: "Gilda Display", "Didot", "Bodoni 72", Georgia, serif;
  /* CSS icons (masks: the shape only, filled with currentColor) */
  --i-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  --i-right: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.5 6l6 6-6 6'/%3E%3C/svg%3E");
  --i-left: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.5 6l-6 6 6 6'/%3E%3C/svg%3E");
  --i-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E");
  --i-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7.5v5.5M12 16.5h.01'/%3E%3C/svg%3E");
  --i-ok: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M8 12.3l2.8 2.8L16.2 9.5'/%3E%3C/svg%3E");
  --i-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 11v5.5M12 7.5h.01'/%3E%3C/svg%3E");
  --i-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237d6d65' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='10.8' cy='10.8' r='6.8'/%3E%3Cpath d='M20 20l-4.4-4.4'/%3E%3C/svg%3E");
  --i-chat: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 20l1.4-4.2A8 8 0 1 1 8.5 19z'/%3E%3C/svg%3E");
  --i-talian: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 8.8a14.5 14.5 0 0 1 19 0M5.5 12.2a10 10 0 0 1 13 0M8.6 15.5a5.4 5.4 0 0 1 6.8 0M12 19.5h.01M3.5 3.5l17 17'/%3E%3C/svg%3E");
  --i-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E");
}

/* ---------- Base ---------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  scrollbar-color: var(--line-2) transparent;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
::selection {
  background: var(--accent-soft);
  color: var(--ink);
}
input,
select,
textarea,
button {
  font: inherit;
  color: inherit;
}
input,
textarea {
  caret-color: var(--accent);
}
a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
a:hover {
  color: var(--accent-strong);
}
:focus-visible {
  outline: 2.5px solid var(--accent);
  outline-offset: 2px;
}
main:focus {
  outline: none;
}
img,
svg {
  max-width: 100%;
}
[hidden] {
  display: none !important;
}
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
.icon {
  width: 20px;
  height: 20px;
  flex: none;
}
.muted {
  color: var(--ink-2);
}
.small,
small {
  font-size: 14px;
}
.upper {
  text-transform: uppercase;
}
.num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* Without JS the picker falls back to a <select>; with JS the rich picker shows (app.js adds .js). */
.js-only {
  display: none;
}
html.js .js-only {
  display: block;
}
html.js .nojs-only {
  display: none;
}

h1 {
  font-size: 26px;
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 4px 0 14px;
  text-wrap: balance;
}
h2 {
  font-size: 19px;
  line-height: 1.25;
  font-weight: 650;
  letter-spacing: -0.005em;
  margin: 26px 0 10px;
  text-wrap: balance;
}
h3 {
  font-size: 15.5px;
  line-height: 1.3;
  font-weight: 650;
  margin: 18px 0 8px;
}
p {
  margin: 0 0 10px;
}

/* ---------- Shell ---------- */
.skip {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 100;
  padding: 10px 16px;
  border-radius: var(--pill);
  background: var(--primary);
  color: var(--on-primary);
  font-weight: 600;
  text-decoration: none;
}
.skip:focus {
  top: 12px;
}
.skip:hover,
.skip:focus {
  color: var(--on-primary);
}
.app {
  min-height: 100vh;
  min-height: 100dvh;
}
.main-col {
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}
main.wrap {
  flex: 1;
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: 18px 16px 56px;
  view-transition-name: page;
}

/* Top bar: menu, where you are, + Rekod. */
header.top {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--top-h);
  padding: 6px 12px 6px 6px;
  padding-top: max(6px, env(safe-area-inset-top));
  background: var(--top-bg);
  border-bottom: 1px solid var(--line);
  -webkit-user-select: none;
  user-select: none;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  header.top {
    -webkit-backdrop-filter: saturate(1.6) blur(14px);
    backdrop-filter: saturate(1.6) blur(14px);
  }
}
.top-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.2;
  padding-left: 4px;
}
.top-title .brand {
  font-size: 12.5px;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.top-title .brand strong {
  font-weight: 650;
  color: var(--ink);
}
.top-title .page-name {
  font-size: 17px;
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
body.keluar header.top {
  justify-content: center;
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
body.keluar .top-title {
  flex: none;
  text-align: center;
  padding: 0;
}
/* Final fix wave, lens 3 M13. Signed out on a phone (the sign-in page, with its "Sesi tamat" notice): the brand line
   scrolls away with the page, as the signed-in pages' header line does (body.masuk, below) and takes the same slim band.
   Sticky, it stayed at the top and drew over "PIN 6 nombor." once the page was scrolled; static alone does not give
   the height back (sticky and static both sit in the flow), the slim band does: the Masuk key then ends inside a
   360x740 first screen with the notice. */
@media (max-width: 1023.98px) {
  body.keluar header.top {
    position: static;
    min-height: calc(40px + var(--safe-t));
    padding: var(--safe-t) 16px 0;
  }
}
.icon-btn {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex: none;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 160ms ease, transform 140ms var(--ease-out);
}
.icon-btn .icon {
  width: 24px;
  height: 24px;
}
.icon-btn:hover {
  background: var(--surface-3);
}
.icon-btn:active {
  transform: scale(0.94);
}
.rekod-btn {
  flex: none;
}

/* ---------- v1.2: the phone shell (docs/spec-v1.2-shell.md, variant A "Tab tetap") ----------
   Below 1024px: a header line that scrolls away, and a bottom bar of five labelled tabs; Lagi opens
   the menu as a sheet. From 1024px: the v1.1 sidebar and top bar, unchanged. */
.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  /* The side insets matter on a phone on its side, where a notch takes one edge (review 1, Minor 7). */
  padding: 0 max(4px, var(--safe-r)) var(--safe-b) max(4px, var(--safe-l));
  /* Solid and on the element itself, with no blur: Safari 26 tints its toolbar from it (§5.3). */
  background: var(--surface);
  border-top: 1px solid var(--line);
  view-transition-name: tabbar;
  transition: transform 200ms var(--ease-out), visibility 0s linear 0s;
}
.tabbar ul {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}
.tabbar li {
  display: flex;
  min-width: 0;
}
.tab {
  position: relative;
  flex: 1;
  display: flex;
  justify-content: center;
  min-width: 0;
  min-height: var(--tab-h);
  padding: 7px 2px 5px;
  border-radius: 16px;
  color: var(--ink-2);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  text-decoration: none;
  -webkit-user-select: none;
  user-select: none;
}
.tab:hover {
  color: var(--ink-2);
}
.tab:focus-visible {
  outline-offset: -3px;
}
/* Icon and label; above the indicator pill, also while a page change draws them (view transitions). */
.tab-in {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
  max-width: 100%;
}
.tab-ind {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 32px;
  flex: none;
  border-radius: 16px;
  transition: transform 120ms var(--ease-out), background-color 140ms ease;
}
.tab-ind .icon {
  width: 24px;
  height: 24px;
}
/* At 200% text a label wraps to two lines and the bar grows; it is never clipped (§3). */
.tab-label {
  max-width: 100%;
  overflow-wrap: anywhere;
}
/* The current tab's indicator. It is its own element so a page change can glide it (§7). */
.tab-pill {
  position: absolute;
  top: 7px;
  left: 50%;
  width: 56px;
  height: 32px;
  margin-left: -28px;
  border-radius: 16px;
  background: var(--accent-soft);
  transition: transform 120ms var(--ease-out);
  view-transition-name: tab-current;
}
/* Current: colour, weight and the pill's shape carry it, not colour alone. Lagi looks current while its sheet is open. */
.tab[aria-current="page"],
.tab[aria-expanded="true"] {
  color: var(--accent-ink);
  font-weight: 700;
}
.tab[aria-current="page"] .icon,
.tab[aria-expanded="true"] .icon {
  color: var(--accent);
}
.tab[aria-expanded="true"] .tab-ind {
  background: var(--accent-soft);
}
.tab:active .tab-ind,
.tab:active .tab-pill {
  transform: scale(0.92);
}
.tab:not([aria-current]):not([aria-expanded="true"]):active .tab-ind {
  background: var(--surface-3);
}
@media (hover: hover) and (pointer: fine) {
  .tab:not([aria-current]):not([aria-expanded="true"]):hover .tab-ind {
    background: var(--surface-3);
  }
}
/* A badge only where there is something to do (Baki, Pindahan); its digits are aria-hidden (§1.4). */
.tab-badge {
  position: absolute;
  top: -6px;
  left: calc(50% + 6px);
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  font-size: 12px;
  font-weight: 700;
  line-height: 20px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--surface);
}
.tab-badge.warn {
  background: var(--warn-soft);
  color: var(--warn);
}
.tab-badge.accent {
  background: var(--accent);
  color: var(--on-primary);
}
/* The on-screen keyboard is up (shell.js, html.kb): the bar slides away and never covers a field (§5.1). */
html.kb .tabbar {
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 160ms var(--ease-out), visibility 0s linear 160ms;
}
/* A page change: the bar stays still while the page fades; the indicator glides; labels cross-fade (§7). */
.tabbar li:nth-child(1) .tab-in {
  view-transition-name: tab-1;
}
.tabbar li:nth-child(2) .tab-in {
  view-transition-name: tab-2;
}
.tabbar li:nth-child(3) .tab-in {
  view-transition-name: tab-3;
}
.tabbar li:nth-child(4) .tab-in {
  view-transition-name: tab-4;
}
.tabbar li:nth-child(5) .tab-in {
  view-transition-name: tab-5;
}
@media (max-width: 1023.98px) {
  /* The header line: the brand and the page name on one line, not sticky. Lagi and Rekod are in the bar. */
  body.masuk header.top {
    position: static;
    min-height: calc(40px + var(--safe-t));
    padding: var(--safe-t) 16px 0;
    background: none;
    border-bottom: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  body.masuk .top-title {
    flex-direction: row;
    align-items: center;
    padding: 0;
  }
  body.masuk .top-title .brand {
    flex: none;
    max-width: 70%;
    font-size: 13px;
  }
  body.masuk .top-title .page-name {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-2);
  }
  body.masuk .top-title .page-name[data-fallback] {
    display: none;
  }
  body.masuk .top-title .page-name::before {
    content: "";
    display: inline-block;
    width: 14px;
    height: 14px;
    margin: 0 4px 0 6px;
    vertical-align: -2px;
    background: var(--ink-3);
    -webkit-mask: var(--i-right) center / 14px no-repeat;
    mask: var(--i-right) center / 14px no-repeat;
  }
  body.masuk .rekod-btn {
    display: none;
  }
  /* A control scrolled into view (Tab, focus, a jump) stops above the bar, and above Rekod's Simpan bar
     while it shows: focus is never hidden under them (WCAG 2.2 SC 2.4.11; review 1, Minor 6). */
  html:has(body.masuk) {
    scroll-padding-bottom: calc(var(--tab-h) + var(--safe-b) + 8px);
  }
  html:has(#simpan-bar:not([hidden])) {
    scroll-padding-bottom: calc(var(--tab-h) + var(--safe-b) + 8px + 88px);
  }
  /* The last thing on every page clears the bar (§5.2). */
  body.masuk main.wrap {
    padding-top: 6px;
    padding-bottom: calc(var(--tab-h) + var(--safe-b) + 28px);
  }
  /* Rekod's Simpan bar docks 10px above the bar's hairline. With the keyboard up (the bar is hidden) it
     goes back to the bottom edge, where the keyboard covers it, as in v1.1 (spec §5.2): the keyboard's
     Go key submits. The page keeps its room either way, so the form never jumps. */
  body.masuk .simpan-bar {
    bottom: calc(var(--tab-h) + var(--safe-b));
    padding-bottom: 10px;
  }
  html.kb body.masuk .simpan-bar {
    bottom: 0;
  }
  body.masuk .update-bar {
    bottom: calc(var(--tab-h) + 8px + var(--safe-b));
  }
  body.masuk .talian {
    bottom: calc(var(--tab-h) + var(--safe-b));
  }
  html.luar-talian body.masuk main.wrap {
    padding-bottom: calc(var(--tab-h) + var(--safe-b) + 28px + 44px);
  }
  /* Toasts stay at the top, under the header line, above the keyboard. */
  body.masuk .toasts {
    top: calc(48px + var(--safe-t));
  }
}
/* 600 to 1023px (tablets, a laptop at 150%): icon beside label, the bar at most 640px wide (§6). */
@media (min-width: 600px) and (max-width: 1023.98px) {
  .tabbar ul {
    max-width: 640px;
    margin: 0 auto;
  }
}
/* A phone on its side: a compact 52px bar (§5.3). (A portrait phone at 200% zoom is also under 500px tall.) */
@media (max-width: 1023.98px) and (max-height: 499.98px) and (orientation: landscape) {
  :root {
    --tab-h: 52px;
  }
}
@media (min-width: 600px) and (max-width: 1023.98px), (max-width: 1023.98px) and (max-height: 499.98px) and (orientation: landscape) {
  .tab {
    align-items: center;
    padding: 4px;
  }
  .tab-in {
    flex-direction: row;
    gap: 6px;
  }
  .tab-ind {
    width: 40px;
  }
  .tab-pill {
    display: none;
  }
  .tab[aria-current="page"] .tab-ind {
    background: var(--accent-soft);
  }
}

/* The menu: the persistent sidebar from 1024px; below it, the Lagi sheet rising from the bottom
   (v1.2 §4). One markup: the sheet leaves out what the bottom bar already holds ([data-bar]). */
.sidebar {
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  -webkit-user-select: none;
  user-select: none;
}
.side-grab {
  display: none;
}
/* The scrim has no colour while it is hidden: Safari 26 samples hidden fixed elements for its
   toolbar tint (v1.2 §5.3). The colour leaves only after the fade-out. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 45;
  background-color: transparent;
  opacity: 0;
  visibility: hidden;
  transition: opacity 220ms ease, visibility 0s linear 220ms, background-color 0s linear 220ms;
}
html[data-menu="open"] .scrim,
html:not(.shell-js) .sidebar:target + .scrim {
  background-color: var(--scrim);
  opacity: 1;
  visibility: visible;
  transition: opacity 260ms ease, visibility 0s, background-color 0s;
}
html[data-menu="open"] {
  overflow: hidden;
}
/* Pasang app without JavaScript (a <noscript> note in the menu's foot): how to install from the browser. */
.pasang-nojs summary {
  list-style: none;
  cursor: pointer;
}
.pasang-nojs summary::-webkit-details-marker {
  display: none;
}
.pasang-nojs p {
  margin: 4px 10px 10px;
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink-2);
}
.side-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 10px 2px 22px;
}
.side-brand {
  display: flex;
  flex-direction: column;
  color: var(--ink);
  text-decoration: none;
  padding-top: 2px;
}
.side-brand:hover {
  color: var(--ink);
}
.side-brand .mark,
.signin-brand .mark {
  font-family: var(--font-brand);
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 1;
}
.side-brand .mark {
  font-size: 31px;
}
.side-brand .sys {
  margin-top: 5px;
  font-size: 12.5px;
  color: var(--ink-2);
}
.side-nav {
  flex: 1;
  padding: 2px 12px 8px;
}
.nav-sec {
  margin-top: 10px;
}
.nav-group {
  margin: 0 0 4px;
  padding: 0 10px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-3);
}
.nav-sec ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}
.nav-end {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 44px;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font-size: 15.5px;
  font-weight: 550;
  line-height: 1.2;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 140ms ease, color 140ms ease;
}
.nav-item .icon {
  color: var(--ink-2);
}
.nav-item:hover {
  background: var(--surface-3);
  color: var(--ink);
}
.nav-item[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 650;
}
.nav-item[aria-current="page"] .icon {
  color: var(--accent);
}
.nav-badge {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  padding: 3px 8px;
  border-radius: var(--pill);
  font-size: 12.5px;
  font-weight: 700;
  line-height: 16px;
}
.nav-badge.warn {
  background: var(--warn-soft);
  color: var(--warn);
}
.nav-badge.accent {
  background: var(--accent);
  color: var(--on-primary);
}
.nav-badge.quiet {
  background: var(--surface-3);
  color: var(--ink-2);
}
.nav-item .nav-ext {
  width: 16px;
  height: 16px;
  margin-left: auto;
  color: var(--ink-3);
}
.nav-status {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0 0;
  padding: 0 10px 0 42px;
  font-size: 13.5px;
  color: var(--ink-2);
}
.live-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px var(--ok-soft);
}
.side-foot {
  padding: 8px 12px 10px;
  border-top: 1px solid var(--line);
}
.install-link {
  margin-bottom: 6px;
}
.who {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 2px 0 6px;
}
.who-icon {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--ink-2);
}
.who-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.who-text small {
  font-size: 12.5px;
  color: var(--ink-3);
}
.who-text b {
  font-size: 15px;
}
.whoami {
  margin: 0;
}
/* The owner's Keluar carries "Matikan notifikasi pemilik di telefon ini" (shown on a phone with
   push): one compact line under who is signed in; Keluar stays on the first row on a phone. */
.who.has-notif {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto;
  grid-template-areas: "icon text out" "notif notif notif";
  align-items: center;
  column-gap: 10px;
}
.who.has-notif .who-icon {
  grid-area: icon;
}
.who.has-notif .who-text {
  grid-area: text;
}
.who.has-notif .whoami {
  display: contents;
}
.who.has-notif .btn.keluar {
  grid-area: out;
}
label.keluar-notif {
  grid-area: notif;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink-2);
  cursor: pointer;
}
label.keluar-notif[hidden] {
  display: none;
}
label.keluar-notif input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--accent);
}
/* Below 1024px the menu is the Lagi sheet (after the sidebar's own rules, which it adjusts). */
@media (max-width: 1023.98px) {
  .sidebar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    max-width: 640px;
    max-height: calc(100vh - var(--safe-t) - 8px);
    max-height: calc(100dvh - var(--safe-t) - 8px);
    margin: 0 auto;
    padding-bottom: var(--safe-b);
    border-radius: 24px 24px 0 0;
    overflow: hidden;
    transform: translateY(102%);
    visibility: hidden;
    transition: transform 220ms var(--ease-drawer), visibility 0s linear 220ms;
  }
  /* Open: by shell.js, or by the #menu link (:target) only while shell.js has not run yet (a slow line).
     Once it runs, html has the class shell-js and shell.js alone opens and closes the sheet. */
  html[data-menu="open"] .sidebar,
  html:not(.shell-js) .sidebar:target {
    transform: none;
    visibility: visible;
    box-shadow: var(--shadow-3);
    transition: transform 300ms var(--ease-drawer), visibility 0s;
  }
  .sidebar.dragging {
    transition: none;
  }
  /* The handle and the head take a swipe down; the list scrolls inside, and the foot stays put. */
  .side-grab {
    display: block;
    flex: none;
    width: 40px;
    height: 5px;
    margin: 8px auto 0;
    border-radius: 3px;
    background: var(--line-2);
    touch-action: none;
  }
  .side-head {
    flex: none;
    margin: 0 12px;
    padding: 4px 0 10px 10px;
    border-bottom: 1px solid var(--line);
    touch-action: none;
  }
  .side-close {
    width: 48px;
    height: 48px;
  }
  .side-nav {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    touch-action: pan-y;
  }
  .side-foot {
    flex: none;
    touch-action: none;
  }
  .nav-item {
    min-height: 48px;
  }
  .side-nav [data-bar] {
    display: none;
  }
}

/* ---------- Buttons ---------- */
.btn,
button.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding: 10px 18px;
  border: 1px solid var(--line-2);
  border-radius: var(--pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: transform 140ms var(--ease-out), background-color 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
}
.btn:hover {
  border-color: var(--ink-3);
  color: var(--ink);
}
.btn:active {
  transform: scale(0.97);
}
.btn.primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}
.btn.primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  color: var(--on-primary);
}
.btn.danger {
  background: var(--neg);
  border-color: var(--neg);
  color: #fff;
}
.btn.danger:hover {
  background: var(--neg-ink);
  border-color: var(--neg-ink);
  color: #fff;
}
.btn.danger-outline {
  background: var(--surface);
  border-color: var(--neg-line);
  color: var(--neg);
}
.btn.ghost {
  background: transparent;
  border-color: transparent;
}
.btn.ghost:hover {
  background: var(--surface-3);
}
.btn.small {
  min-height: 44px;
  padding: 8px 14px;
  font-size: 15px;
}
.btn.big {
  width: 100%;
  min-height: 56px;
  margin: 8px 0;
  font-size: 18px;
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.55;
  cursor: default;
  transform: none;
}
/* Busy: the press was heard (shell.js). */
.btn.is-busy {
  pointer-events: none;
  opacity: 0.8;
}
.btn.is-busy::after {
  content: "";
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: spin 700ms linear infinite;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
.linkish {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 2px;
  border: 0;
  background: none;
  color: var(--accent);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}
.linkish.small {
  font-size: 14.5px;
}
.row-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 12px 0 0;
}
.row-links .btn {
  flex: 1 1 140px;
}
.back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 44px;
  margin: -6px 0 2px -4px;
  padding: 0 6px 0 2px;
  border-radius: var(--radius-sm);
  font-weight: 600;
  text-decoration: none;
}
.back::before {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: currentColor;
  -webkit-mask: var(--i-left) center / 20px no-repeat;
  mask: var(--i-left) center / 20px no-repeat;
}

/* ---------- Alerts and toasts ---------- */
.alert {
  position: relative;
  margin: 12px 0 16px;
  padding: 12px 14px 12px 44px;
  border: 1px solid;
  border-radius: var(--radius);
  font-size: 15.5px;
  line-height: 1.45;
}
.alert::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 13px;
  width: 20px;
  height: 20px;
  background: currentColor;
  -webkit-mask: var(--i-alert) center / 20px no-repeat;
  mask: var(--i-alert) center / 20px no-repeat;
}
.alert.err {
  background: var(--neg-soft);
  border-color: var(--neg-line);
  color: var(--neg-ink);
}
.alert.ok {
  background: var(--ok-soft);
  border-color: var(--ok-line);
  color: var(--ok);
}
.alert.ok::before {
  -webkit-mask-image: var(--i-ok);
  mask-image: var(--i-ok);
}
.alert.info {
  background: var(--warn-soft);
  border-color: var(--warn-line);
  color: #5c3a0c;
}
.alert.info::before {
  -webkit-mask-image: var(--i-info);
  mask-image: var(--i-info);
}
.alert p {
  margin: 0;
}
.alert ul {
  margin: 6px 0 0;
  padding-left: 20px;
}
.alert a {
  color: inherit;
  font-weight: 700;
}
.alert .linkish {
  color: inherit;
}

/* Amaran: the question the server asks before saving (Rekod, Pindahan baharu). It is the first thing
   in the form, under the title, so it is on screen when the page comes back. The alert keeps the "!"
   icon (the plain info alert has an "i"); Kembali is the filled button on top, Simpan juga the outlined
   one under it. */
.amaran {
  margin: 0 0 18px;
}
.amaran .alert {
  margin: 0 0 10px;
}
.amaran .alert::before {
  -webkit-mask-image: var(--i-alert);
  mask-image: var(--i-alert);
}
.amaran .alert p + p {
  margin-top: 8px;
}
.amaran-actions {
  display: grid;
  gap: 8px;
}
.amaran-actions .btn.big {
  margin: 0;
}

/* v1.2 §5.5: no line. A strip on top of the bar (shell.js), in place of a toast at the top; on Rekod with
   a record ready the words sit in the Simpan bar instead (.talian-simpan), so only one shows. */
.talian {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 39;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 8px max(16px, var(--safe-r)) 8px max(16px, var(--safe-l));
  background: var(--warn-soft);
  border-top: 1px solid var(--warn-line);
  color: #5c3a0c;
  font-size: 14px;
  font-weight: 650;
  line-height: 1.3;
}
.talian::before {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: currentColor;
  -webkit-mask: var(--i-talian) center / 20px no-repeat;
  mask: var(--i-talian) center / 20px no-repeat;
}
.talian.kembali {
  background: var(--ok-soft);
  border-top-color: var(--ok-line);
  color: var(--ok);
}
.talian.kembali::before {
  -webkit-mask-image: var(--i-ok);
  mask-image: var(--i-ok);
}
.simpan-bar .talian-simpan.info::before {
  -webkit-mask-image: var(--i-talian);
  mask-image: var(--i-talian);
}
html.kb .talian,
html:has(#simpan-bar:not([hidden])) .talian {
  display: none;
}

/* v1.2 §14.5: the leave guard on Rekod. One question in the middle of the screen, over the sheet and
   the bar (a modal <dialog>, shell.js); Tidak, the safe answer, comes first and filled. */
dialog.tanya {
  width: min(340px, calc(100vw - 32px));
  margin: auto;
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-3);
}
dialog.tanya[open] {
  animation: tanya-in 200ms var(--ease-out) both;
}
dialog.tanya::backdrop {
  background: var(--scrim);
}
.tanya-in {
  padding: 20px 18px 16px;
}
@keyframes tanya-in {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
}
.tanya-text {
  margin: 0 0 16px;
  font-size: 17px;
  font-weight: 650;
  line-height: 1.35;
  text-wrap: balance;
}
.tanya-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.tanya-actions .btn {
  min-height: 48px;
}

.toasts {
  position: fixed;
  z-index: 70;
  left: 12px;
  right: 12px;
  top: calc(var(--top-h) + 8px + env(safe-area-inset-top));
  display: grid;
  justify-items: center;
  gap: 8px;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 440px;
  margin: 0;
  padding: 10px 6px 10px 14px;
  border-radius: 14px;
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--shadow-3);
  font-weight: 600;
  font-size: 15.5px;
  line-height: 1.35;
  opacity: 1;
  transform: none;
  transition: opacity 240ms var(--ease-out), transform 300ms var(--ease-out);
}
@starting-style {
  .toast {
    opacity: 0;
    transform: translateY(-10px) scale(0.98);
  }
}
.toast::before {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: #9fd3b6;
  -webkit-mask: var(--i-ok) center / 20px no-repeat;
  mask: var(--i-ok) center / 20px no-repeat;
}
.toast.err {
  background: var(--neg);
  color: #fff;
}
.toast.err::before {
  background: #fff;
  -webkit-mask-image: var(--i-alert);
  mask-image: var(--i-alert);
}
.toast-text {
  flex: 1;
}
.toast-close {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex: none;
  margin: -6px 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.toast-close::before {
  content: "";
  width: 16px;
  height: 16px;
  background: currentColor;
  opacity: 0.75;
  -webkit-mask: var(--i-x) center / 16px no-repeat;
  mask: var(--i-x) center / 16px no-repeat;
}
.toast.leaving {
  opacity: 0;
  transform: translateY(-6px);
  transition-duration: 160ms;
}

/* ---------- Cards, page heads, forms ---------- */
.card {
  margin: 14px 0;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.card > h2:first-child,
.card > h1:first-child {
  margin-top: 0;
}
.card.notice {
  max-width: 520px;
  margin: 24px auto;
  padding: 22px 20px;
}
.card.notice h1 {
  margin-top: 0;
}
details.card {
  padding: 0;
}
details.card > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 56px;
  padding: 12px 16px;
  font-weight: 650;
  cursor: pointer;
  list-style: none;
  border-radius: var(--radius-lg);
}
details.card > summary::-webkit-details-marker,
details > summary::-webkit-details-marker {
  display: none;
}
details.card > summary::after,
.new-colour > summary::after,
.warna-hex > summary::after,
.chart-table > summary::after,
.pdpa summary::after,
details.resend > summary::after {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: currentColor;
  opacity: 0.7;
  -webkit-mask: var(--i-down) center / 20px no-repeat;
  mask: var(--i-down) center / 20px no-repeat;
  transition: transform 200ms var(--ease-out);
}
details[open] > summary::after {
  transform: rotate(180deg);
}
details.card[open] > summary {
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
details.card > form,
details.card > div {
  padding: 14px 16px 16px;
}
.page-head {
  margin-bottom: 6px;
}
.page-head h1 {
  margin-bottom: 4px;
}
.page-head p {
  margin: 0;
}
.head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.head-row h1 {
  margin-bottom: 8px;
}
.head-row .btn.big {
  margin: 0 0 12px;
}
.card.notice .btn {
  margin-top: 6px;
}
.offline {
  text-align: center;
}
.offline-icon {
  display: block;
  width: 48px;
  height: 48px;
  margin: 4px auto 10px;
  color: var(--ink-3);
}

fieldset.step {
  min-width: 0;
  margin: 0 0 20px;
  padding: 0;
  border: 0;
}
fieldset.step > legend,
.sublabel {
  margin: 0 0 8px;
  padding: 0;
  font-size: 15.5px;
  font-weight: 650;
}
.field {
  margin: 0 0 18px;
}
.field > label,
form.stack > label,
.grid2 > label,
.grid3 > label,
.filters label,
.email-form label,
.copy-link label,
.batal-form label {
  display: block;
  margin-bottom: 6px;
  font-size: 15px;
  font-weight: 600;
}
.field > small {
  display: block;
  margin-top: 6px;
  font-size: 13.5px;
  line-height: 1.4;
}
.field > label small {
  font-size: 14px;
  font-weight: 450;
}
form.stack {
  display: grid;
  gap: 6px;
}
form.stack > label input,
form.stack > label select {
  margin-top: 6px;
}
form.inline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  margin: 0 0 8px;
}
input[type="text"],
input[type="search"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="tel"],
input[type="email"],
select,
textarea {
  display: block;
  width: 100%;
  min-height: var(--tap);
  padding: 12px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: 17px;
  line-height: 1.25;
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
input[type="date"] {
  -webkit-appearance: none;
  appearance: none;
}
select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237d6d65' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 20px;
}
input::placeholder,
textarea::placeholder {
  color: var(--ink-3);
  opacity: 1;
}
input:hover,
select:hover,
textarea:hover {
  border-color: var(--ink-3);
}
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--ring);
}
input[readonly] {
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 15px;
}
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  background: var(--ink-3);
  -webkit-mask: var(--i-x) center / 16px no-repeat;
  mask: var(--i-x) center / 16px no-repeat;
  cursor: pointer;
}
input.search,
input[type="search"] {
  padding-left: 44px;
  background-image: var(--i-search);
  background-repeat: no-repeat;
  background-position: 14px center;
  background-size: 20px;
}
.search {
  margin: 0 0 10px;
}
.grid2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.grid3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.grid4 {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
.align-end {
  align-items: end;
}

/* Choice tiles (radio and checkbox) */
label.opt {
  position: relative;
  display: block;
  cursor: pointer;
}
label.opt input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
label.opt > span {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100%;
  min-height: var(--tap);
  padding: 9px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface);
  font-weight: 600;
  line-height: 1.25;
  transition: border-color 140ms ease, background-color 140ms ease, box-shadow 140ms ease, transform 140ms var(--ease-out);
}
label.opt:hover > span {
  border-color: var(--ink-3);
}
label.opt:active > span {
  transform: scale(0.98);
}
label.opt.big > span {
  min-height: 68px;
  padding: 11px 34px 11px 14px;
}
label.opt > span small {
  margin-top: 3px;
  font-size: 13px;
  font-weight: 450;
  line-height: 1.35;
  color: var(--ink-2);
}
label.opt.big > span small {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
label.opt.big input:checked + span small {
  display: block;
  overflow: visible;
}
label.opt.small > span {
  min-height: 48px;
  padding: 7px 10px;
  font-size: 15px;
}
label.opt input:checked + span {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent);
  color: var(--accent-ink);
}
label.opt input:checked + span small {
  color: var(--accent-ink);
}
label.opt.big input:checked + span::after {
  content: "";
  position: absolute;
  top: 10px;
  right: 10px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
  -webkit-mask: none;
}
label.opt.big input:checked + span::before {
  content: "";
  position: absolute;
  z-index: 1;
  top: 13px;
  right: 13px;
  width: 14px;
  height: 14px;
  background: var(--on-primary);
  -webkit-mask: var(--i-check) center / 14px no-repeat;
  mask: var(--i-check) center / 14px no-repeat;
}
label.opt input:focus-visible + span {
  outline: 2.5px solid var(--accent);
  outline-offset: 2px;
}
label.opt input:disabled + span {
  opacity: 0.4;
  cursor: not-allowed;
}
/* Movement types carry a colour dot next to their word (never colour alone). */
label.opt > span b::before,
.badge[class*="t-"]::before,
.quick-chip[class*="t-"]::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 7px;
  border-radius: 50%;
  vertical-align: 0.08em;
  background: var(--dot, var(--ink-3));
}
label.opt:not([class*="t-"]) > span b::before {
  display: none;
}
.t-kiraan_awal {
  --dot: var(--info);
}
.t-stok_baru {
  --dot: var(--ok);
}
.t-pindah {
  --dot: #b7791f;
}
.t-jual {
  --dot: var(--accent);
}
.t-pulang {
  --dot: var(--teal);
}
.t-rosak {
  --dot: var(--neg);
}

/* ---------- Rekod ---------- */
.rekod-page,
.resit-page,
.tetapan-page,
.narrow {
  max-width: 680px;
  margin: 0 auto;
}
.rekod-page form {
  padding-bottom: 4px;
}
.design-list {
  display: grid;
  gap: 6px;
}
button.design {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: var(--tap);
  padding: 10px 12px 10px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: left;
  line-height: 1.3;
  cursor: pointer;
  transition: border-color 140ms ease, background-color 140ms ease, transform 140ms var(--ease-out);
}
button.design::after {
  content: "";
  width: 18px;
  height: 18px;
  margin-left: auto;
  flex: none;
  background: var(--ink-3);
  -webkit-mask: var(--i-right) center / 18px no-repeat;
  mask: var(--i-right) center / 18px no-repeat;
}
button.design:hover {
  border-color: var(--ink-3);
}
button.design:active {
  transform: scale(0.99);
  background: var(--surface-2);
}
button.design b {
  color: var(--accent);
  font-weight: 700;
  white-space: nowrap;
}
.chosen {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 14px;
  padding: 8px 8px 8px 14px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 650;
}
.chosen .linkish {
  flex: none;
  color: var(--accent-ink);
}
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}
button.chip {
  min-width: 56px;
  min-height: 48px;
  padding: 8px 16px;
  border: 1px solid var(--line-2);
  border-radius: var(--pill);
  background: var(--surface);
  font-weight: 600;
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, transform 140ms var(--ease-out);
}
button.chip:hover {
  border-color: var(--ink-3);
}
button.chip:active {
  transform: scale(0.96);
}
button.chip[aria-pressed="true"] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}
#chosen-sku {
  margin: -4px 0 0;
  font-size: 14px;
}
/* v1.2 Pilih cepat: the last two Jenis and places saved on this phone, one row that scrolls sideways
   from edge to edge on a phone (app.js draws it; JavaScript only). */
.quick {
  margin: -2px -16px 16px;
}
.quick .sublabel {
  margin: 0 16px 8px;
}
.quick-row {
  display: flex;
  gap: 8px;
  padding: 0 16px 2px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.quick-row::-webkit-scrollbar {
  display: none;
}
.quick-row .chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}
.stepper {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) 64px;
  gap: 8px;
  align-items: stretch;
}
.stepper input {
  min-height: 60px;
  text-align: center;
  font-size: 26px;
  font-weight: 700;
}
button.step-btn {
  min-height: 60px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 28px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background-color 140ms ease, transform 140ms var(--ease-out);
}
button.step-btn:active {
  transform: scale(0.95);
  background: var(--surface-3);
}
#price-hint.need {
  color: var(--neg);
  font-weight: 700;
}
.simpan-bar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  margin: 0 -16px;
  padding: 14px 16px calc(12px + env(safe-area-inset-bottom));
  background: linear-gradient(to top, var(--bg) 72%, rgb(244 242 239 / 0));
}
.simpan-bar .alert {
  margin: 0 0 8px;
  box-shadow: 0 3px 10px rgb(43 33 30 / 0.1);
}
.simpan-bar .btn.big {
  margin: 0;
  box-shadow: 0 4px 12px rgb(43 33 30 / 0.2);
}
.tarikh-note {
  font-weight: 650;
}
.by-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 4px 2px 8px;
  font-size: 15px;
}
.field.collapsed {
  display: none;
}
.fix-tip {
  margin: 12px 2px 0;
  font-size: 14px;
}

/* After saving */
.done .ok-title {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 2px 0 12px;
  color: var(--ok);
}
.done .ok-title::before {
  content: "";
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  background-color: var(--ok);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  background-size: 20px;
  background-repeat: no-repeat;
  background-position: center;
}
@media (prefers-reduced-motion: no-preference) {
  .done .ok-title::before {
    animation: pop 420ms var(--ease-out) both;
  }
}
@keyframes pop {
  from {
    transform: scale(0.6);
    opacity: 0;
  }
}
.done,
.produk-page {
  max-width: 680px;
  margin-left: auto;
  margin-right: auto;
}
.produk-page {
  max-width: 920px;
}
.done .summary,
.done .item {
  margin: 6px 0;
}
.done .summary b {
  font-size: 18px;
}
.baki-list {
  list-style: none;
  margin: 4px 0 14px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.baki-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 11px 14px;
  border-top: 1px solid var(--line);
  font-size: 17px;
}
.baki-list li:first-child {
  border-top: 0;
}
.baki-list b {
  font-size: 22px;
}
.kiraan-result b {
  font-size: 22px;
}
.sale-actions {
  display: grid;
  margin: 6px 0;
}
.sale-actions .btn.big {
  margin: 5px 0;
}

/* ---------- Badges and tags ---------- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: var(--pill);
  background: var(--surface-3);
  color: var(--ink);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}
.badge.t-kiraan_awal {
  background: var(--info-soft);
  color: var(--info);
}
.badge.t-stok_baru {
  background: var(--ok-soft);
  color: var(--ok);
}
.badge.t-pindah {
  background: var(--warn-soft);
  color: var(--warn);
}
.badge.t-jual {
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.badge.t-pulang {
  background: var(--teal-soft);
  color: var(--teal);
}
.badge.t-rosak {
  background: var(--neg-soft);
  color: var(--neg-ink);
}
.tag,
.tag-neg {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: var(--pill);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.5;
  vertical-align: middle;
  white-space: nowrap;
}
.tag {
  background: var(--surface-3);
  color: var(--ink-2);
}
.tag-neg {
  background: var(--neg-soft);
  color: var(--neg-ink);
}
.neg {
  color: var(--neg);
  font-weight: 700;
}

/* ---------- Baki ---------- */
.loc-tabs {
  display: flex;
  gap: 6px;
  margin: 0 -16px 12px;
  padding: 2px 16px 4px;
  overflow-x: auto;
  scrollbar-width: none;
}
.loc-tabs::-webkit-scrollbar {
  display: none;
}
.loc-tabs a {
  display: inline-flex;
  align-items: center;
  flex: none;
  min-height: 44px;
  padding: 6px 16px;
  border: 1px solid var(--line-2);
  border-radius: var(--pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 140ms ease, border-color 140ms ease;
}
.loc-tabs a:hover {
  border-color: var(--ink-3);
}
.loc-tabs a.on,
.loc-tabs a.on:hover {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}
.search-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  margin-bottom: 8px;
}
.search-row .search {
  margin: 0;
}
.search-row .btn {
  min-height: var(--tap);
}
.export-link {
  margin: 4px 0 12px;
  font-size: 15px;
}
.export-link a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-weight: 600;
}
.export-link a::before {
  content: "";
  width: 18px;
  height: 18px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19.5h14'/%3E%3C/svg%3E") center / 18px no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4v11M7.5 10.5 12 15l4.5-4.5M5 19.5h14'/%3E%3C/svg%3E") center / 18px no-repeat;
}
.design-group {
  margin: 0 0 12px;
  padding: 12px 4px 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.design-group h2 {
  margin: 0 8px 8px;
  font-size: 16px;
  line-height: 1.3;
}
.design-group h2 b {
  color: var(--accent);
}
.empty {
  padding: 28px 12px;
  text-align: center;
}

/* ---------- Tables ---------- */
.table-wrap {
  overflow-x: auto;
}
table.grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 15px;
}
table.grid th {
  padding: 4px 4px 7px;
  font-size: 12.5px;
  font-weight: 650;
  line-height: 1.15;
  color: var(--ink-2);
  text-align: left;
  vertical-align: bottom;
}
table.grid td {
  padding: 9px 4px;
  border-top: 1px solid var(--line);
  vertical-align: middle;
}
table.grid .num {
  text-align: right;
}
/* Baki fits a 390px phone: fixed layout, narrow Warna/Saiz, location columns share the rest.
   D103: Warna is 18% (it was 15%): its flag ("perlu semak · Metrocity") wraps under the colour name
   inside it, and 15% was too narrow for the flag's words. The headers are half a pixel smaller so
   "Metrocity" still fits its column at 390px. Saiz stays at 10% ("XXL" needs it). */
table.grid.baki {
  table-layout: fixed;
}
table.grid.baki col.c-warna {
  width: 18%;
}
table.grid.baki col.c-saiz {
  width: 10%;
}
table.grid.baki col.c-total {
  width: 13%;
}
/* One location tab: its single number column has room to spare, so Warna and Saiz take it. */
table.grid.baki.one-loc col.c-warna {
  width: 36%;
}
table.grid.baki.one-loc col.c-saiz {
  width: 14%;
}
table.grid.baki th {
  padding: 4px 1px 7px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}
table.grid.baki td {
  padding: 9px 2px;
  overflow-wrap: anywhere;
}
table.grid.baki td:first-child {
  font-size: 13px;
  line-height: 1.2;
}
/* A flag in the Warna cell ("perlu semak", "tidak aktif") takes its own line under the colour and
   wraps inside the cell, which grows to hold it. The cell is narrow, so a flag that stayed one nowrap
   line spilled into the Saiz cell (D103). It breaks between words; only a word wider than the cell
   itself breaks inside, so nothing ever leaves the cell. */
table.grid.baki td:first-child .tag,
table.grid.baki td:first-child .tag-neg {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 3px 0 0;
  padding: 1px 8px;
  border-radius: 9px;
  font-size: 12px;
  line-height: 1.3;
  white-space: normal;
  overflow-wrap: anywhere;
}
/* All places on a phone: Warna is a fifth of the table, so the flag is compact there. */
@media (max-width: 559.98px) {
  table.grid.baki:not(.one-loc) td:first-child .tag,
  table.grid.baki:not(.one-loc) td:first-child .tag-neg {
    padding: 1px 3px;
    border-radius: 8px;
    font-size: 10px;
  }
}
table.grid td.zero {
  color: var(--ink-3);
}
table.grid td.total {
  font-weight: 700;
}
table.grid tr.inactive td {
  color: var(--ink-2);
}
table.grid input {
  min-height: 44px;
  padding: 6px 8px;
  font-size: 16px;
}
/* Produk variants: harga normal over harga selepas less in one column (fits a 360px phone). */
table.grid th.price span,
table.grid td.price span {
  display: block;
}
table.grid th.price span {
  white-space: nowrap;
}
table.grid th.price,
table.grid td.price {
  padding-right: 8px;
}
table.grid td.price .np {
  font-size: 13px;
  line-height: 1.2;
  color: var(--ink-2);
}
table.grid td.price .np.was {
  text-decoration: line-through;
}
table.grid td.price .sell {
  font-weight: 700;
}
table.prices td:first-child {
  font-size: 14px;
}
table.prices tr.all-row td {
  background: var(--accent-soft);
}
/* The Booth view: why an item's Dijangka differs from its records (rosak, kiraan). */
table.grid td .row-note {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  line-height: 1.25;
  color: var(--ink-2);
}

/* ---------- Sejarah ---------- */
.filters {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 10px;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.filters label:first-child {
  grid-column: 1 / -1;
}
.filters .filter-actions {
  grid-column: 1 / -1;
  display: flex;
  gap: 8px;
}
.filters .filter-actions .btn {
  flex: 1;
}
.mv-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.mv {
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
  scroll-margin-top: calc(var(--top-h) + 12px);
}
.mv:target {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--ring);
}
.mv-top {
  display: flex;
  align-items: center;
  gap: 10px;
}
.mv-top .muted {
  margin-left: auto;
  font-size: 14px;
}
.mv-qty {
  font-size: 18px;
  font-weight: 800;
}
.mv-item {
  margin: 6px 0 2px;
}
.mv-item b {
  color: var(--accent);
}
.mv-loc,
.mv-note {
  font-size: 15px;
}
.mv-loc {
  color: var(--ink-2);
}
.mv-note {
  font-style: italic;
}
.mv-by {
  margin-top: 4px;
  font-size: 13px;
}
.mv.voided {
  background: var(--surface-2);
}
.mv.voided .mv-body {
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  color: var(--ink-2);
}
.mv.voided .mv-body b,
.mv.voided .badge {
  color: var(--ink-2);
}
.void-note {
  margin-top: 8px;
  font-size: 14px;
  font-weight: 650;
  color: var(--neg);
}
details.batal {
  margin-top: 10px;
}
details.batal > summary {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 16px;
  border: 1px solid var(--neg-line);
  border-radius: var(--pill);
  color: var(--neg);
  font-weight: 700;
  list-style: none;
  cursor: pointer;
}
details.batal[open] > summary {
  background: var(--neg-soft);
}
.batal-form {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}
.resit-link,
.pindahan-link {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: 44px;
  margin-top: 2px;
  font-size: 15px;
  font-weight: 700;
}
.resit-link::after,
.pindahan-link::after {
  content: "";
  width: 16px;
  height: 16px;
  background: currentColor;
  -webkit-mask: var(--i-right) center / 16px no-repeat;
  mask: var(--i-right) center / 16px no-repeat;
}

/* ---------- Produk ---------- */
.prod-list,
.cust-list {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.prod a,
.cust a,
.cust .cust-static {
  position: relative;
  display: block;
  padding: 13px 40px 13px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  color: var(--ink);
  text-decoration: none;
  box-shadow: var(--shadow-1);
  transition: border-color 140ms ease, transform 140ms var(--ease-out);
}
/* D105: a staff row is not a link, so it has no chevron and no right gutter for one. */
.cust .cust-static {
  padding-right: 14px;
}
.prod a::after,
.cust a::after {
  content: "";
  position: absolute;
  right: 12px;
  top: 50%;
  width: 20px;
  height: 20px;
  margin-top: -10px;
  background: var(--ink-3);
  -webkit-mask: var(--i-right) center / 20px no-repeat;
  mask: var(--i-right) center / 20px no-repeat;
}
.prod a:hover,
.cust a:hover {
  border-color: var(--line-2);
  color: var(--ink);
}
.prod a:active,
.cust a:active {
  transform: scale(0.99);
}
.prod .prod-name {
  display: block;
  font-weight: 600;
}
.prod .prod-name b {
  color: var(--accent);
}
.prod .prod-meta {
  display: block;
  margin-top: 2px;
  font-size: 15px;
  color: var(--ink-2);
}
.prod .prod-foto {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 13.5px;
  color: var(--ink-2);
}
.prod .prod-foto.none {
  color: var(--warn);
  font-weight: 600;
}
.prod .prod-foto.none::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}
.prod.inactive a {
  background: var(--surface-2);
  opacity: 0.75;
}
.new-colour {
  margin-top: 10px;
}
.new-colour > summary,
.warna-hex > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.new-colour .grid2 {
  margin-top: 8px;
}
.danger-zone {
  display: grid;
  gap: 8px;
  margin: 26px 0 6px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}
.loc-edit {
  list-style: none;
  margin: 0 0 8px;
  padding: 0;
}

/* Gambar */
.gambar-section {
  scroll-margin-top: calc(var(--top-h) + 12px);
}
.gambar-grid {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.gambar {
  position: relative;
  display: grid;
  align-content: start;
  gap: 8px;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.gambar .thumb {
  display: block;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: 12px;
  background: var(--surface-3);
}
.gambar .thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.gambar .badge.utama {
  position: absolute;
  top: 14px;
  left: 14px;
  background: var(--primary);
  color: var(--on-primary);
}
.gambar form {
  display: grid;
  gap: 6px;
  margin: 0;
}
.gambar select {
  min-height: 44px;
  padding: 6px 34px 6px 10px;
  font-size: 15px;
}
.gambar .btn {
  width: 100%;
  min-height: 44px;
  padding: 6px 10px;
  font-size: 14px;
}
.gambar details.batal {
  margin-top: 0;
}
.gambar details.batal > summary {
  justify-content: center;
  width: 100%;
}
.gambar-upload {
  display: grid;
  gap: 6px;
  margin: 4px 0 12px;
}
.gambar-upload .btn.big {
  margin: 0;
}
.gambar-upload input[type="file"] {
  font-size: 15px;
}
html.js .gambar-upload input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  overflow: hidden;
}
.gambar-upload.busy label.btn {
  opacity: 0.6;
  pointer-events: none;
}
#gambar-msg {
  margin: 0;
}
#gambar-msg.err {
  color: var(--neg);
  font-weight: 700;
}
.katalog-toggle {
  display: grid;
  gap: 8px;
  margin: 4px 0 18px;
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.katalog-toggle p {
  margin: 0;
}

/* ---------- Papan pemuka ---------- */
.dash {
  container: dash / inline-size;
}
.dash-grid {
  display: grid;
  gap: 12px;
  margin-top: 14px;
}
.tile {
  container: tile / inline-size;
  min-width: 0;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.tile-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: -4px 0 10px;
}
.tile-head h2 {
  margin: 0;
  font-size: 17px;
}
.tile-link {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-height: 44px;
  padding: 0 0 0 6px;
  font-size: 14.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.tile-link::after {
  content: "";
  width: 18px;
  height: 18px;
  background: currentColor;
  -webkit-mask: var(--i-right) center / 18px no-repeat;
  mask: var(--i-right) center / 18px no-repeat;
}
.tile h3 {
  margin: 18px 0 6px;
  font-size: 14.5px;
  color: var(--ink-2);
}
.ledger {
  width: 100%;
  border-collapse: collapse;
}
.ledger th,
.ledger td {
  padding: 10px 0;
  border-top: 1px solid var(--line);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ledger thead th {
  padding-top: 0;
  border-top: 0;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink-2);
  vertical-align: bottom;
}
.ledger thead th small {
  display: block;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ink-3);
}
.ledger tbody th {
  text-align: left;
  font-weight: 550;
  color: var(--ink-2);
  font-size: 15px;
}
.ledger td {
  width: 32%;
  font-weight: 650;
  font-size: 16px;
  padding-left: 8px;
}
.ledger tr.lead td {
  font-size: 19px;
  font-weight: 750;
  letter-spacing: -0.01em;
  color: var(--ink);
}
.ledger td.need-cost {
  font-size: 14px;
  font-weight: 650;
  color: var(--accent);
  text-align: left;
}
.notes {
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 4px;
  font-size: 13.5px;
  color: var(--ink-2);
}
.trend-sum {
  margin: 0 0 8px;
}
.trend-sum b {
  font-size: 22px;
  letter-spacing: -0.01em;
}
.chart {
  display: block;
  width: 100%;
  height: 132px;
  overflow: visible;
}
.chart .bar {
  fill: var(--bar);
}
.chart .bar.today {
  fill: var(--accent);
}
.chart .bar.none {
  fill: var(--line-2);
}
.chart .chart-base {
  stroke: var(--line-2);
  stroke-width: 1px;
}
.chart-axis {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  font-size: 12.5px;
  color: var(--ink-3);
}
.chart-axis .peak {
  color: var(--ink-2);
  text-align: center;
}
.chart-table {
  margin-top: 8px;
}
.chart-table > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: var(--accent);
  font-weight: 600;
  font-size: 14.5px;
  cursor: pointer;
  list-style: none;
}
.hbar {
  display: block;
  width: 100%;
  height: 8px;
  border-radius: 4px;
  background: var(--surface-3);
  overflow: hidden;
}
.hbar path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 8px;
  stroke-linecap: round;
}
.rank {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: r;
}
.rank li a,
.locs li a {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name num" "bar bar";
  align-items: center;
  gap: 6px 12px;
  min-height: 44px;
  padding: 8px 0;
  border-top: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
}
.rank li:first-child a {
  border-top: 0;
}
.rank li a:hover .rank-name b,
.locs li a:hover .loc-name {
  text-decoration: underline;
  text-underline-offset: 3px;
}
.rank-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
}
@container tile (max-width: 459px) {
  .rank-name {
    white-space: normal;
  }
}
.rank-name::before {
  counter-increment: r;
  content: counter(r) ".";
  display: inline-block;
  min-width: 1.4em;
  color: var(--ink-3);
  font-weight: 700;
}
.rank-name b,
.item-name b {
  color: var(--accent);
}
.rank .num {
  font-size: 14.5px;
  font-weight: 650;
}
.kv,
.item-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.kv li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 44px;
  padding: 6px 0;
  border-top: 1px solid var(--line);
}
.kv li:first-child {
  border-top: 0;
}
.kv li > span {
  color: var(--ink-2);
  font-size: 15px;
}
.kv b {
  font-size: 17px;
}
.kv.locs li {
  display: block;
  padding: 0;
  border-top: 0;
}
.rank-name,
.loc-name {
  grid-area: name;
}
.rank li a > .num,
.locs li a > .num {
  grid-area: num;
}
.rank .hbar,
.locs .hbar {
  grid-area: bar;
}
@container tile (min-width: 460px) {
  .rank li a,
  .locs li a {
    grid-template-columns: minmax(0, 1.4fr) minmax(64px, 1fr) 4.5em;
    grid-template-areas: "name bar num";
    gap: 12px;
  }
}
.locs li:first-child a {
  border-top: 0;
}
.loc-name {
  font-size: 15px;
}
.locs .num {
  font-size: 17px;
}
.kv li.sum {
  display: flex;
  min-height: 48px;
  padding: 8px 0;
  border-top: 2px solid var(--line-2);
  font-weight: 750;
}
.kv li.sum span {
  color: var(--ink);
}
.kv li.sum b {
  font-size: 19px;
}
.stock-value {
  margin-top: 6px;
  padding-top: 2px;
}
.stock-value .figure {
  margin: 0 0 2px;
  font-size: 22px;
  font-weight: 750;
  letter-spacing: -0.01em;
}
.need-cost {
  margin: 4px 0;
  color: var(--accent);
  font-weight: 650;
}
.attn {
  display: grid;
  gap: 8px;
}
.attn-row {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  scroll-margin-top: 12px;
}
.attn-row > summary,
.attn-row.empty-row,
.attn-link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 8px 12px 8px 14px;
}
.attn-row > summary {
  list-style: none;
  cursor: pointer;
}
.attn-row > summary::after {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: var(--ink-3);
  -webkit-mask: var(--i-down) center / 20px no-repeat;
  mask: var(--i-down) center / 20px no-repeat;
  transition: transform 200ms var(--ease-out);
}
.attn-row[open] > summary::after {
  transform: rotate(180deg);
}
.attn-title {
  flex: 1;
  font-weight: 650;
  font-size: 15.5px;
}
.attn-count {
  min-width: 32px;
  padding: 2px 10px;
  border-radius: var(--pill);
  background: var(--surface-3);
  font-weight: 750;
  font-size: 14px;
  text-align: center;
}
.attn-row.tone-neg .attn-count {
  background: var(--neg-soft);
  color: var(--neg-ink);
}
.attn-row.tone-warn .attn-count {
  background: var(--warn-soft);
  color: var(--warn);
}
.attn-count.zero {
  background: transparent;
  color: var(--ink-3);
  font-weight: 600;
}
.attn-row.empty-row .attn-title {
  color: var(--ink-2);
  font-weight: 550;
}
.attn-link {
  color: var(--ink);
  text-decoration: none;
}
.attn-row > p {
  margin: 0 14px 4px;
}
.attn-row .item-list {
  margin: 0 6px 6px;
}
.item-list li a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 44px;
  padding: 8px 8px;
  border-top: 1px solid var(--line);
  color: var(--ink);
  font-size: 14.5px;
  text-decoration: none;
}
.item-list li:first-child a {
  border-top: 0;
}
.item-list li a:hover {
  background: var(--surface-3);
}
.item-list .num {
  flex: none;
  font-weight: 650;
}
.item-list li.is-neg .num {
  color: var(--neg);
}
.item-list li.more a {
  color: var(--accent);
  font-weight: 600;
}
.code-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.code-list a {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 4px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--pill);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
}
.code-list a:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.code-list li.muted {
  align-self: center;
  font-size: 14px;
}
.tile-csv .row-links {
  margin: 0;
}
/* v1.2 §8: Hari ini, what needs the owner now, first on Papan pemuka. Each row opens its list. */
.hari {
  margin: 14px 0 0;
  padding: 12px 6px 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.hari h2 {
  margin: 0 10px 4px;
  font-size: 17px;
}
.hari-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Nothing needs action: one plain line, not a link. */
.hari-tenang {
  margin: 2px 10px 8px;
  color: var(--ink-2);
}
.hari-list a {
  display: grid;
  grid-template-columns: 42px minmax(0, 1fr) 20px;
  align-items: center;
  gap: 12px;
  min-height: 60px;
  padding: 8px 10px;
  border-top: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
  transition: background-color 140ms ease;
}
.hari-list li:first-child a {
  border-top-color: transparent;
}
.hari-list a::after {
  content: "";
  width: 20px;
  height: 20px;
  background: var(--ink-3);
  -webkit-mask: var(--i-right) center / 20px no-repeat;
  mask: var(--i-right) center / 20px no-repeat;
}
.hari-list a:hover {
  background: var(--surface-2);
  color: var(--ink);
}
.hari-list a:active {
  background: var(--surface-3);
}
.hari-n {
  display: grid;
  place-items: center;
  height: 34px;
  border-radius: 11px;
  font-size: 17px;
  font-weight: 700;
}
.hari-list .tone-accent .hari-n {
  background: var(--accent);
  color: var(--on-primary);
}
.hari-list .tone-neg .hari-n {
  background: var(--neg-soft);
  color: var(--neg-ink);
}
.hari-list .tone-warn .hari-n {
  background: var(--warn-soft);
  color: var(--warn);
}
.hari-t {
  font-size: 15.5px;
  font-weight: 650;
  line-height: 1.3;
}
.hari-t small {
  display: block;
  margin-top: 2px;
  font-size: 13.5px;
  font-weight: 400;
  color: var(--ink-2);
}
@container dash (min-width: 700px) {
  .hari-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 4px;
  }
  .hari-list a {
    border-top-color: transparent;
    border-radius: var(--radius);
  }
  .dash-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
  }
  .tile-csv {
    grid-column: 1 / -1;
  }
}
@container dash (min-width: 940px) {
  .dash-grid {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    grid-template-areas:
      "jualan trend"
      "pelanggan trend"
      "perhatian stok"
      "perhatian katalog"
      "perhatian csv";
  }
  .tile-jualan {
    grid-area: jualan;
  }
  .tile-trend {
    grid-area: trend;
  }
  .tile-pelanggan {
    grid-area: pelanggan;
  }
  .tile-perhatian {
    grid-area: perhatian;
  }
  .tile-stok {
    grid-area: stok;
  }
  .tile-katalog {
    grid-area: katalog;
  }
  .tile-csv {
    grid-area: csv;
  }
  .chart {
    height: 168px;
  }
}

/* ---------- Sign in ---------- */
.signin {
  max-width: 360px;
  margin: 4vh auto 0;
  text-align: center;
}
.signin-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-bottom: 22px;
}
.signin-brand .mark {
  font-size: 52px;
  color: var(--ink);
}
.signin-brand .sys {
  padding: 2px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--pill);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}
.signin h1 {
  margin-bottom: 4px;
  font-size: 22px;
}
.signin .alert {
  text-align: left;
}
.pin-input {
  margin: 14px 0 16px;
  min-height: 64px !important;
  text-align: center;
  font-size: 30px !important;
  letter-spacing: 0.4em;
  border-radius: 16px !important;
}
.pinpad {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.pinpad .key {
  min-height: 64px;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--surface);
  box-shadow: var(--shadow-1);
  font-size: 26px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 120ms ease, transform 120ms var(--ease-out);
}
.pinpad .key:active {
  transform: scale(0.95);
  background: var(--surface-3);
}
.pinpad .key.soft {
  background: transparent;
  box-shadow: none;
  border-color: transparent;
  font-size: 16px;
  color: var(--ink-2);
}
.pinpad .key.go {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
  font-size: 18px;
}
.pinpad .key:disabled {
  opacity: 0.45;
}

/* ---------- Tetapan ---------- */
.warna-hex {
  margin-top: 14px;
}
.hex-list {
  list-style: none;
  margin: 4px 0 8px;
  padding: 0;
}
.hex-list li {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) 7.5em;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-top: 1px solid var(--line);
}
.hex-list label {
  font-size: 15px;
  font-weight: 600;
}
.hex-list input {
  min-height: 44px;
  padding: 6px 8px;
}
.swatch {
  display: block;
  width: 28px;
  height: 28px;
}
.swatch.none {
  border: 1.5px dashed var(--line-2);
  border-radius: 50%;
}
.install-state {
  font-weight: 650;
  color: var(--ok);
}

/* ---------- Resit ---------- */
.resit-items {
  list-style: none;
  margin: 0 0 4px;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.resit-items li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 12px;
  padding: 12px 14px;
  border-top: 1px solid var(--line);
}
.resit-items li:first-child {
  border-top: 0;
}
.resit-items .ri-main {
  grid-column: 1;
  font-size: 16px;
}
.resit-items .ri-main b {
  color: var(--accent);
}
.resit-items .ri-qty {
  grid-column: 1;
  color: var(--ink-2);
  font-size: 15px;
}
.resit-items .ri-sum {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  font-size: 17px;
}
.resit-items li.void .ri-main,
.resit-items li.void .ri-qty,
.resit-items li.void .ri-sum {
  text-decoration: line-through;
  color: var(--ink-2);
}
.resit-items .tag {
  grid-column: 1;
  justify-self: start;
  margin-left: 0;
  background: var(--neg-soft);
  color: var(--neg-ink);
}
.resit-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 8px 4px 18px;
  font-size: 18px;
  font-weight: 700;
}
.resit-total b {
  font-size: 26px;
  letter-spacing: -0.01em;
}
.resit-form {
  gap: 6px;
}
.pdpa {
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-2);
}
.pdpa p {
  margin: 0;
}
.pdpa details {
  margin-top: 6px;
}
.pdpa summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
label.consent,
form.stack > label.consent {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 44px;
  padding: 12px 2px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}
label.consent input,
form.stack > label.consent input {
  flex: none;
  width: 26px;
  height: 26px;
  margin: 0;
  accent-color: var(--accent);
}
.resit-send {
  display: grid;
  gap: 10px;
  margin: 6px 0 14px;
}
.btn.wa {
  background: #1f7a4d;
  border-color: #1f7a4d;
  color: #fff;
}
.btn.wa::before {
  content: "";
  width: 20px;
  height: 20px;
  flex: none;
  background: currentColor;
  -webkit-mask: var(--i-chat) center / 20px no-repeat;
  mask: var(--i-chat) center / 20px no-repeat;
}
.btn.wa:hover {
  background: #17603c;
  border-color: #17603c;
  color: #fff;
}
.email-status {
  margin: 4px 2px 0;
  font-weight: 700;
  overflow-wrap: anywhere;
}
.email-status.ok {
  color: var(--ok);
}
.email-status.bad {
  color: var(--neg);
}
.email-form {
  display: grid;
  gap: 6px;
}
details.resend > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.copy-link {
  display: grid;
  gap: 6px;
}

/* ---------- Pelanggan ---------- */
.cust .cust-name {
  display: block;
  font-size: 17px;
}
.cust .cust-contact {
  display: block;
  margin-top: 2px;
  font-size: 15px;
  overflow-wrap: anywhere;
}
.cust .cust-meta {
  display: block;
  margin-top: 2px;
  font-size: 14px;
  color: var(--ink-2);
}
.addr-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
}
.addr {
  padding: 10px 0;
  border-top: 1px solid var(--line);
  overflow-wrap: anywhere;
}
details.padam {
  margin-top: 18px;
}
details.padam .batal-form p {
  margin: 0;
  font-size: 15px;
}

/* ---------- Installable app ---------- */
.update-bar {
  position: fixed;
  z-index: 75;
  left: 12px;
  right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  max-width: 460px;
  margin: 0 auto;
  padding: 8px 8px 8px 16px;
  border-radius: 16px;
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--shadow-3);
  font-weight: 650;
}
.update-bar .btn.primary {
  background: var(--on-primary);
  border-color: var(--on-primary);
  color: var(--primary);
}
dialog.sheet {
  position: fixed;
  inset: auto 0 0 0;
  width: 100%;
  max-width: 520px;
  max-height: 88dvh;
  margin: 0 auto;
  padding: 6px 18px calc(18px + env(safe-area-inset-bottom));
  border: 0;
  border-radius: 24px 24px 0 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-3);
  overflow-y: auto;
  overscroll-behavior: contain;
}
dialog.sheet[open] {
  transform: none;
  transition: transform 320ms var(--ease-drawer);
}
@starting-style {
  dialog.sheet[open] {
    transform: translateY(100%);
  }
}
dialog.sheet.dragging {
  transition: none;
}
dialog.sheet::backdrop {
  background: var(--scrim);
}
.sheet-grab {
  width: 40px;
  height: 5px;
  margin: 4px auto 10px;
  border-radius: 3px;
  background: var(--line-2);
  touch-action: none;
}
.sheet-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}
.sheet-icon {
  width: 52px;
  height: 52px;
  flex: none;
  border-radius: 13px;
  box-shadow: var(--shadow-2);
}
.sheet-title {
  flex: 1;
  min-width: 0;
}
.sheet-title h2 {
  margin: 2px 0 2px;
  font-size: 19px;
}
.sheet-title p {
  margin: 0;
  font-size: 15px;
}
.sheet-body {
  margin-top: 14px;
}
.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
  counter-reset: s;
}
.steps li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 1px solid var(--line);
}
.step-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  color: #0a72e8;
}
.sheet-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
}

/* ---------- Motion: one quiet page change, reduced-motion safe ---------- */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root),
::view-transition-group(page) {
  animation: none;
}
::view-transition-old(page) {
  animation: vt-out 120ms ease-out both;
}
::view-transition-new(page) {
  animation: vt-in 240ms var(--ease-out) both;
}
::view-transition-group(tabbar),
::view-transition-old(tabbar),
::view-transition-new(tabbar) {
  animation: none;
}
::view-transition-group(tab-current),
::view-transition-group(tab-1),
::view-transition-group(tab-2),
::view-transition-group(tab-3),
::view-transition-group(tab-4),
::view-transition-group(tab-5),
::view-transition-old(tab-1),
::view-transition-new(tab-1),
::view-transition-old(tab-2),
::view-transition-new(tab-2),
::view-transition-old(tab-3),
::view-transition-new(tab-3),
::view-transition-old(tab-4),
::view-transition-new(tab-4),
::view-transition-old(tab-5),
::view-transition-new(tab-5) {
  animation-duration: 280ms;
  animation-timing-function: var(--ease-out);
}
@keyframes vt-out {
  to {
    opacity: 0;
  }
}
@keyframes vt-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
/* Reduced motion: nothing slides, scales or spins; fades that explain a change stay. */
@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }
  .btn:active,
  .icon-btn:active,
  label.opt:active > span,
  button.chip:active,
  button.step-btn:active,
  button.design:active,
  .pinpad .key:active,
  .prod a:active,
  .cust a:active {
    transform: none;
  }
  .done .ok-title::before {
    animation: none;
  }
  .tabbar,
  html.kb .tabbar,
  .tab-ind,
  .tab-pill {
    transition: none;
  }
  dialog.tanya[open] {
    animation: page-fade 160ms ease both;
  }
  html.kb .tabbar,
  .tab:active .tab-ind,
  .tab:active .tab-pill {
    transform: none;
  }
  .btn.is-busy::after {
    animation: none;
    border-right-color: currentColor;
    opacity: 0.5;
  }
  .toast,
  .toast.leaving {
    transform: none;
  }
  @starting-style {
    .toast {
      transform: none;
    }
    dialog.sheet[open] {
      transform: none;
      opacity: 0;
    }
  }
  dialog.sheet[open] {
    transition: opacity 160ms ease;
  }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
/* The sheet fades in 160ms instead of rising; closed, it waits off-screen (§7). */
@media (prefers-reduced-motion: reduce) and (max-width: 1023.98px) {
  .sidebar {
    opacity: 0;
    transition: opacity 160ms ease, visibility 0s linear 160ms, transform 0s linear 160ms;
  }
  html[data-menu="open"] .sidebar,
  html:not(.shell-js) .sidebar:target {
    opacity: 1;
    transition: opacity 160ms ease, visibility 0s, transform 0s;
  }
}
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}
@keyframes page-fade {
  from {
    opacity: 0;
  }
}

/* ---------- Touch hygiene (pwa-native-feel) ---------- */
button,
a,
label,
summary,
[role="button"] {
  touch-action: manipulation;
}
nav,
header.top,
.sidebar,
button,
.btn,
label.opt,
summary {
  -webkit-touch-callout: none;
}
input,
textarea,
select,
table,
.mv,
.resit-items {
  -webkit-user-select: text;
  user-select: text;
}

/* ---------- Wider screens ---------- */
@media (min-width: 560px) {
  .amaran-actions {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .grid4 {
    grid-template-columns: repeat(8, minmax(0, 1fr));
  }
  .gambar-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  main.wrap {
    padding: 22px 24px 64px;
  }
  .loc-tabs {
    margin: 0 0 12px;
    padding: 2px 0 4px;
    flex-wrap: wrap;
    overflow: visible;
  }
  .simpan-bar {
    margin: 0 -24px;
    padding-left: 24px;
    padding-right: 24px;
  }
  .quick {
    margin: -2px -24px 16px;
  }
  .quick .sublabel {
    margin: 0 24px 8px;
  }
  .quick-row {
    padding: 0 24px 2px;
  }
  .card {
    padding: 20px;
  }
  .prod-list,
  .cust-list {
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  }
  .head-row .btn.big,
  .btn.big.rekod-stok {
    width: auto;
    min-height: 48px;
    padding: 10px 24px;
    font-size: 16px;
  }
  table.grid.baki th {
    padding: 4px 4px 7px;
    font-size: 12.5px;
    letter-spacing: -0.01em;
  }
  table.grid.baki td {
    padding: 9px 4px;
  }
  table.grid.baki td:first-child {
    font-size: 14px;
  }
}
@media (min-width: 760px) {
  .sejarah-page {
    max-width: 880px;
    margin: 0 auto;
  }
  .filters {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr) auto;
    align-items: end;
  }
  .filters label:first-child,
  .filters .filter-actions {
    grid-column: auto;
  }
  .filters .filter-actions .btn {
    min-height: var(--tap);
    padding-left: 22px;
    padding-right: 22px;
  }
}
@media (min-width: 1024px) {
  body {
    font-size: 16px;
  }
  .attn-row {
    scroll-margin-top: calc(60px + 16px);
  }
  body.masuk .app {
    display: grid;
    grid-template-columns: var(--side-w) minmax(0, 1fr);
  }
  body.masuk .sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    padding-top: var(--safe-t);
    border-right: 1px solid var(--line);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .scrim,
  .side-close,
  .tabbar {
    display: none !important;
  }
  .side-head {
    padding: 16px 10px 2px 22px;
  }
  header.top {
    min-height: 60px;
    padding: 8px 32px;
  }
  body.masuk .top-title .brand {
    display: none;
  }
  body.masuk .top-title .page-name {
    font-size: 15px;
    font-weight: 600;
    color: var(--ink-2);
  }
  main.wrap {
    padding: 26px 32px 72px;
  }
  .simpan-bar {
    margin: 0;
    padding-left: 0;
    padding-right: 0;
  }
  .quick {
    margin: -2px 0 16px;
  }
  .quick .sublabel {
    margin: 0 0 8px;
  }
  .quick-row {
    flex-wrap: wrap;
    padding: 0 0 2px;
  }
  h1 {
    font-size: 28px;
  }
  .toasts {
    left: auto;
    right: 24px;
    top: auto;
    bottom: 24px;
    width: 380px;
    justify-items: end;
  }
  @starting-style {
    .toast {
      opacity: 0;
      transform: translateY(10px) scale(0.98);
    }
  }
  .update-bar {
    left: auto;
    right: 24px;
    bottom: 24px;
    margin: 0;
  }
  body.masuk .talian {
    left: var(--side-w);
  }
  dialog.sheet {
    inset: auto 24px 24px auto;
    width: 400px;
    border-radius: 20px;
    padding-bottom: 18px;
  }
  .sheet-grab {
    display: none;
  }
  @starting-style {
    dialog.sheet[open] {
      transform: translateY(16px);
      opacity: 0;
    }
  }
}

/* ---------- Dark (follows the phone setting; overrides last) ---------- */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --bg: #141110;
    --surface: #1d1917;
    --surface-2: #181412;
    --surface-3: #2a2421;
    --ink: #f3ece7;
    --ink-2: #c9bab2;
    --ink-3: #a3948c;
    --line: #322a26;
    --line-2: #4a3f3a;
    --accent: #d9ae97;
    --accent-strong: #e8c4b0;
    --accent-soft: #3a2b24;
    --accent-ink: #ecc9b5;
    --ring: rgb(217 174 151 / 0.32);
    --primary: #f3ece7;
    --primary-hover: #ffffff;
    --on-primary: #171312;
    --ok: #8fd3ae;
    --ok-soft: #1b2b23;
    --ok-line: #2f5a44;
    --warn: #f0c070;
    --warn-soft: #33270f;
    --warn-line: #5f4a1f;
    --neg: #ff8f80;
    --neg-ink: #ffb2a8;
    --neg-soft: #3a1c18;
    --neg-line: #6b2f27;
    --info: #a9c3d8;
    --info-soft: #1e2a33;
    --teal: #8fd0cb;
    --teal-soft: #17302e;
    --bar: #5a463d;
    --top-bg: rgb(20 17 16 / 0.86);
    --scrim: rgb(0 0 0 / 0.6);
    --shadow-1: 0 1px 2px rgb(0 0 0 / 0.3);
    --shadow-2: 0 6px 18px rgb(0 0 0 / 0.4);
    --shadow-3: 0 18px 48px rgb(0 0 0 / 0.55);
    --i-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3948c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='10.8' cy='10.8' r='6.8'/%3E%3Cpath d='M20 20l-4.4-4.4'/%3E%3C/svg%3E");
  }
  select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a3948c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E");
  }
  .simpan-bar {
    background: linear-gradient(to top, var(--bg) 72%, rgb(20 17 16 / 0));
  }
  .alert.info {
    color: var(--warn);
  }
  .btn.danger,
  .btn.danger:hover {
    color: #1a0d0b;
  }
  .toast {
    background: #2e2622;
    color: var(--ink);
  }
  .toast.err {
    background: #5c2019;
    color: #ffe2dd;
  }
  .toast.err::before {
    background: #ffb2a8;
  }
  .update-bar {
    background: #2e2622;
    color: var(--ink);
  }
  .talian {
    color: var(--warn);
  }
  .update-bar .btn.primary {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
  }
  .btn.wa {
    background: #2f9a66;
    border-color: #2f9a66;
    color: #06140d;
  }
  .step-icon {
    color: #5aa9ff;
  }
  .done .ok-title::before {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23102018' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
  }
  .swatch {
    filter: drop-shadow(0 0 0.5px rgb(255 255 255 / 0.6));
  }
}

/* A mouse needs less height than a finger: the laptop sidebar keeps sign-out on screen. */
@media (min-width: 1024px) and (pointer: fine) {
  .nav-item {
    min-height: 38px;
    padding: 7px 10px;
    font-size: 15px;
  }
  .side-foot {
    padding: 8px 12px 12px;
  }
  .side-foot .install-link {
    display: none;
  }
}
@media (min-width: 1024px) {
  .who {
    flex-wrap: wrap;
  }
  .whoami {
    flex: 1 0 100%;
  }
  .whoami .btn {
    width: 100%;
    min-height: 40px;
  }
  .who.has-notif {
    grid-template-columns: 36px minmax(0, 1fr);
    grid-template-areas: "icon text" "notif notif" "out out";
    row-gap: 4px;
  }
}

/* ==========================================================================
   v1.3 (docs/spec-v1.3-2026.md). One block per build area, so the parallel
   branches never edit the same lines. Keep each area's new rules inside its
   own block; say in the area notes when a rule outside it had to change.
   ========================================================================== */

/* ---- v1.3 block A: shell (offline guard, freshness reload, tablet, sidebar) ---- */

/* A3 (fix round 1): the offline guard's inline message (shell.js) reuses .alert.err for its colour and
   "!" icon. It is inserted above the pressed button; these rules make it take its OWN full-width row so
   a grid/flex container never squashes it into a side cell nor pushes the button off-screen. A plain or
   single-column form (Rekod Simpan bar, Pindahan baharu, Tetapan's 1-col grid) needs nothing but spacing. */
.offline-block {
  margin: 12px 0;
}
/* Multi-column grids whose cell the button lives in: span the whole row and sit first — above the PIN
   keys, the Kembali / Simpan juga pair, and the input of an inline/search row (Tetapan, Produk, Pelanggan). */
.pinpad .offline-block,
.amaran-actions .offline-block,
form.inline .offline-block,
.search-row .offline-block {
  grid-column: 1 / -1;
  order: -1;
}
/* A form inside a table cell (the item page's Nyahaktif / Aktifkan, owner only) has just its narrow cell to
   hold the message: it wrapped to a ~123x341 px column that grew the table past its wrapper (an inner scroll
   at 390) and ran off the wrapper's edge. shell.js gives the message a table row of its own, directly above
   the button's row, so it is as wide as the table and nothing in the table moves sideways. Where the table
   already scrolls inside its wrapper (the owner at 360), the message sticks to the wrapper's left edge and is
   capped to the wrapper's visible width (100cqw: the wrapper is a size container while the row exists, so no
   script has to measure it), and every word shows without scrolling. */
table.grid .offline-row td {
  padding: 0;
  border-top: 0;
}
.table-wrap:has(.offline-row) {
  container-type: inline-size;
}
.offline-row .offline-block {
  position: sticky;
  left: 0;
  width: min(100%, 100cqw);
  margin: 6px 0 8px;
}
/* Rekod Simpan bar: keep it one line — hide the pre-press "Tunggu talian…" note while the stronger
   "belum dihantar" message shows. (The bar's own `.simpan-bar .alert` spacing already applies.) */
.simpan-bar:has(.offline-block) .talian-simpan {
  display: none;
}
/* Staff's Keluar is a flex row (icon · text · the whoami form). A message inside the form would make the
   form demand width and squash "Masuk sebagai"; wrap the form onto its own full row instead. (Owner's
   grid ignores these: it is display:grid and its whoami is display:contents.) */
.who:has(.offline-block) {
  flex-wrap: wrap;
}
.who:has(.offline-block) .whoami {
  flex: 1 0 100%;
}
/* Owner's Keluar is a grid with the form display:contents, so the message is a direct grid child. Below
   1024px it is a full-width row at the very top (above the identity row and the button); at >=1024px the
   media query below moves it to sit directly above Keluar instead, so the sidebar foot stays in view
   (fix round 2). */
.who.has-notif:has(.offline-block) {
  grid-template-areas: "msg msg msg" "icon text out" "notif notif notif";
}
.who.has-notif .offline-block {
  grid-area: msg;
  margin: 4px 0 2px;
}
@media (min-width: 1024px) {
  /* The sidebar is a scrollable 100vh column; a grown foot would push Keluar below the fold. Make the
     nav the scroller instead so the foot — the message and Keluar — stays in view (fix round 2). */
  body.masuk .sidebar:has(.offline-block) {
    overflow-y: hidden;
  }
  body.masuk .sidebar:has(.offline-block) .side-nav {
    min-height: 0;
    overflow-y: auto;
  }
  /* The message sits directly above Keluar, below the identity and notif rows. */
  .who.has-notif:has(.offline-block) {
    grid-template-areas: "icon text" "notif notif" "msg msg" "out out";
  }
}

/* C2: the laptop sidebar can be made narrow — an 84px rail (spec C2), only >=1024px. Below 1024px the
   class changes nothing (the Lagi sheet always shows the full menu). The rail puts a short word under
   each icon, replaces group headings with a thin divider, moves badges onto the icon, hides the
   "N rekaan dipapar" line, and reduces the foot to the role icon and a Keluar icon button with its
   label. Every item keeps its full label as the accessible name (nav-label stays, only visually hidden)
   and its aria-current. The width animates via the grid column (200ms); none with reduced motion.
   While the foot holds the offline message (Task 8), the rail returns to 248px so that message and
   Keluar stay in view — so the narrow rules all step aside when the sidebar holds an .offline-block. */

/* The short rail word: hidden in the wide sidebar and the sheet; shown only in the narrow rail. */
.nav-short {
  display: none;
}
/* The toggle belongs to the real sidebar (>=1024px); it is never shown in the Lagi sheet below it. */
@media (max-width: 1023.98px) {
  .side-toggle {
    display: none !important;
  }
}
@media (min-width: 1024px) {
  /* The width animates between 248px and 84px via the grid's first column. */
  body.masuk .app {
    transition: grid-template-columns 200ms var(--ease-out);
  }
  /* The toggle keeps .icon-btn's 44x44 touch target (no size override, so a coarse pointer gets 44px too). */
  /* 84px, unless the foot is showing the offline message — then stay wide so Task 8's foot rules hold. */
  html.menu-kecil:not(:has(.sidebar .offline-block)) {
    --side-w: 84px;
  }
  /* The head: centre the toggle; 84px has no room for the Gilda wordmark, so it stands in as the way back. */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .side-head {
    justify-content: center;
    padding: 16px 6px 2px;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .side-brand {
    display: none;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .side-nav {
    padding: 2px 8px 8px;
  }
  /* The taller stacked items need more height, so pin the foot (the role icon and Keluar) and let the nav
     scroll if the icons do not all fit — the same mechanism Task 8 uses while the offline message shows. A
     soft fade at the bottom edge hints there is more to scroll to (macOS has no permanent scrollbar). */
  html.menu-kecil .sidebar:not(:has(.offline-block)) {
    overflow-y: hidden;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .side-nav {
    min-height: 0;
    overflow-y: auto;
    /* The always-on bottom fade (below) would otherwise dim a focus-scrolled last item; this much
       room keeps it clear of the fade, and scroll-padding-bottom stops the browser's own
       scroll-into-view from stopping short of that room (C2 tidy). */
    padding-bottom: 20px;
    scroll-padding-bottom: 20px;
    /* A classic (non-overlay) scrollbar otherwise reserves enough width at 1024x768/1366x768 to
       overflow the rail sideways (a short label like "Pelanggan" no longer fits); Safari ignores this
       property, so .nav-short below also carries its own, scrollbar-independent fix (C2 tidy). */
    scrollbar-width: thin;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
  }
  /* Group headings and the catalogue status line give way to a thin divider between sections. */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-group,
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-status {
    display: none;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-sec {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--line);
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .side-nav > .nav-sec:first-child {
    margin-top: 2px;
    padding-top: 0;
    border-top: 0;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-sec ul {
    gap: 4px;
  }
  /* Each item stacks: the icon over a short word. The full label is kept for the accessible name
     (visually clipped, not removed), so a screen reader still reads "Baki, 7 perlu perhatian". */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-item {
    flex-direction: column;
    gap: 3px;
    justify-content: center;
    min-height: 56px;
    padding: 8px 2px;
    font-size: 11.5px;
    line-height: 1.15;
    text-align: center;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-short {
    display: block;
    max-width: 100%;
    overflow: hidden;
    /* No label is ever cut (C2 tidy): a touch smaller than the 11.5px item font, and free to wrap
       (breaking a long single word if it must) rather than hold a single line that would force the
       rail wider than its 84px — the scrollbar-width fix above is not available in every browser. */
    font-size: 11px;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  /* The external-link glyph has no room in the rail; the catalogue link still opens a new tab. */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-ext {
    display: none;
  }
  /* The badge becomes the bar's number badge, on the icon's top-right, not a trailing pill. */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-badge {
    position: absolute;
    top: 6px;
    left: calc(50% + 7px);
    min-width: 18px;
    height: 18px;
    margin: 0;
    padding: 0 5px;
    border-radius: 9px;
    font-size: 11px;
    line-height: 18px;
    box-shadow: 0 0 0 2px var(--surface-2);
  }
  /* The foot: the role icon and a compact Keluar icon button with its label. The identity text, the
     owner's notif checkbox and the install button step aside (the wide sidebar still carries them). */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .side-foot {
    padding: 8px 6px 10px;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .install-link {
    display: none;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .who,
  html.menu-kecil .sidebar:not(:has(.offline-block)) .who.has-notif {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 2px 0 0;
  }
  /* "Masuk sebagai Pemilik/Staf" is clipped (sr-only), not removed, so assistive tech still reads the role;
     the role avatar carries a title for sighted users. The notif checkbox steps aside (widen to reach it). */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .who-text {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .keluar-notif {
    display: none;
  }
  html.menu-kecil .sidebar:not(:has(.offline-block)) .whoami {
    flex: none;
    width: 100%;
    display: flex;
    justify-content: center;
  }
  /* Keluar stacks like the nav items (icon over the word) so it fits the 84px rail instead of a 93px pill
     that overflowed the slot and made the sidebar scroll sideways. */
  html.menu-kecil .sidebar:not(:has(.offline-block)) .btn.keluar {
    flex-direction: column;
    gap: 2px;
    width: 100%;
    min-height: 48px;
    padding: 6px 2px;
    font-size: 11.5px;
  }
}
@media (min-width: 1024px) and (prefers-reduced-motion: reduce) {
  body.masuk .app {
    transition: none;
  }
}
/* A mouse (fine pointer) needs less height than a finger, so more of the items fit above the pinned foot
   before the nav must scroll; a coarse pointer (iPad landscape) keeps the 56px touch target. */
@media (min-width: 1024px) and (pointer: fine) {
  html.menu-kecil .sidebar:not(:has(.offline-block)) .nav-item {
    min-height: 44px;
    padding: 3px 2px;
    gap: 1px;
  }
}

/* ---- end of v1.3 block A ---- */

/* ---- v1.3 block B: stock views (Baki filters, item page, Sejarah, freshness line) ---- */

/* B2: the Baki filter chips, under the location tabs: three toggles ("Perlu semak · n", "Hampir habis · n",
   "Habis · n"). There is no "Semua" chip: the location row above already starts with one, and two filled
   "Semua" pills in two look-alike rows said nothing about which was place and which was filter. They WRAP
   when they must (controller ruling: a sideways scroll pushed a chip off-screen and a reload could hide
   the filled one), never scroll. The chip that is on is filled, carries a clear mark (a cross, below) and
   aria-current, never colour alone; tapping it again takes the filter off. */
.tapis-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: -4px 0 12px;
}
.tapis-chips a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 6px 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 140ms ease, border-color 140ms ease;
}
.tapis-chips a:hover {
  border-color: var(--ink-3);
}
.tapis-chips a[aria-current="true"],
.tapis-chips a[aria-current="true"]:hover {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}
/* The clear mark of the chip that is on. The cross fills the middle half of its 18 px box, so the box is
   pulled in by 4 px on the right: it costs the row 16 px, and the cross ends where the label would. */
.tapis-chips a[aria-current="true"]::after {
  content: "";
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0 -4px 0 2px;
  background: currentColor;
  -webkit-mask: var(--i-x) center / 18px no-repeat;
  mask: var(--i-x) center / 18px no-repeat;
}
/* On a phone the three chips share ONE row from 390 px (they took two, 94 px, and pushed the first stock
   row down): a smaller label and side padding, the words unchanged. At 320 and 360 px they still take two
   rows. The height stays 44 px. From 480 px the roomy chip above fits on one row by itself. */
@media (max-width: 479.98px) {
  .tapis-chips a {
    padding: 6px 7px;
    font-size: 13px;
  }
}

/* B2: each Baki design heading links to its item page. A ≥44 px touch target with a chevron (the same
   "this row navigates" idiom as button.design), so a touch user sees it is tappable; the words are kept.
   fin2: the unit count ("· 12 unit") is its own flex item, so a heading that was wider than the row used
   to push it onto a line by itself under the name (16 of 24 headings at 360 px). The row no longer wraps:
   the link (a default flex item, free to shrink) gives up width and wraps its name instead, and the count
   stays beside it, one unbreakable piece. Where the name and count fit on one row nothing changes. */
#baki-list .design-group h2 {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 2px 8px;
}
#baki-list .design-group h2 .muted {
  flex: none;
  white-space: nowrap;
}
/* The design code stays whole too ("HAB-C09" would otherwise break at its hyphen once the link shrinks). */
#baki-list .design-group h2 a b {
  flex: none;
  white-space: nowrap;
}
/* The ≥44 px target comes from PADDING (12 px above and below the words), and an equal negative margin
   gives the room back: the heading row is as tall as its words again, as before the heading was a link (a
   44 px row made every card 23 px taller and pushed the first stock row down). The padding reaches up into
   the card's own 12 px and down over the 8 px under the heading; nothing else that can be tapped is there. */
#baki-list .design-group h2 a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 12px 0 12px 6px;
  margin: -12px 0 -12px -6px;
  color: inherit;
  text-decoration: none;
}
/* The focus ring is drawn just inside that box, so it stays in the card and clear of the table's headers. */
#baki-list .design-group h2 a:focus-visible {
  outline-offset: -3px;
}
#baki-list .design-group h2 a::after {
  content: "";
  width: 18px;
  height: 18px;
  flex: none;
  background: var(--ink-3);
  -webkit-mask: var(--i-right) center / 18px no-repeat;
  mask: var(--i-right) center / 18px no-repeat;
}
#baki-list .design-group h2 a:hover {
  text-decoration: underline;
}
#baki-list .design-group h2 a:hover::after {
  background: var(--ink);
}

/* B3: the item page's action row under the heading (the primary Rekod stok baru, Rekod jenis lain, Lihat
   sejarah). It wraps on a phone; each is already a ≥44 px .btn. */
.produk-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 10px 0 18px;
}
/* B3: "Baki ikut lokasi" sits on Baki's own card (.design-group), so its zeros read as they do there
   (--ink-3 on the page background was 4.42:1; on the card it is 4.94:1). The card has no heading inside
   it here, so it needs no extra room at the top. */
.produk-page .lokasi-kad {
  padding: 4px;
}
/* On this page the table never breaks a word in the middle ("Metroci/ty", "perjala/nan", "XX/L"): the
   columns size themselves to whole words (auto layout; Baki's own percentages stay as preferences), words
   break only at spaces, and when the columns cannot all fit, the table scrolls inside its .table-wrap.
   Baki's own table (fixed layout, breaks anywhere) is not changed. */
.produk-page table.grid.baki {
  table-layout: auto;
}
.produk-page table.grid.baki th,
.produk-page table.grid.baki td,
.produk-page table.grid.baki td:first-child .tag,
.produk-page table.grid.baki td:first-child .tag-neg {
  overflow-wrap: normal;
}
/* At 320 px the usual table (four places and Jumlah) is 2 px wider than its card with whole words. Header
   labels half a pixel smaller (10 px, from 10.5) let it fit, so at that width it scrolls only when a
   "Dalam perjalanan" column is there too. */
@media (max-width: 359.98px) {
  .produk-page table.grid.baki th {
    font-size: 10px;
  }
}
/* B3: "Lihat semua pergerakan rekaan ini" under the recent-movements list. */
.lihat-semua {
  margin: 12px 0 4px;
}
.lihat-semua a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 700;
}
/* B3/i2 (fix 2): the Varian row's "perlu semak" pill (and the "tidak aktif" tag) sit in the WARNA cell on
   their own line, exactly as Baki draws them — the Baki number cell stays a plain number. white-space:
   normal wraps between words; there is deliberately NO overflow-wrap: anywhere here, so the auto-layout
   column never shrinks below its longest word and the pill wraps word-by-word instead of becoming a
   letter stack (round 1's bug, when the pill lived in the starved right-aligned number column). Not the
   Baki table (its own td:first-child rule already styles its pill). fin2: the pill also takes Baki's
   corners (9px) and line height (1.3), so it reads the same on both tables; the global capsule radius
   and 1.5 line height made a pill that wraps onto two or three lines look fat and loose. */
.produk-page table.grid:not(.baki) td:first-child .tag,
.produk-page table.grid:not(.baki) td:first-child .tag-neg {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 3px 0 0;
  border-radius: 9px;
  line-height: 1.3;
  white-space: normal;
}

/* B4: Sejarah's filter fields fold into a details panel so they take less of a phone (the card was a
   third of a 390 px screen). The summary is the tap target; from 1024 px the panel is always open and
   the summary hidden (::details-content). Where that pseudo is unsupported, the summary stays and the
   panel simply opens on a click. */
.filters-form {
  margin-bottom: 10px;
}
.tapis-panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.tapis-panel > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 14px;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}
.tapis-panel > summary::-webkit-details-marker {
  display: none;
}
.tapis-panel > summary::after {
  content: "";
  width: 18px;
  height: 18px;
  margin-left: auto;
  background: var(--ink-2);
  -webkit-mask: var(--i-right) center / 18px no-repeat;
  mask: var(--i-right) center / 18px no-repeat;
}
.tapis-panel[open] > summary::after {
  transform: rotate(90deg);
}
/* The summary names the filters that are on ("Tapis: Jual · HAB-C01"); each name stays whole on its line. */
.tapis-panel > summary .tapis-apa {
  white-space: nowrap;
}
@media (prefers-reduced-motion: no-preference) {
  .tapis-panel > summary::after {
    transition: transform 150ms var(--ease-out);
  }
}
/* Inside the panel the fields are just the grid; the panel itself is the card. */
.tapis-panel .filters {
  background: none;
  border: 0;
  border-radius: 0;
  margin: 0;
  padding: 0 14px 14px;
}

/* B4: the item filter from a link, and the count row with its own Kosongkan / Kembali. */
.sku-item {
  margin: 10px 0;
  padding: 8px 12px;
  background: var(--surface-2);
  border-radius: var(--radius);
  font-size: 15px;
}
.sku-item a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-left: 4px;
  font-weight: 700;
  white-space: nowrap;
}
.sejarah-count {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 12px 0 10px;
}
.sejarah-count .muted {
  margin: 0;
}
/* One Kosongkan on screen at a time. The one beside the count line is for the CLOSED panel (clearing is
   then one tap); while the panel is open its own Kosongkan shows, so this one steps aside. From 1024 px the
   panel is always laid out, so it is hidden there too (in the rule further down). */
.sejarah-page:has(.tapis-panel[open]) .kosongkan-luar {
  display: none;
}
/* A refused Batal comes back at its row (the form's #m-<id>): its message sits in the row, above the form. */
.mv > .alert {
  margin: 10px 0 0;
}
.lihat-lagi {
  margin: 14px 0 4px;
  text-align: center;
}
.lihat-lagi .btn {
  min-width: 220px;
}

@media (min-width: 1024px) {
  /* The panel is always open on a laptop; the summary is not needed. */
  @supports selector(::details-content) {
    .tapis-panel > summary {
      display: none;
    }
    .tapis-panel::details-content {
      content-visibility: visible;
      display: block;
    }
    .tapis-panel .filters {
      padding: 14px;
    }
    .sejarah-count .kosongkan-luar {
      display: none;
    }
  }

  /* B4: from 1024 px Sejarah's list is a ledger — one compact row per movement, columns aligned across
     rows (subgrid), Batal on its OWN row when open. Every rule is scoped to .sejarah-page so the Pindahan
     list (ul.mv-list.transfer-list uses the same inner classes) keeps its own layout. Phones (< 1024) are
     untouched. Sejarah may use the content width up to ~1200 px (the 880 px cap would starve columns). */
  .sejarah-page {
    max-width: 1200px;
  }
  /* Bounded columns only — no max-content track (one long row would otherwise starve item/route/by to
     0 px). item takes the widest share; item/route/by stay ≥ 120 px (what the e2e checks). */
  .sejarah-page .mv-list {
    display: grid;
    grid-template-columns:
      [type] minmax(130px, 220px)
      [item] minmax(160px, 1.8fr)
      [route] minmax(130px, 1fr)
      [by] minmax(140px, 1.1fr)
      [end] minmax(92px, 140px);
    column-gap: 10px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    overflow: hidden;
  }
  /* A subgrid row's side padding comes out of its EDGE tracks: the type cell is the type track minus the
     left padding. 10 px (not the card's 14) keeps 120 px of the type track's 130 px minimum for the cell. */
  .sejarah-page .mv-list .mv {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
    align-items: start;
    column-gap: 10px;
    row-gap: 4px;
    padding: 8px 10px;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    box-shadow: none;
  }
  .sejarah-page .mv-list .mv:last-child {
    border-bottom: 0;
  }
  .sejarah-page .mv-list .mv:target {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 2px var(--accent);
  }
  .sejarah-page .mv-body {
    display: contents;
  }
  .sejarah-page .mv-top {
    grid-column: type;
    grid-row: 1;
    flex-wrap: wrap;
    white-space: normal;
    row-gap: 2px;
  }
  /* Smaller than the phone's 18px so "Kiraan: n (baki tadi m)" keeps the row inside the height budget;
     the date drops to its own line under the type and quantity. */
  .sejarah-page .mv-top .mv-qty {
    font-size: 14px;
    font-weight: 700;
  }
  .sejarah-page .mv-top .muted {
    margin-left: 0;
    flex-basis: 100%;
  }
  /* The widest badge ("Pulang / tukar", 124 px as a phone pill) must fit that 120 px cell at the track's
     minimum, not on leftover space: a classic 15 px scrollbar at 1024 px leaves the track at ~131 px. A
     slimmer ledger pill is ~118 px, and if a wider system font still overflows, its words wrap inside
     the cell instead of running into the item column. */
  .sejarah-page .mv-top .badge {
    padding: 3px 8px;
    white-space: normal;
  }
  .sejarah-page .mv-top .badge::before {
    margin-right: 5px;
  }
  /* item and route are clamped (a trailing ellipsis shows when a line is cut); the full item text is in a
     title for a mouse. 'Dicatat oleh' is left unclamped so its time is never cut (it wraps to 2–4 lines). */
  .sejarah-page .mv-item,
  .sejarah-page .mv-loc {
    min-width: 0;
    margin: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
  }
  .sejarah-page .mv-by {
    min-width: 0;
    margin: 0;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  .sejarah-page .mv-item {
    grid-column: item;
    grid-row: 1;
  }
  .sejarah-page .mv-loc {
    grid-column: route;
    grid-row: 1;
  }
  .sejarah-page .mv-by {
    grid-column: by;
    grid-row: 1;
  }
  .sejarah-page details.batal {
    grid-column: end;
    grid-row: 1;
    margin: 0;
    justify-self: end;
  }
  /* text-decoration does not cross the box-less .mv-body, so strike the voided row's own cells here. */
  .sejarah-page .mv.voided .mv-top,
  .sejarah-page .mv.voided .mv-item,
  .sejarah-page .mv.voided .mv-loc,
  .sejarah-page .mv.voided .mv-by,
  .sejarah-page .mv.voided .mv-note {
    text-decoration: line-through;
    text-decoration-thickness: 2px;
    color: var(--ink-2);
  }
  /* Note, the receipt/transfer link, the void note and an OPEN Batal form each take their OWN full-width
     auto row below (grid-row: auto, so the open form never covers the row's cells and none overlap). */
  .sejarah-page .mv-note,
  .sejarah-page .void-note,
  .sejarah-page .resit-link,
  .sejarah-page .pindahan-link,
  .sejarah-page .mv > .alert,
  .sejarah-page details.batal[open] {
    grid-column: 1 / -1;
    grid-row: auto;
    margin: 0;
  }
  .sejarah-page .mv-note,
  .sejarah-page .void-note,
  .sejarah-page .mv > .alert,
  .sejarah-page details.batal[open] {
    justify-self: stretch;
  }
  /* The receipt/transfer links are compact: fit-content for any pointer (never a full-width tap band). */
  .sejarah-page .resit-link,
  .sejarah-page .pindahan-link {
    justify-self: start;
    width: fit-content;
  }
}

/* B4: below ~1180 px the item gets a FOURTH line so colour and size show for every real catalogue variant
   (0/144 truncated; rows stay inside the 120 px budget); 1180–1279 px uses three lines; ≥ 1280 px keeps
   two. The full item (and route) text is also in a title for a mouse. */
@media (min-width: 1024px) and (max-width: 1179.98px) {
  .sejarah-page .mv-item {
    -webkit-line-clamp: 4;
    line-clamp: 4;
  }
}
@media (min-width: 1180px) and (max-width: 1279.98px) {
  .sejarah-page .mv-item {
    -webkit-line-clamp: 3;
    line-clamp: 3;
  }
}

/* B4: a fine pointer (a mouse) drops the ledger links' 44 px floor; a coarse pointer keeps the 44 px
   touch target (global-constraints touch rule — an iPad in landscape ≥ 1024 px gets the ledger too). */
@media (min-width: 1024px) and (pointer: fine) {
  .sejarah-page .resit-link,
  .sejarah-page .pindahan-link {
    min-height: 0;
  }
}

/* Below 760 px the panel keeps the two-column grid (Tarikh on its own row, then Lokasi | Jenis), but
   Rekaan — the fourth field — takes a whole row: in half a row "HAB-C01" and its "cth HAB-C01" hint were cut.
   Below 480 px Lokasi and Jenis stack too: side by side each select cut its own chosen option ("Semua
   lokasi" and "Pulang / tukar" up to 390 px, "Butik rakan" up to 360). 480 is where the longest place,
   "Dalam perjalanan", first fits half a row with room to spare (measured: 10 px over at 440, 0.4 px to
   spare at 460, 10 px at 480). */
@media (max-width: 759.98px) {
  .tapis-panel .filters label:nth-child(4) {
    grid-column: 1 / -1;
  }
}
@media (max-width: 479.98px) {
  .tapis-panel .filters label {
    grid-column: 1 / -1;
  }
}

/* B4: the panel holds four fields plus the actions. From 760 to 1319 px a five-track row would clip the
   fields (and the in-panel Kosongkan would steal more), so use three tracks with Rekaan spanning two and
   the actions sharing Rekaan's row — every field stays ≥ 150 px and nothing clips. From 1320 px the row
   fits five tracks; Lokasi and Jenis never go below their longest option ("Dalam perjalanan", "Pulang /
   tukar"), so a chosen one is never cut when a filter is on and Kosongkan widens the actions.
   fin2: the five tracks used to start at 1280 px, but those two max-content minimums take ~11.5 px from
   Tarikh: with a classic 15–17 px scrollbar and a filter on, at 1280–1283 px the date input was 148.5–149.5
   px wide and its year lost the last digit (it is whole from about 150.5 px). From 1320 px Tarikh is 167 px
   or more; 1280–1319 keeps the three tracks (fields about 300 px). */
@media (min-width: 760px) {
  .tapis-panel .filters {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: end;
  }
  .tapis-panel .filters label:first-child,
  .tapis-panel .filters .filter-actions {
    grid-column: auto;
  }
  .tapis-panel .filters label:nth-child(4) {
    grid-column: span 2;
  }
}
@media (min-width: 1320px) {
  .tapis-panel .filters {
    grid-template-columns: minmax(0, 1fr) minmax(max-content, 1fr) minmax(max-content, 1fr) minmax(0, 1fr) auto;
  }
  .tapis-panel .filters label:nth-child(4) {
    grid-column: auto;
  }
}

/* Papan pemuka: an item that needs a check carries Baki's own words, with the place ("perlu semak ·
   Metrocity"). The tag takes its own line under the item's name, as Baki puts it under the colour, and
   wraps between words when the places are many; the number keeps the right edge. */
.item-list .item-name {
  min-width: 0;
}
.item-list .item-name .tag-neg {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 3px 0 0;
  border-radius: 9px;
  line-height: 1.3;
  white-space: normal;
}

/* B5: the freshness line under a data page's heading — "Data setakat HH:MM · Muat semula" (Baki, Sejarah,
   Papan pemuka, the Pindahan list, the item page). Muted and small: one line of text. The screenshot specs
   hide .segar (e2e/flow.spec.ts, e2e/zz-d103-baki.spec.ts, e2e/qa-matrix.spec.ts, e2e/qa-screens.spec.ts)
   so the per-minute time never changes a committed PNG's bytes. */
.segar {
  margin: 0 0 12px;
  color: var(--ink-2);
  font-size: 14px;
}
/* The line belongs to the heading above it: it sits 6 px under an h1 (whose own gap is 14 px), as it
   already sits 4 px under Papan pemuka's. */
h1 + .segar {
  margin-top: -8px;
}
/* On Papan pemuka the line sits inside .page-head, whose `p { margin: 0 }` (higher specificity than .segar)
   would otherwise drop the gap below it; restore it. */
.page-head .segar {
  margin: 0 0 12px;
}
.segar a {
  white-space: nowrap;
}
/* A phone's installed app has no reload button, so "Muat semula" IS the reload; it, and the two links of
   the Pindahan footer, need a real ≥ 44 px target on a touch device — ANY available coarse pointer, so a
   touch laptop driven by a mouse too. The target comes from PADDING on the link's own inline box: 14 px
   above and below make it at least 44 px tall, yet an inline box's vertical padding never grows its line,
   so the line stays one line of text; the 10 px at each side are given back by an equal negative margin,
   so the words sit exactly where they would without it. (A 44 px inline-flex box made each line 44 px tall
   and pushed Baki's list down; its side padding pushed the footer's full stop away from "Rekod" and
   indented the second link; and a box of its own lets a line break fall before that full stop.) */
@media (any-pointer: coarse) {
  .segar a,
  .pindahan-foot a {
    padding: 14px 10px;
    margin: 0 -10px;
  }
  /* The focus ring keeps to the words (2 px off them at the sides), not to the whole padded target, which
     reaches over the heading or the line of text above it. */
  .segar a:focus-visible,
  .pindahan-foot a:focus-visible {
    outline-offset: -8px;
  }
  /* The footer's two links are one per row. The 26 px between the rows (the larger of the two collapsing
     margins) puts their lines 46 px apart, so the two 45 px targets never overlap. */
  .pindahan-foot p + p {
    margin-top: 26px;
  }
}

/* ---- end of v1.3 block B ---- */

/* ---- v1.3 block C: Rekod balance preview, error page ---- */

/* The Kuantiti stepper shows the unit after the number. The input keeps its own box; "unit" sits to its
   right in the middle grid cell, so the stepper reads "5 unit" (the done page already says "N unit"). */
.step-mid {
  display: flex;
  align-items: center;
  gap: 8px;
}
.step-mid input {
  flex: 1;
  min-width: 0;
}
/* --ink-3 on the page background was 4.42:1 in light (17px, 600; 4.5 needed): --ink-2 is 6.47:1 (final fix wave, lens 3 M2). */
.step-mid .unit {
  color: var(--ink-2);
  font-weight: 600;
  white-space: nowrap;
}

/* The balance preview under the Kuantiti stepper: what the record will make the balance, before Simpan.
   app.js fills it from GET /rekod/baki; it stays hidden without JavaScript, offline, or on any failure. */
.pratonton {
  margin: 10px 2px 0;
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 15px;
  font-weight: 650;
  line-height: 1.35;
}
/* A piece of the line that never breaks inside ("Booth: 25 → 15", "selepas rekod: 15", "(tidak cukup)"), so the result
   number never sits alone on a line. app.js builds them; a class, not an inline style, because of the CSP
   (final fix wave, lens 3 M1). */
.pratonton .nb {
  white-space: nowrap;
}
/* Below zero at the source: a warning (colour AND the alert icon, never colour alone). */
.pratonton.kurang {
  position: relative;
  padding-left: 40px;
  border-color: var(--warn-line);
  background: var(--warn-soft);
  color: var(--warn);
}
.pratonton.kurang::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 50%;
  width: 20px;
  height: 20px;
  margin-top: -10px;
  background: currentColor;
  -webkit-mask: var(--i-alert) center / 20px no-repeat;
  mask: var(--i-alert) center / 20px no-repeat;
}

/* P3 tidy: Tetapan's PIN fields are 6 digits (maxlength=6) and stretched to the form's full ~640px
   width; cap them to a readable field width instead. Named .pin-field (not .pin-input) to avoid the
   sign-in pad's own .pin-input (its hidden mirror input, styled completely differently, above). */
.pin-field {
  max-width: 14ch;
}

/* The failure page's two buttons (src/views/ralat.tsx wraps them in <div class="ralat-actions">): the markup has no
   white space between the links, so they touched (0px). A flex row with a gap, wrapping to two rows when narrow
   (final fix wave, lens 3 M9). */
.ralat-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ---- end of v1.3 block C ---- */

/* ---------- Papan Lead: Ringkasan today's figures ----------
   Four compact stat tiles (no existing tile fits a single big number): two across on a phone, four
   across from 720px. The surrounding tables, tabs, badges, cards and the freshness line reuse the
   classes above unchanged. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin: 6px 0 10px;
}
.kpi {
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-1);
}
.kpi-n {
  font-size: 30px;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.kpi-l {
  margin-top: 2px;
  font-size: 14px;
  color: var(--ink-2);
}
@media (min-width: 720px) {
  .kpi-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
