/* ==========================================================================
   Grocery POS — application shell and shared components.

   Design notes
   ------------
   The palette is neutral first. Indigo is the one accent and it marks the
   thing you are meant to press; green is no longer decoration, it now means
   one thing only — money in your favour (profit, paid, stock in hand). That
   is what pulls the eye to the right number on a busy screen.

   Every colour is a token, and every token has a dark counterpart in the
   [data-theme="dark"] block. Nothing below this token layer should contain a
   raw hex value, so a single edit here re-skins the whole application.

   Class names and markup are deliberately untouched: pos.js, purchase.js,
   expenses.js, khata.js and product-form.js all build DOM from these exact
   names, so this file restyles them rather than replacing them.
   ========================================================================== */

:root {
  color-scheme: light;

  /* --- Surfaces ---------------------------------------------------------- */
  --bg: #f4f5f8;
  --panel: #ffffff;
  --surface-2: #f7f8fa;          /* table heads, inset fills, quiet chips */
  --surface-3: #f0f2f6;          /* pressed / recessed */
  --overlay: rgba(13, 18, 28, .55);
  --topbar-bg: rgba(244, 245, 248, .82);
  --glow-a: rgba(79, 70, 229, .13);
  --glow-b: rgba(31, 111, 208, .10);

  /* --- Ink --------------------------------------------------------------- */
  --text: #101623;
  --text-2: #3b465a;
  --muted: #6a7688;
  --line: #e4e7ee;
  --line-soft: #eef0f5;
  --line-hover: #c3cad6;

  /* --- Accent ------------------------------------------------------------ */
  --primary: #4f46e5;            /* fills: buttons, active pills */
  --primary-strong: #4338ca;     /* fill hover */
  --primary-ink: #4338ca;        /* accent as text or icon */
  --primary-weak: #eef0ff;       /* tinted background */
  --primary-line: #cfd3fb;       /* border on a tinted background */
  --primary-contrast: #ffffff;   /* text on a filled accent */
  --focus-ring: rgba(79, 70, 229, .22);
  /* Tighter and less saturated than a single wide bloom: a crisp contact edge
     plus a short coloured lift. A big soft halo under every primary button is
     what makes a business screen look like a consumer app. */
  --accent-glow: 0 1px 2px rgba(16, 22, 35, .10), 0 4px 10px -2px rgba(79, 70, 229, .32);

  /* --- Status --------------------------------------------------------------
     Each status has an ink value (readable as text on the page background) and
     a fill value (readable with white text on top). In dark mode those have to
     diverge: the salmon that reads well as warning text would be unreadable
     behind white button text. */
  --success: #0f7b52;
  --success-fill: #0f7b52;
  --success-soft: #e7f6ef;
  --success-line: #bfe6d3;
  --danger: #cf3a30;
  --danger-fill: #cf3a30;
  --danger-soft: #fdeeec;
  --danger-line: #f6cfca;
  --warning: #a86a06;
  --warning-fill: #a86a06;
  --warning-soft: #fdf4e3;
  --warning-line: #f0dcb0;
  --info: #1f6fd0;
  --info-soft: #eaf2fd;
  --info-line: #c9dffa;

  /* --- Navigation chrome -------------------------------------------------- */
  --nav-bg: #ffffff;
  --nav-line: #e6e9ef;
  --nav-text: #4c5769;
  --nav-text-strong: #101623;
  --nav-hover: #f2f4f8;
  --nav-active-bg: #eef0ff;
  --nav-active-text: #4338ca;
  --nav-muted: #7c879a;
  --flyout-bg: #1b2231;
  --flyout-ink: #f2f5fa;

  /* The sidebar's two widths, and the timings the whole shell shares. One
     curve for layout that moves, a shorter one for colour that just changes -
     using the same duration for both makes hovers feel sluggish. */
  --nav-w: 245px;
  --nav-w-mini: 68px;
  --ease-nav: .22s cubic-bezier(.4, 0, .2, 1);
  --ease-ui: .13s ease;

  /* Deep ink. Named --sidebar for history: purchase.css, khata.css and
     receipt.css all use it as "the strongest dark in the palette" for step
     markers, active tabs, statement rules and the receipt stage. It is no
     longer the sidebar's background, but it is still that darkest ink. */
  --sidebar: #131a26;

  /* --- Elevation -----------------------------------------------------------
     Three stacked layers rather than one: a hairline contact shadow that keeps
     the edge crisp, a short mid shadow for the lift, and a wide low-opacity
     spread for the ambient. One-layer shadows are what make a panel read as a
     sticker; the spread is what makes it read as a card resting on the page. */
  --shadow-xs: 0 1px 1px rgba(16, 22, 35, .04), 0 1px 3px rgba(16, 22, 35, .04);
  --shadow-sm: 0 1px 1px rgba(16, 22, 35, .04), 0 3px 6px -1px rgba(16, 22, 35, .05), 0 10px 20px -8px rgba(16, 22, 35, .06);
  --shadow: 0 1px 1px rgba(16, 22, 35, .04), 0 6px 12px -2px rgba(16, 22, 35, .06), 0 18px 36px -12px rgba(16, 22, 35, .10);
  --shadow-lg: 0 2px 4px rgba(16, 22, 35, .05), 0 12px 28px -6px rgba(16, 22, 35, .12), 0 32px 64px -16px rgba(16, 22, 35, .18);

  /* The top edge of a raised surface catches the light. Invisible on white, so
     it costs nothing here - but it is what stops a dark card looking like a
     hole cut in the page, and it has to be a token to have a dark counterpart. */
  --panel-sheen: inset 0 1px 0 rgba(255, 255, 255, .7);

  /* --- Shape and type ---------------------------------------------------- */
  --radius-sm: 9px;
  --radius: 13px;
  --radius-lg: 18px;
  --font-sans: "Segoe UI Variable Text", "Segoe UI", Inter, system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Segoe UI Variable Display", "Segoe UI Variable Text", "Segoe UI", Inter, system-ui, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;

  /* --- Legacy aliases -----------------------------------------------------
     Kept so khata.css, purchase.css, expenses.css and product-form.css keep
     resolving without being rewritten line by line. */
  --primary-dark: var(--primary-ink);
  --primary-soft: var(--primary-weak);
}

:root[data-theme="dark"] {
  color-scheme: dark;

  /* The page is pushed a shade deeper and the panel a shade lighter than they
     were. Dark mode fails when every surface sits within a few points of every
     other: the eye gets no depth cue and the screen reads as flat grey soup.
     Widening the bg/panel gap is what lets a card look raised without needing
     a heavy border to prove it. */
  --bg: #080b12;
  --panel: #161d2a;
  --surface-2: #1d2534;
  --surface-3: #252e3f;
  --overlay: rgba(3, 6, 12, .7);
  --topbar-bg: rgba(11, 15, 23, .82);
  --glow-a: rgba(99, 102, 241, .16);
  --glow-b: rgba(111, 168, 240, .10);

  --text: #e9eef6;
  --text-2: #c0cad9;
  --muted: #8c98ab;
  --line: #2b3546;
  --line-soft: #212a3a;
  --line-hover: #445269;

  --primary: #6366f1;
  --primary-strong: #7b7dff;
  --primary-ink: #a8aeff;
  --primary-weak: #1d2140;
  --primary-line: #333a66;
  --primary-contrast: #ffffff;
  --focus-ring: rgba(122, 125, 255, .30);
  --accent-glow: 0 6px 18px rgba(0, 0, 0, .45);

  --success: #3fbe89;
  --success-fill: #157a55;
  --success-soft: #10281f;
  --success-line: #1e4436;
  --danger: #f2867c;
  --danger-fill: #c23a2e;
  --danger-soft: #2b1614;
  --danger-line: #4d2724;
  --warning: #e0a94a;
  --warning-fill: #8a5a12;
  --warning-soft: #2a2011;
  --warning-line: #493720;
  --info: #6fa8f0;
  --info-soft: #131f31;
  --info-line: #22364f;

  --nav-bg: #101620;
  --nav-line: #212a38;
  --nav-text: #9fabbe;
  --nav-text-strong: #eef2f8;
  --nav-hover: #1a2130;
  --nav-active-bg: #1e2340;
  --nav-active-text: #b3b8ff;
  --nav-muted: #748093;
  --flyout-bg: #2b3446;
  --flyout-ink: #f2f5fa;

  --sidebar: #05080e;

  --shadow-xs: 0 1px 2px rgba(0, 0, 0, .38);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .42), 0 3px 8px -1px rgba(0, 0, 0, .34), 0 10px 22px -8px rgba(0, 0, 0, .40);
  --shadow: 0 1px 2px rgba(0, 0, 0, .44), 0 8px 16px -3px rgba(0, 0, 0, .40), 0 20px 40px -12px rgba(0, 0, 0, .50);
  --shadow-lg: 0 4px 8px rgba(0, 0, 0, .45), 0 16px 36px -8px rgba(0, 0, 0, .52), 0 40px 72px -20px rgba(0, 0, 0, .62);

  /* Carries real weight here, unlike the light theme where it is invisible:
     this single hairline along a card's top edge is the difference between a
     raised surface and a hole. Keep it under .06 or the cards look plasticky. */
  --panel-sheen: inset 0 1px 0 rgba(255, 255, 255, .045);
}

/* ==========================================================================
   Base
   ========================================================================== */

* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: var(--primary-weak); color: var(--primary-ink); }

a { color: var(--primary-ink); text-decoration: none; }
a:hover { color: var(--primary-strong); }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
h1, h2, h3, p { margin-top: 0; }
h1 { font-family: var(--font-display); font-size: 26px; line-height: 1.2; margin-bottom: 5px; letter-spacing: -.5px; font-weight: 700; }
h2 { font-family: var(--font-display); font-size: 17px; margin-bottom: 4px; letter-spacing: -.2px; font-weight: 650; }
h3 { font-size: 14px; letter-spacing: -.1px; }
small { color: var(--muted); }
hr { border: 0; border-top: 1px solid var(--line); }

:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: 4px;
}

/* --- Icons -----------------------------------------------------------------
   One stroke weight and one grid for every icon in the application, drawn in
   currentColor so each one takes the colour of whatever it sits in. The sprite
   itself is in templates/_icons.html. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.ui-icon, .nav-icon {
  width: 18px; height: 18px;
  /* Slightly heavier than a typical icon set: these are read at a glance
     across a counter, often on a cheap screen, and a 1.5 stroke disappears. */
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: middle;
  pointer-events: none;
}

/* Visible to a screen reader, not to the eye. The icon buttons carry no text
   label, so this is the only thing that tells an assistive user what they do. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Money and counts line up column-wise wherever they appear. */
.text-right, .money-input, .summary-card strong, .metric-card strong {
  font-variant-numeric: tabular-nums;
}

/* A quieter scrollbar. The counter looks at these lists all day. */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line); border: 3px solid transparent; background-clip: content-box; border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: content-box; }

/* ==========================================================================
   Shell
   ---------------------------------------------------------------------------
   The sidebar has two widths and the page has to follow both, so its width is
   a token rather than a number. --nav-w is read by .main-area here and by the
   sticky money bar in purchase.css; anything else that has to sit clear of the
   sidebar should read it too rather than hardcoding a figure.
   ========================================================================== */

/* 100vh on a phone is the tallest the window ever gets, not the tallest it
   is now: iOS measures it with the URL bar retracted, so the last hundred
   pixels of every screen sit underneath browser chrome. dvh is the same
   number on a desktop and the honest one on a handset. The vh line stays
   first as the fallback for anything that has not heard of dvh. */
.app-shell { min-height: 100vh; min-height: 100dvh; display: flex; }

.sidebar {
  width: var(--nav-w);
  background: var(--nav-bg);
  border-right: 1px solid var(--nav-line);
  color: var(--nav-text);
  display: flex;
  flex-direction: column;
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 50;
  transition: width var(--ease-nav);
  /* The drawer runs the full height of a notched screen, so its own contents
     have to keep clear of the notch and the home indicator rather than the
     element being inset - otherwise the panel stops short and the background
     shows through at the top. */
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* --- Brand and the collapse control -------------------------------------- */

.brand {
  height: 68px; flex: 0 0 auto;
  padding: 0 12px 0 16px;
  display: flex; align-items: center; gap: 10px;
  border-bottom: 1px solid var(--nav-line);
}
.brand-id { display: flex; align-items: center; gap: 11px; min-width: 0; flex: 1; color: inherit; }
.brand-mark, .login-logo {
  width: 38px; height: 38px; flex: 0 0 auto;
  display: grid; place-items: center;
  background: linear-gradient(150deg, #6f6af0, #4338ca);
  color: #fff;
  border-radius: 11px;
  font-family: var(--font-display);
  font-weight: 800; font-size: 14px; letter-spacing: .4px;
  box-shadow: 0 6px 16px rgba(79, 70, 229, .3);
}
.brand-text { min-width: 0; }
.brand strong { display: block; font-size: 14.5px; color: var(--nav-text-strong); letter-spacing: -.2px; white-space: nowrap; }
.brand small { display: block; color: var(--nav-muted); margin-top: 1px; font-size: 10.5px; white-space: nowrap; }

.nav-collapse {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border: 0; border-radius: 8px;
  background: transparent; color: var(--nav-muted);
  transition: background var(--ease-ui), color var(--ease-ui);
}
.nav-collapse:hover { background: var(--nav-hover); color: var(--nav-text-strong); }
.nav-collapse .collapse-out { display: none; }

/* --- Menu ----------------------------------------------------------------- */

.nav-menu { padding: 12px 10px; display: flex; flex-direction: column; gap: 2px; flex: 1; overflow-y: auto; overflow-x: hidden; }
.nav-menu a {
  position: relative;
  color: var(--nav-text);
  height: 40px;
  padding: 0 12px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  gap: 11px;
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  transition: background var(--ease-ui), color var(--ease-ui);
}
.nav-icon { width: 20px; height: 20px; flex: 0 0 auto; opacity: .92; }
.nav-label { min-width: 0; overflow: hidden; }
.nav-menu a:hover { background: var(--nav-hover); color: var(--nav-text-strong); }
.nav-menu a.active { background: var(--nav-active-bg); color: var(--nav-active-text); font-weight: 700; }
.nav-menu a.active .nav-icon { opacity: 1; }

/* A hairline on the active item. Small, but it is what tells the eye at a
   glance which screen it is looking at without having to read the label. */
.nav-menu a.active::before {
  content: "";
  position: absolute; left: -10px; top: 50%;
  width: 3px; height: 20px; margin-top: -10px;
  border-radius: 0 3px 3px 0;
  background: var(--primary);
}

/* New Sale is the reason the till is switched on. It is the only filled item
   in the menu and it stays filled whether or not it is the current page. */
.nav-menu a.pos-link {
  background: var(--primary);
  color: var(--primary-contrast);
  font-weight: 700;
  margin-bottom: 6px;
  box-shadow: var(--accent-glow);
}
.nav-menu a.pos-link .nav-icon { opacity: 1; }
.nav-menu a.pos-link:hover, .nav-menu a.pos-link.active { background: var(--primary-strong); color: var(--primary-contrast); }
.nav-menu a.pos-link.active::before { display: none; }

/* --- Footer --------------------------------------------------------------- */

.sidebar-footer { flex: 0 0 auto; padding: 10px 12px 12px; border-top: 1px solid var(--nav-line); color: var(--nav-muted); font-size: 11px; }
.sidebar-user { display: flex; align-items: center; gap: 10px; }
.sidebar-avatar {
  flex: 0 0 auto; width: 34px; height: 34px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--primary-weak); color: var(--primary-ink);
  font-size: 14px; font-weight: 800;
}
.sidebar-user-text { min-width: 0; flex: 1; }
.sidebar-user-text strong { display: block; color: var(--nav-text-strong); font-size: 13px; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-user-text small { display: block; margin-top: 1px; color: var(--nav-muted); font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700; }

/* Two icon buttons instead of a labelled pair and a full-width logout bar.
   That was three stacked rows of chrome at the bottom of every screen; this is
   one, and it gives the height back to the menu. */
.sidebar-tools { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.sidebar-tools form { margin: 0; display: flex; }
.tool-button {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 0; border-radius: 9px;
  background: transparent; color: var(--nav-text);
  transition: background var(--ease-ui), color var(--ease-ui), transform var(--ease-ui);
}
.tool-button .ui-icon { width: 17px; height: 17px; }
.tool-button:hover { background: var(--nav-hover); color: var(--nav-text-strong); }
.tool-button.is-danger:hover { background: var(--danger-soft); color: var(--danger); }
.tool-button:active { transform: scale(.92); }

/* The single sun/moon button on the sign-in and expired cards, which have no
   sidebar to hold the pair. */
.theme-toggle {
  width: 34px; height: 34px;
  display: inline-grid; place-items: center;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); color: var(--text-2);
  transition: background var(--ease-ui), color var(--ease-ui), border-color var(--ease-ui);
}
.theme-toggle:hover { border-color: var(--primary); color: var(--primary-ink); background: var(--primary-weak); }

/* Which glyph shows is decided here, off the attribute the pre-paint script
   sets, so the right one is on screen from the first frame rather than being
   swapped in afterwards. Shared by the sidebar tool button and the card one. */
.theme-out { display: none; }
:root[data-theme="dark"] .theme-in { display: none; }
:root[data-theme="dark"] .theme-out { display: block; }

.sidebar-status { margin: 9px 0 0; color: var(--nav-muted); font-size: 10.5px; line-height: 1.5; display: flex; align-items: center; gap: 6px; }
.status-dot { width: 7px; height: 7px; flex: 0 0 auto; display: inline-block; background: var(--success); border-radius: 50%; }
.status-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ==========================================================================
   Mini sidebar
   ---------------------------------------------------------------------------
   Icons only, labels on hover. The width is the only thing that animates —
   the labels are switched off outright rather than faded, because a label
   half-way through a fade while the panel is still moving is what makes a
   collapsing sidebar feel cheap.
   ========================================================================== */

:root[data-nav="mini"] { --nav-w: var(--nav-w-mini); }

:root[data-nav="mini"] .brand { padding: 0; justify-content: center; }
:root[data-nav="mini"] .brand-id { flex: 0 0 auto; justify-content: center; }
:root[data-nav="mini"] .brand-text,
:root[data-nav="mini"] .nav-label,
:root[data-nav="mini"] .sidebar-user-text,
:root[data-nav="mini"] .status-text { display: none; }

/* No room for the control beside the mark, so the header becomes a short
   vertical stack instead. Nothing is positioned by hand and nothing has to be
   padded around. */
:root[data-nav="mini"] .brand { flex-direction: column; height: auto; padding: 12px 0 10px; gap: 8px; }
:root[data-nav="mini"] .nav-collapse .collapse-in { display: none; }
:root[data-nav="mini"] .nav-collapse .collapse-out { display: block; }

:root[data-nav="mini"] .nav-menu { padding: 12px 8px; align-items: center; }
:root[data-nav="mini"] .nav-menu a { width: 46px; height: 44px; padding: 0; justify-content: center; gap: 0; }
:root[data-nav="mini"] .nav-icon { width: 21px; height: 21px; opacity: 1; }
/* The filled pill already says which page this is; the hairline beside it only
   showed as a stray sliver against the window edge. */
:root[data-nav="mini"] .nav-menu a.active::before { display: none; }

:root[data-nav="mini"] .sidebar-footer { padding: 10px 8px 12px; }
:root[data-nav="mini"] .sidebar-user { flex-direction: column; gap: 6px; }
:root[data-nav="mini"] .sidebar-tools { flex-direction: column; gap: 2px; }
:root[data-nav="mini"] .sidebar-avatar { width: 32px; height: 32px; font-size: 13px; }
:root[data-nav="mini"] .sidebar-status { justify-content: center; margin-top: 8px; }

/* --- Hover labels ---------------------------------------------------------
   A real flyout rather than the browser's title tooltip: it appears instantly
   instead of after a second, it is styled like the rest of the application,
   and it cannot be clipped by the sidebar because it is positioned against the
   viewport. Only ever built for the mini sidebar, where the label is the one
   piece of information missing from the screen. */
:root[data-nav="mini"] .nav-menu a::after,
:root[data-nav="mini"] .tool-button::after {
  content: attr(data-label);
  /* Fixed, with top left to auto: the box keeps the vertical static position it
     would have had inside the row, but escapes .nav-menu's scroll container,
     which would otherwise clip it. An absolutely positioned flyout cannot work
     here - overflow-y: auto forces the horizontal axis to clip too. */
  position: fixed;
  left: calc(var(--nav-w-mini) + 8px);
  margin-top: -2px;
  padding: 7px 11px;
  border-radius: 9px;
  background: var(--flyout-bg);
  color: var(--flyout-ink);
  font-size: 12.5px; font-weight: 650; white-space: nowrap;
  box-shadow: var(--shadow-lg);
  opacity: 0; visibility: hidden;
  transition: opacity .12s ease, visibility .12s ease;
  pointer-events: none;
  z-index: 90;
}
:root[data-nav="mini"] .nav-menu a:hover::after,
:root[data-nav="mini"] .nav-menu a:focus-visible::after,
:root[data-nav="mini"] .tool-button:hover::after,
:root[data-nav="mini"] .tool-button:focus-visible::after {
  opacity: 1; visibility: visible;
}
.main-area {
  margin-left: var(--nav-w);
  width: calc(100% - var(--nav-w));
  /* See .app-shell above: vh first as the fallback, dvh as the honest figure
     on a handset, or the last hundred pixels sit under the browser chrome. */
  min-height: 100vh; min-height: 100dvh;
  transition: margin-left var(--ease-nav), width var(--ease-nav);
}

.topbar {
  height: 60px;
  background: var(--topbar-bg);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
  padding: 0 24px;
  display: flex; align-items: center; gap: 14px;
  position: sticky; top: 0; z-index: 30;
  padding-top: env(safe-area-inset-top, 0px);
}
.topbar-title { font-family: var(--font-display); font-weight: 650; font-size: 15.5px; letter-spacing: -.2px; }
.topbar-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.icon-button {
  width: 36px; height: 36px; display: grid; place-items: center;
  border: 0; background: transparent; color: var(--text-2);
  border-radius: 9px; transition: background var(--ease-ui), color var(--ease-ui);
}
.icon-button:hover { background: var(--surface-3); color: var(--text); }
.mobile-menu { display: none; }
/* The drawer's close button. Phone only - above the breakpoint the sidebar is
   permanent furniture and there is nothing to close. */
.nav-close {
  display: none;
  width: 40px; height: 40px; place-items: center;
  border: 0; background: transparent; color: var(--nav-muted);
  border-radius: 9px; transition: background var(--ease-ui), color var(--ease-ui);
}
.nav-close:hover { background: var(--nav-line); color: var(--nav-text-strong); }
.link-button { border: 0; background: transparent; color: var(--primary-ink); font-weight: 700; padding: 5px; }
.page-content { padding: 24px; max-width: 1600px; margin: 0 auto; }

/* ==========================================================================
   Page furniture
   ========================================================================== */

.page-heading { display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; margin-bottom: 20px; }
.page-heading p, .panel-header p, .muted { color: var(--muted); margin-bottom: 0; }
.button-row { display: flex; gap: 8px; flex-wrap: wrap; }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm), var(--panel-sheen);
  padding: 18px;
}
.panel-header { display: flex; justify-content: space-between; align-items: center; gap: 15px; margin-bottom: 14px; }
.panel-header h2 { margin-bottom: 2px; }
.panel-header a { font-weight: 650; font-size: 13px; }
.panel-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.table-panel { padding: 0; overflow: hidden; }
.table-panel .pagination { padding: 14px 18px; border-top: 1px solid var(--line-soft); }

/* --- Metric cards ----------------------------------------------------------
   The old card wore a coloured dash above the label that carried no meaning.
   The accent now sits on the figure's own baseline as a hairline rule, so the
   card is quieter and the number is what you read first. */
/* auto-fit rather than a fixed five. The dashboard shows six cards to an owner
   and four to a cashier, and the reports page shows five or six depending on
   whether anything came back - a fixed count left one card stranded on a row
   of its own in most of those cases. */
.metric-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 13px; margin-bottom: 20px; }
.metric-card {
  position: relative; overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 17px;
  box-shadow: var(--shadow-xs), var(--panel-sheen);
  min-height: 112px;
  display: flex; flex-direction: column;
  color: var(--text);
  transition: border-color .14s ease, box-shadow .14s ease, transform .14s ease;
}
a.metric-card:hover { border-color: var(--primary); box-shadow: var(--shadow), var(--panel-sheen); transform: translateY(-2px); }
a.metric-card:active { transform: translateY(0); }
/* The stripe fades out down the card rather than running full height as a hard
   bar. Twelve of these on the dashboard at full strength is what turns a
   meaningful colour code into a row of highlighter pen. */
.metric-card::before {
  content: "";
  position: absolute; inset: 0 auto 0 0;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: linear-gradient(to bottom, var(--primary) 0%, var(--primary) 38%, transparent 100%);
  opacity: .9;
}
.metric-card.warning-card::before { background: linear-gradient(to bottom, var(--danger) 0%, var(--danger) 38%, transparent 100%); }
.metric-card.expense-card::before { background: linear-gradient(to bottom, var(--warning) 0%, var(--warning) 38%, transparent 100%); }
/* Micro-caps, matching the table headers and the cash cards. The dashboard was
   mixing sentence-case labels here with uppercase ones a row above. */
.metric-card .metric-label {
  color: var(--muted); font-weight: 750; font-size: 10.5px;
  text-transform: uppercase; letter-spacing: .06em;
}
.metric-card strong {
  font-family: var(--font-display); font-size: 25px; margin: 9px 0 4px;
  letter-spacing: -.8px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.metric-card small { margin-top: auto; font-size: 11.5px; line-height: 1.45; }

.dashboard-grid { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(320px, .8fr); gap: 16px; align-items: start; }
.compact-list { display: flex; flex-direction: column; }
.compact-row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 11px 2px; border-bottom: 1px solid var(--line-soft); color: var(--text);
}
.compact-row:last-child { border-bottom: 0; }
.compact-row:hover strong { color: var(--primary-ink); }
.compact-row small, .table-subtext { display: block; margin-top: 2px; }

/* ==========================================================================
   Tables
   ========================================================================== */

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th {
  color: var(--muted);
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: .06em;
  font-weight: 800;
  background: var(--surface-2);
}
th, td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--line-soft); white-space: nowrap; }
th { border-bottom-color: var(--line); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background .1s ease; }
tbody tr:hover { background: var(--surface-2); }
/* Money in a column has to line up. Proportional digits let "Rs 9,000.00" and
   "Rs 885.00" wander against each other down a right-aligned column, which is
   the single most obvious tell of an unconsidered financial table. */
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
.text-center { text-align: center; }
.empty-cell, .empty-state { text-align: center; color: var(--muted); padding: 34px 14px; white-space: normal; }
/* Every .actions-cell in the application is a <td>. `display: flex` took the
   cell out of the table's box model, so it stopped sharing its row's height
   and baseline - on the products list the Edit and Disable buttons sat below
   the row they belonged to and outside the table's own rule lines. Laying the
   buttons out inline keeps the cell a table cell. */
.actions-cell { white-space: nowrap; }
.actions-cell form { display: inline-block; margin: 0; vertical-align: middle; }
.actions-cell > .btn, .actions-cell > a { vertical-align: middle; }
.actions-cell > * + * { margin-left: 6px; }

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 9px 14px;
  font-weight: 650;
  font-size: 13.5px;
  line-height: 1.2;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  transition: background var(--ease-ui), border-color var(--ease-ui), color var(--ease-ui), box-shadow var(--ease-ui), transform var(--ease-ui);
  white-space: nowrap;
}
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: not-allowed; box-shadow: none; transform: none; }
/* :focus-visible, not :focus - a mouse click on a button should not leave a
   ring behind, but tabbing to it must. The counter is driven from the keyboard. */
.btn:focus-visible, .button:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.btn-primary { background: var(--primary); color: var(--primary-contrast); box-shadow: var(--accent-glow); }
.btn-primary:hover:not(:disabled) { background: var(--primary-strong); color: var(--primary-contrast); }

.btn-secondary { background: var(--panel); border-color: var(--line); color: var(--text-2); box-shadow: var(--shadow-xs); }
.btn-secondary:hover:not(:disabled) { background: var(--surface-2); border-color: var(--muted); color: var(--text); }

.btn-large { padding: 11px 18px; font-size: 14.5px; border-radius: 11px; }
.btn-small { padding: 6px 10px; font-size: 12px; border-radius: 8px; }
.btn-block { width: 100%; }

.btn-danger-soft { background: var(--danger-soft); color: var(--danger); border-color: var(--danger-line); }
.btn-danger-soft:hover:not(:disabled) { background: var(--danger-fill); color: #fff; border-color: var(--danger-fill); }
.btn-success-soft { background: var(--success-soft); color: var(--success); border-color: var(--success-line); }
.btn-success-soft:hover:not(:disabled) { background: var(--success-fill); color: #fff; border-color: var(--success-fill); }
.btn-danger { background: var(--danger-fill); border-color: var(--danger-fill); color: #fff; }
.btn-danger:hover:not(:disabled) { background: var(--danger-fill); filter: brightness(.92); }

/* code_sheet.html prints with a plain <button class="button">. */
.button {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 14px; background: var(--panel); color: var(--text-2);
  font-size: 13.5px; font-weight: 650;
}
.button:hover { background: var(--surface-2); border-color: var(--muted); }

/* ==========================================================================
   Badges, pills and status ink
   ========================================================================== */

.badge {
  display: inline-flex; align-items: center;
  padding: 4px 9px; border-radius: 999px;
  background: var(--surface-3); color: var(--text-2);
  font-size: 11px; font-weight: 750; letter-spacing: .01em;
  white-space: nowrap;
}
.badge-success { background: var(--success-soft); color: var(--success); }
/* The warning and danger variants were being used by templates without ever
   having been defined, so a failed backup and a successful one wore the same
   neutral grey pill. The colour is the whole point of a status badge. */
.badge-warning { background: var(--warning-soft); color: var(--warning); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }

.stock-danger, .danger-text { color: var(--danger) !important; font-weight: 750; }
.stock-good { color: var(--success); font-weight: 650; }
.good-text { color: var(--success); }
.nowrap { white-space: nowrap; }

/* ==========================================================================
   Forms
   ========================================================================== */

.form-input {
  width: 100%; min-height: 42px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--text);
  outline: none;
  transition: border-color var(--ease-ui), box-shadow var(--ease-ui), background var(--ease-ui);
}
.form-input:hover:not(:focus) { border-color: var(--line-hover); }
.form-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--focus-ring); }
.form-input::placeholder { color: var(--muted); opacity: .8; }
.form-input:disabled { background: var(--surface-3); color: var(--muted); cursor: not-allowed; }
textarea.form-input { min-height: 92px; line-height: 1.55; resize: vertical; }

/* The native "Choose File" button is drawn by the browser and ignores the
   page palette, so on the import screens it stayed a light grey slab in dark
   mode. Styling the pseudo-element brings it into the theme. */
input[type="file"] { color: var(--text-2); }
input[type="file"]::file-selector-button {
  margin-right: 10px; padding: 7px 12px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface-2); color: var(--text-2);
  font: inherit; font-size: 13px; font-weight: 650; cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--surface-3); border-color: var(--line-hover); }

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.form-group { display: flex; flex-direction: column; gap: 6px; font-weight: 650; font-size: 13px; }
.form-group small { font-weight: 400; font-size: 12px; line-height: 1.45; }
/* Stacked form groups have nothing between them: the 6px gap above is internal,
   and the separation inside a form comes from .form-grid's own gap. In a plain
   panel - a side panel, a settings card - that leaves each label sitting on the
   previous field's help text. Grid children opt back out, so they are not
   spaced twice. */
.form-group + .form-group { margin-top: 14px; }
/* Any container that lays its own groups out with a gap opts back out, or the
   margin is added on top of the gap and the row goes crooked. */
.form-grid .form-group + .form-group,
.filter-bar .form-group + .form-group { margin-top: 0; }
.form-group + button, .form-group + .btn { margin-top: 14px; }
/* Keeps a label and its "*" or "optional" marker on one line inside the
   column-flex .form-group, where they would otherwise be two flex items. */
.label-line { display: block; }
.label-line .optional, .label-line .required { margin-left: 2px; }
.full-width { grid-column: 1 / -1; }
.checkbox-group { justify-content: flex-end; }
.checkbox-input { width: 17px; height: 17px; accent-color: var(--primary); }
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); }
.model-form { max-width: 900px; }
.form-panel { max-width: 980px; }
.form-actions { display: flex; gap: 9px; margin-top: 20px; flex-wrap: wrap; }
.required { color: var(--danger); margin-left: 3px; }

.field-error, .inline-error {
  color: var(--danger); background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  padding: 9px 11px; border-radius: 8px; font-weight: 600; font-size: 12.5px;
}

/* The same box saying the opposite. The purchase screen uses one banner for
   both answers, because a second one further down the form is one nobody
   scrolls to - and "saved on this computer" has to be read. Tokens, so it
   follows the theme rather than pinning a green. */
.inline-error.is-good {
  color: var(--success, var(--accent));
  background: var(--success-soft, var(--accent-soft));
  border-color: var(--success-line, var(--accent-line));
}

/* A wrapping flex row rather than a fixed four-column grid. The grid was
   written for the products screen; the kharcha book puts six controls in the
   same bar, so the last two dropped onto a second row and stretched to half
   the page each. purchase.css and khata.css still impose their own grids on
   their own screens. */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: end;
  margin-bottom: 16px; padding: 12px;
  box-shadow: none; background: var(--panel);
}
.filter-bar > * { flex: 1 1 168px; min-width: 0; }
.filter-bar > input { flex: 2 1 240px; }
.filter-bar > button, .filter-bar > .btn { flex: 0 0 auto; }
.filter-bar label { display: flex; align-items: center; gap: 8px; color: var(--muted); font-weight: 650; }
/* A label that also carries .form-group stacks its caption above the control.
   Without resetting align-items it inherits `center` from the rule above,
   which in a column flex centres the caption over the field instead of
   starting it at the left edge. */
.filter-bar label.form-group { flex-direction: column; align-items: stretch; gap: 6px; }
.filter-bar label.form-group > span { font-size: 11px; font-weight: 750; letter-spacing: .05em; text-transform: uppercase; }

/* --- Filter chips ----------------------------------------------------------
   Lived only in khata.css, so the purchase list carried the markup and got
   none of the styling - the quick filters rendered as five plain links run
   together on one line. Page furniture, like .tab-row below. khata.css keeps
   its own copy and still wins on the khata screens. */
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.chip {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); background: var(--panel); cursor: pointer;
  padding: 6px 13px; border-radius: 999px;
  font-size: 12.5px; font-weight: 650; color: var(--muted);
  transition: background var(--ease-ui), color var(--ease-ui), border-color var(--ease-ui);
}
.chip:hover { border-color: var(--primary); color: var(--primary-ink); background: var(--primary-weak); }
.chip.is-on { background: var(--primary); border-color: var(--primary); color: var(--primary-contrast); }

/* --- Section tabs ----------------------------------------------------------
   These lived only in purchase.css, so the three Kharcha screens carried the
   markup and got none of the styling - "Kharcha book Recurring bills
   Categories & budgets" rendered as one run-on line of bare links. They are
   page furniture, like .summary-strip and .detail-grid above. purchase.css
   keeps its own copy and still wins on the purchase and supplier screens. */
.tab-row { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.tab-row a {
  padding: 8px 15px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel);
  color: var(--muted); font-weight: 650; font-size: 13px;
}
.tab-row a.active { background: var(--primary); border-color: var(--primary); color: var(--primary-contrast); }
.tab-row a:hover:not(.active) { border-color: var(--primary); color: var(--primary-ink); background: var(--primary-weak); }

.two-column-layout { display: grid; grid-template-columns: minmax(300px, .7fr) minmax(0, 1.4fr); gap: 18px; align-items: start; }
.search-input-row { display: flex; gap: 8px; }

.pagination { display: flex; justify-content: center; align-items: center; gap: 14px; color: var(--muted); font-size: 13px; }
.pagination a {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; padding: 7px 12px; font-weight: 650; color: var(--text-2);
}
.pagination a:hover { border-color: var(--primary); color: var(--primary-ink); background: var(--primary-weak); }

/* ==========================================================================
   Messages
   ========================================================================== */

.messages { margin-bottom: 15px; }
.alert {
  position: relative;
  overflow: hidden;
  padding: 12px 40px 12px 17px;
  border-radius: 11px;
  border: 1px solid;
  margin-bottom: 9px;
  font-size: 13.5px;
  line-height: 1.5;
}
.alert::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; background: currentColor; opacity: .55; }
.alert-success { background: var(--success-soft); border-color: var(--success-line); color: var(--success); }
.alert-danger { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.alert-info { background: var(--info-soft); border-color: var(--info-line); color: var(--info); }
/* Every messages.warning() in the application renders alert-warning, and this
   rule did not exist - so six call sites, including "your sign-in form
   expired" and the khata advance-payment prompt, have been showing as an
   unstyled box. The --warning tokens were already defined and simply unused. */
.alert-warning { background: var(--warning-soft); border-color: var(--warning-line); color: var(--warning); }
.alert strong { color: inherit; }
.alert-close { position: absolute; right: 8px; top: 6px; border: 0; background: transparent; font-size: 20px; color: inherit; opacity: .6; border-radius: 6px; padding: 0 6px; }
.alert-close:hover { opacity: 1; }
/* Offered by nav.js when a screen could not be opened, so the way out of a
   failed swap is a button rather than the browser's reload icon. */
.alert-retry {
  border: 1px solid currentColor; background: transparent; color: inherit;
  font: inherit; font-size: 12.5px; border-radius: 7px; padding: 2px 10px;
  margin-left: 4px; cursor: pointer; opacity: .85;
}
.alert-retry:hover { opacity: 1; background: rgba(0, 0, 0, .05); }

/* ==========================================================================
   "Something happened elsewhere in the shop"
   ---------------------------------------------------------------------------
   Shown only when a screen could NOT redraw itself, because somebody is typing
   on it or has a form part-filled. It never takes anything away - pressing it
   is what redraws - which is the whole contract of live updates: a phone at the
   other counter must never be able to wipe out what you are in the middle of.

   Fixed to the bottom rather than pushed into the page, so it cannot move the
   row somebody is reading or the button they are reaching for.
   ========================================================================== */
.live-pending {
  position: fixed; z-index: 80;
  left: 50%; bottom: 20px; transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 18px; border-radius: 999px;
  border: 1px solid var(--primary-line);
  background: var(--primary); color: var(--primary-contrast);
  font: inherit; font-size: 13.5px; font-weight: 600;
  box-shadow: var(--accent-glow); cursor: pointer;
  animation: live-pending-in 220ms ease-out;
}
.live-pending:hover { background: var(--primary-strong); }
.live-pending::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; opacity: .9;
}
@keyframes live-pending-in {
  from { opacity: 0; transform: translate(-50%, 10px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce) { .live-pending { animation: none; } }

/* ==========================================================================
   Searching as you type
   ---------------------------------------------------------------------------
   The sales history looks for bills while the cashier is still typing, so two
   small things have to be said clearly: that a search is in flight, and how
   many bills matched. In a rush both matter more than they look - a cashier
   who cannot tell "still looking" from "nothing found" types the name again.
   ========================================================================== */
.search-spinner {
  width: 15px; height: 15px; flex: none; align-self: center;
  border: 2px solid var(--line);
  border-top-color: var(--primary);
  border-radius: 50%;
  opacity: 0;
  transition: opacity 120ms ease;
}
/* htmx puts .htmx-request on the indicator for the life of the request. */
.search-spinner.htmx-request { opacity: 1; animation: search-spin 620ms linear infinite; }
@keyframes search-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .search-spinner.htmx-request { animation: none; }
}

.pl-count {
  margin: 0 0 10px; font-size: 13px; color: var(--muted);
}

/* ==========================================================================
   The swap indicator
   ---------------------------------------------------------------------------
   A navigation no longer reloads the page, so the browser's own loading bar
   never appears. Without something in its place a slow screen looks like a
   dead button and gets pressed again.

   A thin line across the very top of the window, not a spinner over the page:
   most swaps finish inside 200 ms and a modal spinner that comes and goes that
   fast reads as a flicker. The bar only becomes visible once nav.js sets
   .is-busy, and the animation is delayed, so a fast swap draws nothing at all.

   Fixed rather than inside the content area, because the content area is the
   element being replaced - a bar living in there would disappear halfway
   through the request it exists to report on.
   ========================================================================== */
.page-progress {
  position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 90;
  pointer-events: none; overflow: hidden;
}
.page-progress > span {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, transparent, var(--primary), transparent);
  transform: translateX(-100%); opacity: 0;
}
.page-progress.is-busy > span {
  opacity: 1;
  animation: page-progress-sweep 900ms ease-in-out .12s infinite;
}
@keyframes page-progress-sweep {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .page-progress.is-busy > span { animation: none; transform: none; opacity: .5; }
}

/* ==========================================================================
   Shared page layout
   ---------------------------------------------------------------------------
   These started life inside khata.css and purchase.css, which meant any screen
   that did not also load one of those stylesheets got the markup but none of
   the layout - a two-column page collapsing into an unstyled stack. They are
   page furniture, not khata furniture, so they belong in the stylesheet every
   page already loads. khata.css and purchase.css still carry their own copies
   and continue to win on their own pages.
   ========================================================================== */

.summary-strip {
  display: grid;
  /* auto-fit rather than a fixed four, because these strips carry four or five
     cards depending on the screen. */
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
.summary-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  box-shadow: var(--shadow-xs);
}
.summary-card span { display: block; color: var(--muted); font-weight: 650; font-size: 12px; }
.summary-card strong {
  display: block; margin-top: 5px;
  font-family: var(--font-display); font-size: 21px;
  letter-spacing: -.6px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.summary-card small { display: block; margin-top: 3px; font-size: 11.5px; color: var(--muted); }
.summary-card.is-due strong { color: var(--danger); }
.summary-card.is-good strong { color: var(--success); }
.summary-card.is-warn strong { color: var(--warning); }

.detail-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(300px, 380px);
  gap: 18px;
  align-items: start;
}
.detail-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.detail-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* A till is often a small or low-resolution screen; below this the side column
   has to go under the main one or both become unusable. */
@media (max-width: 1100px) {
  .detail-grid { grid-template-columns: minmax(0, 1fr); }
}

/* On a phone the side column goes first.
   -------------------------------------------------------------------------
   Stacked, the khata page reads: every open bill, then the whole ledger, then
   - right at the bottom - the box for taking money. A customer standing at the
   counter with two thousand rupees in his hand is the reason anybody opened
   the screen, and it was the last thing on it. One khata here runs to five
   open bills and about 1,500px of scrolling before the Amount field.

   The ledger is reference; the payment box is the job. Below the phone
   breakpoint the job comes first, and the history is still right underneath
   for anyone who wants to check it. Above it, the two-column layout already
   puts them side by side and nothing moves. */
@media screen and (max-width: 680px) {
  /* `order` and nothing else. The first version of this swapped the grid for a
     column flexbox, which reordered correctly and quietly undid the
     minmax(0, 1fr) above it: a grid track declared that way may shrink below
     its content, and a stretched flex item may not. Day close has a panel with
     a wide min-content inside it, so it grew the column to 352px inside a 322px
     page and put a horizontal scrollbar back on the screen - the exact fault
     P1 existed to remove. Grid items honour `order` just as flex items do, so
     there was never a reason to change the display type. */
  .detail-side { order: -1; }
}

.khata-table { width: 100%; border-collapse: collapse; }
.khata-table td, .khata-table th { vertical-align: top; }
.khata-table .text-right { font-variant-numeric: tabular-nums; }

/* A compact two-column table inside a narrow side panel.
   The global `th, td { white-space: nowrap }` above is right for a wide data
   table, because that sits inside a scrolling .table-wrap. A side panel has
   nowhere to scroll, so the same rule pushed the value column straight out
   past the panel edge and the reading simply vanished - the paper roll and
   every status badge on the settings and backup screens were invisible.
   Fixed layout keeps the two columns honest; wrapping keeps them inside. */
.detail-side .khata-table { table-layout: fixed; }
.detail-side .khata-table th,
.detail-side .khata-table td {
  white-space: normal;
  overflow-wrap: anywhere;
  padding: 9px 0;
}

.small-note { font-size: 12.5px; margin: 0 0 12px; color: var(--muted); }

/* Help text under a checkbox or radio label. Inline it runs straight into the
   label and, inside a table cell, into the next column - the global
   `th, td { white-space: nowrap }` gives it nowhere to break. */
.checkbox-row span small { display: block; margin-top: 2px; line-height: 1.45; }
/* Same for a hint under a control in a table cell. */
.field-hint { display: block; margin-top: 4px; font-size: 12px; color: var(--muted); }

/* ==========================================================================
   Reports
   ========================================================================== */

.report-metrics { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

/* Receipt styling lives in receipt.css (thermal roll layout). */

/* ==========================================================================
   Sign in / first run / expired page
   ========================================================================== */

.login-body {
  min-height: 100vh; min-height: 100dvh;
  display: grid; place-items: center;
  background:
    radial-gradient(1100px 520px at 8% -10%, var(--glow-a), transparent 60%),
    radial-gradient(900px 520px at 100% 110%, var(--glow-b), transparent 60%),
    var(--bg);
  padding: 20px;
}
.login-card {
  position: relative;
  width: 100%; max-width: 408px;
  background: var(--panel);
  padding: 34px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--line);
}
.login-logo { margin: 0 auto 17px; width: 52px; height: 52px; font-size: 18px; }
.login-card h1, .login-card > p, .login-card > small { text-align: center; display: block; }
.login-card > p { color: var(--muted); margin-bottom: 22px; }
.login-card > small { margin-top: 18px; font-size: 12px; }
.login-card .theme-toggle { position: absolute; top: 14px; right: 14px; }

.stack-form { display: flex; flex-direction: column; gap: 15px; }
.stack-form label { font-weight: 650; font-size: 13px; display: flex; flex-direction: column; gap: 7px; }
/* "Keep me signed in" reads as one tap target, so the box and its wording sit
   on a single row instead of inheriting the stacked label layout above. */
.stack-form label.checkbox-row { flex-direction: row; align-items: center; gap: 10px; font-weight: 600; color: var(--muted); cursor: pointer; }
.stack-form label.checkbox-row span { user-select: none; }
.login-card .btn-large { width: 100%; justify-content: center; }

/* ==========================================================================
   Quick code sheet
   ========================================================================== */

.cs-empty code, code {
  background: var(--surface-3);
  border-radius: 6px;
  padding: 2px 6px;
  font-family: var(--font-mono);
  font-size: .92em;
  color: var(--text-2);
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 950px) {
  /* Off-canvas drawer. Collapsing is pointless here - the sidebar is already
     out of the way - so the mini width is overridden back to full and the
     drawer always slides out with its labels showing, whatever the stored
     preference says. */
  :root, :root[data-nav="mini"] { --nav-w: 245px; }
  .sidebar { transform: translateX(-100%); transition: transform var(--ease-nav); box-shadow: var(--shadow-lg); }
  .sidebar.open { transform: translateX(0); }
  .main-area { margin-left: 0; width: 100%; }
  .money-bar { left: 0; }
  :root[data-nav="mini"] .brand { flex-direction: row; height: 68px; padding: 0 12px 0 16px; }
  :root[data-nav="mini"] .brand-text,
  :root[data-nav="mini"] .nav-label,
  :root[data-nav="mini"] .sidebar-user-text,
  :root[data-nav="mini"] .status-text { display: block; }
  :root[data-nav="mini"] .nav-menu { padding: 12px 10px; align-items: stretch; }
  :root[data-nav="mini"] .nav-menu a { width: auto; padding: 0 12px; justify-content: flex-start; gap: 11px; }
  :root[data-nav="mini"] .sidebar-user { flex-direction: row; }
  :root[data-nav="mini"] .sidebar-tools { flex-direction: row; }
  :root[data-nav="mini"] .nav-menu a::after,
  :root[data-nav="mini"] .tool-button::after { content: none; }
  .nav-collapse { display: none; }
  .nav-close { display: grid; }
  .mobile-menu { display: grid; }

  /* The drawer above the sheet, and the sheet above everything the page pins
     down. The figures are not arbitrary: New Sale fixes its Complete sale
     button at 45 and its F5 cost popup at 60, and pos.css loads after this
     file - so a drawer that merely tied with the popup lost to it. Kept below
     .ns-modal at 1200, because a dialog on top of the drawer is right. */
  .sidebar { z-index: 100; }

  /* One grey sheet, created once by main.js and parked on <body> - outside
     #main-area, because a swap replaces that whole element and would carry a
     backdrop living inside it away mid-navigation, or worse, leave one behind
     with nothing listening to dismiss it. */
  .nav-backdrop {
    position: fixed; inset: 0; z-index: 95;
    background: var(--overlay);
    opacity: 0; pointer-events: none;
    transition: opacity var(--ease-nav);
  }
  .nav-backdrop.is-on { opacity: 1; pointer-events: auto; }

  /* Hold the page still underneath. Removed by forceShut() on every exit the
     drawer has - tap, Escape, Back, navigation, rotating past the breakpoint -
     because a body left with overflow:hidden is a shop that cannot scroll and
     no obvious reason why. */
  body.nav-open { overflow: hidden; }
}
@media (prefers-reduced-motion: reduce) {
  .nav-backdrop { transition: none; }
  .dashboard-grid, .two-column-layout { grid-template-columns: minmax(0, 1fr); }
  .report-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 680px) {
  .page-content { padding: 14px; }
  .topbar { padding: 0 14px; }
  .page-heading { align-items: stretch; flex-direction: column; }
  .metric-grid, .report-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .metric-card { min-height: 104px; }
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .filter-bar { grid-template-columns: minmax(0, 1fr); }
  .topbar-title { display: none; }
}
@media (max-width: 420px) {
  .metric-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

/* ==========================================================================
   Print
   ---------------------------------------------------------------------------
   Paper is always white, whatever the screen is set to. Forcing the light
   tokens back means a shop working in dark mode does not print a black page.
   ========================================================================== */

/* ==========================================================================
   Connection and the outbox
   The sidebar line has to be readable at a glance from across the counter,
   because it is the only place the cashier is told a bill has not gone out.
   ========================================================================== */
.status-dot.is-wait { background: var(--warning); animation: outbox-pulse 1.8s ease-in-out infinite; }
.status-dot.is-bad { background: var(--danger); }
.sidebar-status.is-clickable { cursor: pointer; color: var(--nav-text); }
.sidebar-status.is-clickable:hover { text-decoration: underline; }

@keyframes outbox-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
@media (prefers-reduced-motion: reduce) { .status-dot.is-wait { animation: none; } }

.outbox-panel {
  position: fixed; right: 18px; z-index: 1400; width: min(360px, calc(100vw - 36px));
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  max-height: min(60vh, 520px); display: flex; flex-direction: column;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); overflow: hidden;
}
.outbox-panel[hidden] { display: none; }
.outbox-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--line); }
.outbox-close { background: none; border: 0; font-size: 20px; line-height: 1; color: var(--muted); cursor: pointer; }
.outbox-body { overflow-y: auto; padding: 6px 0; }
.outbox-empty { margin: 0; padding: 18px 14px; color: var(--muted); font-size: 13px; text-align: center; }
.outbox-row { padding: 10px 14px; border-bottom: 1px solid var(--line-soft, var(--line)); font-size: 13px; }
.outbox-row:last-child { border-bottom: 0; }
.outbox-row-main { display: flex; justify-content: space-between; gap: 10px; }
.outbox-row small { color: var(--muted); font-size: 11.5px; }
.outbox-row.is-blocked { background: var(--danger-soft); }
.outbox-error { margin: 6px 0 8px; color: var(--danger); font-size: 12px; }
.outbox-actions { display: flex; gap: 8px; }
.outbox-foot { padding: 10px 14px; border-top: 1px solid var(--line); }
.outbox-foot .btn { width: 100%; }
.outbox-status-link { display: block; margin-top: 8px; text-align: center; font-size: 12px; color: var(--muted); }

/* A sync note is a whole sentence, and left on one line it drags the table
   wider than the panel and pushes the amount off the screen. */
.late-bills td:first-child { max-width: 26rem; }
.late-bills .table-subtext { white-space: normal; }

/* ==========================================================================
   Money cards - cash / bank / khata
   Three buckets a shopkeeper checks in three different ways, kept visually
   apart so nobody reads them as one total.
   ========================================================================== */
.money-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; margin-bottom: 16px; }
/* Deliberately the same recipe as .metric-card above - sheen, 3px fading
   stripe, micro-cap label, tabular figures. These two card families sit one
   above the other on the dashboard, so any difference between them reads as
   sloppiness rather than as a distinction that means something. */
.money-card { position: relative; padding: 16px 18px; border-radius: var(--radius); border: 1px solid var(--line); background: var(--panel); box-shadow: var(--shadow-xs), var(--panel-sheen); }
.money-card::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 3px; border-radius: 0 3px 3px 0; opacity: .9; }
.money-card.is-cash::before { background: linear-gradient(to bottom, var(--success) 0%, var(--success) 38%, transparent 100%); }
.money-card.is-bank::before { background: linear-gradient(to bottom, var(--primary) 0%, var(--primary) 38%, transparent 100%); }
.money-card.is-khata::before { background: linear-gradient(to bottom, var(--warning) 0%, var(--warning) 38%, transparent 100%); }
.money-label { display: block; font-size: 10.5px; font-weight: 750; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.money-card strong { display: block; margin: 8px 0 2px; font-size: 25px; letter-spacing: -.8px; font-weight: 700; font-variant-numeric: tabular-nums; font-family: var(--font-display, inherit); }
.money-card small { display: block; color: var(--muted); font-size: 11.5px; line-height: 1.5; }
.money-net { margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--line); font-size: 12px; color: var(--text-2); }
.money-net.is-down { color: var(--danger); }

.money-detail { margin-bottom: 18px; }
.money-detail summary { cursor: pointer; font-size: 13px; color: var(--muted); padding: 6px 0; }
.money-detail summary:hover { color: var(--text); }
.money-detail-body { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2); }
.money-detail-body h4 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.money-row { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; font-size: 13px; border-bottom: 1px solid var(--line-soft, var(--line)); }
.money-row:last-of-type { border-bottom: 0; }

/* ==========================================================================
   Period picker - Today / Last 7 days / Custom
   Shared by the Dashboard and Reports. Ordinary links, not a widget: the whole
   point is that a shopkeeper sees this evening's takings without typing a date
   anywhere, and that the two screens ask the question in the same words.
   ========================================================================== */
.period-picker { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 0 0 18px; }
.period-tabs {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px;
}
.period-tab {
  padding: 7px 16px; border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--muted);
  white-space: nowrap; text-decoration: none;
  transition: color var(--ease-ui), background var(--ease-ui);
}
.period-tab:hover { color: var(--text); }
.period-tab.is-active {
  background: var(--panel); color: var(--text);
  box-shadow: var(--shadow-xs);
}
.period-custom { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
.period-custom label { display: flex; flex-direction: column; gap: 3px; font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.period-custom .form-input { padding: 7px 10px; font-size: 13px; }
.period-custom .btn { padding: 8px 18px; }
@media (max-width: 560px) {
  .period-tabs { width: 100%; }
  .period-tab { flex: 1; text-align: center; padding: 8px 10px; }
  .period-custom { width: 100%; }
  .period-custom label { flex: 1 1 40%; }
}

@media print {
  :root, :root[data-theme="dark"] {
    color-scheme: light;
    --bg: #fff; --panel: #fff;
    --surface-2: #fff; --surface-3: #fff;
    --text: #000; --text-2: #222; --muted: #444;
    --line: #ccc; --line-soft: #ddd;
    --primary: #000; --primary-ink: #000; --primary-weak: #fff; --primary-strong: #000;
    --success: #000; --danger: #000; --warning: #000; --info: #000;
    --success-soft: #fff; --danger-soft: #fff; --warning-soft: #fff; --info-soft: #fff;
    --sidebar: #000;
    --shadow-xs: none; --shadow-sm: none; --shadow: none; --shadow-lg: none;
  }
  body { background: #fff; }
  .sidebar, .topbar, .no-print, .messages, .theme-toggle { display: none !important; }
  .main-area { margin: 0; width: 100%; }
  .page-content { padding: 0; }
  .panel { border: 1px solid #ccc; box-shadow: none; }
}

/* ==========================================================================
   Touch
   --------------------------------------------------------------------------
   A finger is not a mouse pointer. Apple and Google both land on 44px as the
   smallest square somebody hits reliably, and several controls here were built
   at 32px because a cursor finds 32px perfectly well.

   Scoped to `pointer: coarse` rather than to a width, because the thing that
   decides this is the input device and not the screen. A 1280px tablet needs
   the bigger targets; a 380px browser window on a desktop does not, and
   growing everything there would only make the layout worse for no gain.
   ========================================================================== */
@media (pointer: coarse) {
  .tool-button { width: 44px; height: 44px; }
  .nav-menu a { height: 46px; }
  :root[data-nav="mini"] .nav-menu a { width: 46px; height: 46px; }

  /* Anything a thumb has to find. Height rather than min-height so a control
     that was already taller keeps its own size. */
  .btn, .btn-small, .pay-option, .filter-bar select, .filter-bar input,
  .form-input, .page-heading .btn { min-height: 44px; }

  /* Row actions in list screens - the pencil and cross at the end of a line -
     were the smallest targets in the application. */
  .row-remove, .icon-btn, .table-action { min-width: 44px; min-height: 44px; }

  /* A checkbox is 16px of ink with nothing around it. The label is the target
     people actually aim at, so it carries the height instead. */
  .checkbox-row { min-height: 44px; }

  /* The filter chips above a list - "All", "Unpaid", "This month" - were 33px
     and sit in a row, so a near miss lands on the neighbouring filter and
     silently changes what the shopkeeper is looking at. */
  .chip, .icon-button { min-height: 44px; }

  /* The hamburger had the height but not the width: 36px of target in the one
     axis a thumb coming from the screen edge actually misses in. It is the
     first control on every screen, so it is the last one that should need a
     second try. */
  .icon-button, .nav-close { min-width: 44px; min-height: 44px; }

  /* A product name in a list row is the whole point of the row and was the
     smallest target on the screen at 19px. Padding rather than height, so the
     row keeps its own rhythm. */
  .row-name { display: inline-block; padding: 12px 0; }

  /* The brand block is a link to the dashboard. */
  .brand-id { min-height: 44px; }
}

/* ==========================================================================
   Wide tables become cards
   --------------------------------------------------------------------------
   A sales list is seven columns and 877px wide. In a 360px window it sits in a
   box that scrolls sideways, so nothing is lost - and the Total column, the
   only reason anybody opened the screen, is off the right-hand edge. Reading
   what a customer paid meant swiping the table, on every row.

   Below 680px each row becomes a card and each cell prints the column heading
   that static/js/responsive-tables.js copied onto it. Nothing is hidden and
   nothing is truncated, which is the whole point: the alternative pattern -
   dropping "less important" columns on small screens - decides on the
   shopkeeper's behalf which of his own figures he is allowed to see.

   Scoped to `screen` deliberately. A bare width query applies to `all`, print
   included, and a customer's khata statement is a document meant to be handed
   over on paper: run off a 58mm slip printer it is about 219 CSS pixels wide,
   which would have turned a statement into a column of cards.
   ========================================================================== */
@media screen and (max-width: 680px) {
  /* The wrapper's whole job was to scroll sideways. There is nothing left to
     scroll, and leaving it would trap the cards in a box of their own. */
  .table-wrap { overflow-x: visible; }

  .table-wrap > table,
  .table-wrap > table > tbody,
  .table-wrap > table > tbody > tr,
  .table-wrap > table > tbody > tr > td { display: block; width: auto; }

  /* The headings are on the cells now. */
  .table-wrap > table > thead { display: none; }

  .table-wrap > table > tbody > tr {
    margin: 0 0 10px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--panel);
    box-shadow: var(--shadow-xs);
  }
  .table-wrap > table > tbody > tr:last-child { margin-bottom: 0; }

  .table-wrap > table > tbody > tr > td {
    display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
    padding: 5px 0;
    border: 0;
    /* th/td are nowrap globally, which is right for a scrolling table and
       wrong for a card: a long product name has to wrap rather than run out
       of the side of it. */
    white-space: normal;
    text-align: left;
  }
  .table-wrap > table > tbody > tr > td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    color: var(--muted);
    font-size: 11px; font-weight: 800;
    text-transform: uppercase; letter-spacing: .05em;
  }
  /* An action column has a blank heading, and a blank label leaves a gap where
     a word should be. */
  .table-wrap > table > tbody > tr > td[data-label=""]::before { content: none; }
  .table-wrap > table > tbody > tr > td[data-label=""] { justify-content: flex-end; }

  /* "No sales found." spans the table and is not a record. */
  .table-wrap > table > tbody > tr[data-single] { border: 0; background: none; box-shadow: none; }
  .table-wrap > table > tbody > tr[data-single] > td { display: block; text-align: center; }
  .table-wrap > table > tbody > tr[data-single] > td::before { content: none; }

  /* A row's own actions, given room to be pressed. */
  .table-wrap > table > tbody > tr > td .btn,
  .table-wrap > table > tbody > tr > td .btn-small { min-height: 40px; }
  .table-subtext { display: block; }
}
