/* ============================================================================
   NexOne — house styles
   ----------------------------------------------------------------------------
   Colour is Bootstrap's own. Nothing here overrides primary / secondary /
   success / warning / danger / info — every hue below is read from a --bs-*
   variable, so the palette, and both light and dark themes, come straight from
   the framework.

   What this file adds is the furniture Bootstrap has no component for: POS
   density, the selected-row cursor, the tender panel, quick picks, and the
   receipt. No build step: this is the file, served as-is.

   This file is the design system. The screens are the reference now, so a new
   one is checked against the screens beside it rather than against a mockup.
   ============================================================================ */

:root{
  --bs-border-radius:    .4rem;
  --bs-border-radius-sm: .3rem;
  --bs-border-radius-lg: .55rem;

  --bs-font-sans-serif: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --bs-body-font-size:  .875rem;

  /* Layout roles, not colours: the page sits back, records sit forward. */
  --nx-page:    var(--bs-secondary-bg);
  --nx-surface: var(--bs-body-bg);
  --nx-sunken:  var(--bs-tertiary-bg);
}

html,body{ height:100% }
body{ background:var(--nx-page) }

/* ----------------------------------------------------------------- type -- */

/* Machine identifiers only — IMEI, SKU, document numbers, receipts. Fixed
   advance width makes a transposed digit visibly out of line. */
.mono{ font-family:"DM Mono",ui-monospace,Consolas,monospace; font-size:.88em; letter-spacing:-.01em }

/* Any column of digits read down the page. */
.tnum{ font-variant-numeric:tabular-nums lining-nums }

.h-page{ font-size:1.25rem; font-weight:700; letter-spacing:-.02em; margin:0 }
.lbl{ font-size:.75rem; font-weight:500; color:var(--bs-secondary-color); margin-bottom:.2rem }
.meta{ font-size:.75rem; color:var(--bs-secondary-color) }

/* ------------------------------------------------------------- surfaces -- */

.card{ --bs-card-bg:var(--nx-surface); --bs-card-cap-bg:transparent }
.card-header{ font-weight:600 }
.navbar{ background:var(--nx-surface) }

/* --------------------------------------------------------------- tables -- */
/* Density is the point in a POS: more rows visible, fewer scrolls. */

.table{ --bs-table-bg:transparent; margin-bottom:0; font-size:.8125rem }
.table > thead > tr > th{
  font-size:.6875rem; font-weight:600; letter-spacing:.04em; text-transform:uppercase;
  color:var(--bs-secondary-color); background:var(--nx-sunken);
  padding:.5rem .6rem; white-space:nowrap;
}
.table > tbody > tr > td{ padding:.45rem .6rem; vertical-align:middle }
.table-stick thead th{ position:sticky; top:0; z-index:2 }

/* The selected row. In a keyboard-driven app this is the application's cursor,
   so it is unmistakable — not a hover tint. */
tbody tr.is-on > td{
  background:var(--bs-primary-bg-subtle) !important;
  border-color:var(--bs-primary-border-subtle);
}
tbody tr.is-on > td:first-child{ box-shadow:inset 3px 0 0 var(--bs-primary) }
tbody tr.row-pick{ cursor:pointer }

/* ---------------------------------------------------------------- pills -- */
/* Bootstrap 5.3 subtle triplets, so these follow the theme into dark mode. */

.pill{
  display:inline-flex; align-items:center; gap:.35rem;
  font-size:.6875rem; font-weight:600; line-height:1;
  padding:.28rem .55rem; border-radius:2rem; border:1px solid; white-space:nowrap;
}
.pill-success{ background:var(--bs-success-bg-subtle); color:var(--bs-success-text-emphasis); border-color:var(--bs-success-border-subtle) }
.pill-warning{ background:var(--bs-warning-bg-subtle); color:var(--bs-warning-text-emphasis); border-color:var(--bs-warning-border-subtle) }
.pill-danger { background:var(--bs-danger-bg-subtle);  color:var(--bs-danger-text-emphasis);  border-color:var(--bs-danger-border-subtle) }
.pill-info   { background:var(--bs-info-bg-subtle);    color:var(--bs-info-text-emphasis);    border-color:var(--bs-info-border-subtle) }
.pill-muted  { background:var(--bs-secondary-bg);      color:var(--bs-secondary-color);       border-color:var(--bs-border-color) }

/* ------------------------------------------------------------ popups -- */
/* A popup that is a form wraps its header, body and footer in the <form>.
   The dialog lays those out as its own flex children and scrolls only the
   body, so the form must not add a box of its own between them. */

#nx-modal-body > form{ display:contents }

/* The "are you sure" dialog asked from inside a popup: one layer up, with
   its own backdrop dimming the popup as well as the page. */
#nx-confirm.nx-over-dialog{ z-index:1065 }
.modal-backdrop.nx-over-dialog{ z-index:1060 }

/* ---------------------------------------------------------- validation -- */
/* asp-validation-summary always renders, even when the model is valid. The
   tag helper marks that state with this class and leaves an empty list for
   client-side validation to fill. Bootstrap's alert is visible by default, so
   without this the empty red box sits on every form. */

.validation-summary-valid{ display:none }

/* ------------------------------------------------------- stock levels -- */
/* An on-hand figure that needs attention is coloured rather than flagged
   with an icon: orange at or below the reorder level, red once nothing is
   left. Bold as well, so the difference survives a grayscale printout. */

.qty-low{ color:var(--bs-orange); font-weight:700 }
.qty-out{ color:var(--bs-danger); font-weight:700 }

/* -------------------------------------------------------- POS furniture -- */

/* The amount due is the content of a till, so it is the largest thing present. */
.due-amount{
  font-size:clamp(1.9rem,3.6vw,2.6rem); font-weight:800; letter-spacing:-.04em; line-height:1;
  font-variant-numeric:tabular-nums lining-nums;
}

/* Scan field: the first thing a cashier touches, so it reads as live. */
.scanbar{
  position:relative; display:flex; align-items:center; gap:.6rem;
  padding:.5rem .75rem; border-bottom:1px solid var(--bs-border-color);
}
.scanbar input{
  flex:1; border:0; background:transparent; outline:none;
  font-size:1rem; font-weight:500; color:var(--bs-body-color); padding:.15rem 0;
}
.scanbar input::placeholder{ color:var(--bs-secondary-color); font-weight:400 }

/* Search results, hanging under the scan bar.

   The same bar does two jobs: a scanner types a code and presses Enter, a
   person types a name and reads. The list only exists for the second, so it
   overlays the cart rather than pushing it down — nothing should move under a
   cashier who is about to press Enter. */

.findlist{
  position:absolute; top:100%; left:0; right:0; z-index:20;
  max-height:22rem; overflow-y:auto;
  background:var(--nx-surface);
  border:1px solid var(--bs-border-color);
  border-radius:0 0 var(--bs-border-radius) var(--bs-border-radius);
  box-shadow:0 .5rem 1rem rgba(0,0,0,.08);
}
.findrow{
  display:flex; align-items:center; gap:.6rem; width:100%;
  padding:.45rem .75rem; text-align:left;
  background:transparent; border:0; border-bottom:1px solid var(--bs-border-color);
  color:var(--bs-body-color); cursor:pointer;
}
.findrow:last-child{ border-bottom:0 }
.findrow:hover:not(:disabled), .findrow.is-on{ background:var(--bs-primary-bg-subtle) }

/* Out of stock here. Shown rather than hidden, because "we stock it, just not
   today" is a useful answer across a counter — but it cannot be added. */
.findrow:disabled{ cursor:not-allowed; opacity:.55 }

.findrow .f-name{ font-size:.8125rem; font-weight:500; line-height:1.25 }
.findrow .f-meta{ font-size:.6875rem; color:var(--bs-secondary-color) }
.findrow .f-right{ margin-left:auto; text-align:right; white-space:nowrap }

/* The same list inside a form row. In flow rather than overlaid, because the
   row sits in a scrolling table that would clip anything hanging out of it. */
.findlist.findlist-inline{
  position:static; max-height:16rem; margin-top:.25rem;
  border-radius:var(--bs-border-radius);
}

/* A line with no IMEI yet. Loud on purpose: it is the one thing standing
   between the cashier and taking payment. */
.needs-unit{ background:var(--bs-warning-bg-subtle) }

/* Line toolbar. Acts on the selected row — the same actions the function-key
   legend used to list, as real controls. */
.linebar{
  display:flex; flex-wrap:wrap; align-items:center; gap:.35rem;
  padding:.4rem .6rem; border-bottom:1px solid var(--bs-border-color);
  background:var(--nx-sunken);
}
.linebar .sel-name{
  font-size:.75rem; color:var(--bs-secondary-color);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:16rem;
}

/* What has already been taken towards this sale. One line per tender, so a
   split payment is read back the way it was built up. */
.taken-row{
  display:flex; align-items:center; gap:.5rem;
  padding:.35rem .6rem; font-size:.8125rem;
  border-bottom:1px solid var(--bs-border-color);
}
.taken-row:last-child{ border-bottom:0 }

/* What one tender needs recorded.

   Holds the height the keypad used to, so the dialog does not jump as the
   cashier moves between Cash, Card, Cheque and Credit -- a payment dialog that
   resizes under somebody counting notes is worse than one that wastes a little
   space. */
.tender-panel{ min-height:16rem }

/* Matches under the receive-payment search box. */
.receive-find .list-group{ max-height:18rem; overflow:auto; }

/* The card scheme picker. Bootstrap btn-check, sized down to the rest of the
   dialog rather than left at button scale. */
.tender-panel .btn-group .btn{ font-size:.6875rem; padding:.3rem .2rem }

/* The IMEIs of one row, side by side under its description.

   A row is a product, so the units it is selling have to be visible without
   giving each one a row of its own. Each chip is one phone, and carries the
   control that takes that phone back out of the sale. */
.imei-strip{ display:flex; flex-wrap:wrap; gap:.25rem; margin-top:.25rem }
.imei-chip{
  display:inline-flex; align-items:center; gap:.3rem;
  font-family:"DM Mono",ui-monospace,Consolas,monospace;
  font-size:.6875rem; line-height:1;
  padding:.22rem .3rem .22rem .5rem; border-radius:2rem;
  background:var(--bs-info-bg-subtle); color:var(--bs-info-text-emphasis);
  border:1px solid var(--bs-info-border-subtle); white-space:nowrap;
}
.imei-chip button{
  display:inline-flex; align-items:center; justify-content:center;
  width:1rem; height:1rem; padding:0; border:0; border-radius:50%;
  background:transparent; color:inherit; opacity:.6;
  font-size:.8125rem; line-height:1; cursor:pointer;
}
.imei-chip button:hover{ opacity:1; background:var(--bs-danger-bg-subtle) }

/* A phone a parked sale was holding that is no longer on the shelf.

   Struck through rather than removed: the cashier needs to know which box to
   stop looking for. The slot it stood in is already counted as pending, so
   this chip says what happened and nothing more. */
.imei-chip.is-gone{
  background:var(--bs-danger-bg-subtle); color:var(--bs-danger-text-emphasis);
  border-color:var(--bs-danger-border-subtle); text-decoration:line-through;
}

/* Quantity.

   Two kinds of row sit in this column: an accessory whose quantity is typed,
   and a phone whose quantity is however many IMEIs the row holds. They share
   every dimension — width, padding, and a border that is merely transparent on
   the one that cannot be typed in — so the digits land on the same axis and
   both rows are the same height.

   Sharing those dimensions is not enough on its own. A number input reserves
   room inside its box for the spin buttons, so its digits stop short of the
   right edge while the phone row's digits do not, and the column reads as two
   columns half a character apart. The spinners are removed rather than
   mimicked: they are a mouse-sized target in a screen driven by the keyboard,
   and the arrow keys still step the value without them. Both boxes are also
   pinned to the same vertical alignment, since a span and an input do not sit
   on a shared baseline by default.

   What is left is the one difference that means something: a box is drawn
   exactly where a cashier can type, and nowhere else. */
.qty-cell{
  display:inline-block; width:3.5rem; padding:.15rem .35rem; text-align:right;
  font-size:.8125rem; font-variant-numeric:tabular-nums lining-nums;
  color:var(--bs-body-color); vertical-align:middle; line-height:1.5;
  border:1px solid transparent; border-radius:var(--bs-border-radius-sm);
}

/* Sized to a till quantity — nobody sells four figures of screen protectors —
   and styled down so a column of them does not read as a form. */
.qty-inline{
  background:var(--nx-surface);
  border-color:var(--bs-border-color);
  -moz-appearance:textfield; appearance:textfield;
}
.qty-inline::-webkit-outer-spin-button,
.qty-inline::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0 }

/* One unit in the picker. A whole-row label, so the tick target is the line
   and not a checkbox the size of a fingernail. */
.pickrow{
  display:flex; align-items:center; gap:.6rem; width:100%;
  padding:.4rem .75rem; margin:0; cursor:pointer;
  border-bottom:1px solid var(--bs-border-color);
}
.pickrow:last-child{ border-bottom:0 }
.pickrow:hover{ background:var(--bs-primary-bg-subtle) }

/* Quick picks: accessories are tapped, not scanned. */
.quickpick{
  display:flex; flex-direction:column; align-items:flex-start; gap:.15rem;
  padding:.5rem .6rem; text-align:left; min-width:9.5rem;
  background:var(--nx-surface); color:var(--bs-body-color);
  border:1px solid var(--bs-border-color); border-radius:var(--bs-border-radius);
  cursor:pointer; transition:border-color .12s, background .12s;
}
.quickpick:hover{ border-color:var(--bs-primary); background:var(--bs-primary-bg-subtle) }
.quickpick:disabled{ cursor:not-allowed; opacity:.45 }
.quickpick:disabled:hover{ border-color:var(--bs-border-color); background:var(--nx-surface) }
.quickpick .qp-name{ font-size:.75rem; font-weight:600; line-height:1.2 }
.quickpick .qp-price{ font-size:.6875rem; color:var(--bs-secondary-color); font-variant-numeric:tabular-nums }

/* Receipt preview: 80mm roll. */
.receipt{ font-family:"DM Mono",ui-monospace,monospace; font-size:.625rem; line-height:1.8; max-width:19rem }
.receipt .r{ display:flex; justify-content:space-between; gap:.5rem }
.receipt hr{ border:0; border-top:1px dashed var(--bs-border-color); margin:.4rem 0; opacity:1 }

/* Keys appear in exactly one place: the shortcuts sheet. Never in the chrome. */
kbd,.kbd{
  display:inline-block; font-family:var(--bs-font-sans-serif);
  font-size:.6875rem; font-weight:600; line-height:1.4;
  padding:.12rem .4rem; border-radius:.3rem;
  background:var(--bs-secondary-bg); color:var(--bs-body-color);
  border:1px solid var(--bs-border-color); box-shadow:none;
}

/* Every interactive element must show keyboard focus — this app is driven by
   Tab and arrows, not by pointing. */
:where(a,button,input,select,textarea,[tabindex]):focus-visible{
  outline:2px solid var(--bs-primary); outline-offset:2px;
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation:none !important; transition:none !important }
}

/* ============================================================================
   Application shell
   ============================================================================ */

.nx-shell{ display:flex; flex-direction:column; min-height:100dvh }
.nx-main{ flex:1; min-width:0 }

/* htmx swaps this region and nothing else, so the navbar, its focus and any
   open dropdown survive navigation. */
#nx-content{ position:relative }

/* Request feedback. htmx toggles .htmx-request on the indicator, so a slow
   query shows progress instead of a dead screen. */
.nx-progress{
  position:fixed; inset:0 0 auto 0; height:2px; z-index:1080;
  background:var(--bs-primary); transform:scaleX(0); transform-origin:0 50%;
  opacity:0; pointer-events:none;
}
.nx-progress.htmx-request{ opacity:1; animation:nx-progress 1.4s ease-out infinite }
@keyframes nx-progress{
  0%{ transform:scaleX(0) } 60%{ transform:scaleX(.7) } 100%{ transform:scaleX(1) }
}

/* Messages land top centre, below the navbar rather than over it. After a save
   or a delete the eye is at the top of the record it just changed, so a corner
   is the one place it is not looking. The offset clears the navbar, which sits
   in normal flow and would otherwise be covered by a fixed container. */
#nx-toasts{ top:2.6rem }

/* Dim a region being replaced, so a stale table cannot be mistaken for a
   fresh one during the swap. */
.htmx-request.nx-dim, .htmx-request .nx-dim{ opacity:.55; transition:opacity .15s }

/* ============================================================================
   Sign-in
   ============================================================================ */

.nx-signin{
  min-height:100dvh; display:grid; place-items:center; padding:1.5rem;
  background:var(--bs-secondary-bg);
}
.nx-signin-card{ width:100%; max-width:23rem }
.nx-wordmark{ font-weight:800; letter-spacing:-.04em; font-size:1.5rem }

/* ============================================================================
   Receipt

   An 80mm thermal roll. Monospaced because the columns have to line up on
   paper that has no fonts of its own, and sized in millimetres because that is
   what the roll actually is.
   ============================================================================ */

.nx-receipt{
  font-family:"DM Mono",ui-monospace,monospace;
  font-size:9.5pt; line-height:1.45; width:72mm; margin:0 auto; color:#000;
}
.nx-receipt hr{ border:0; border-top:1px dashed #000; margin:1.5mm 0; opacity:1 }
.nx-receipt .r-row{ display:flex; justify-content:space-between; gap:3mm }
.nx-receipt .r-row > span:last-child{ white-space:nowrap }
.nx-receipt .r-sub{ font-size:8pt; padding-left:3mm; opacity:.85 }
.nx-receipt .r-center{ text-align:center }
.nx-receipt .r-bold{ font-weight:600 }
.nx-receipt .r-normal{ font-weight:400 }

/* The company above the branch that sold it. */
.nx-receipt .r-shop{ font-size:13pt; font-weight:700; letter-spacing:.5px }
.nx-receipt .r-head{ letter-spacing:2px }

/* The item table.

   A grid rather than a flex row, because five columns on 72mm only stay in
   their lanes if the lanes are declared. The product name sits on its own
   line above the figures: at this width a name long enough to matter would
   otherwise push the money off the paper or wrap into the column beside it.

   Dropped to 8.5pt for the same reason — five columns of digits need the
   characters to be narrower than the rest of the receipt, and figures stay
   legible at a size body text would not. */
.nx-receipt .r-grid{
  display:grid; grid-template-columns:1fr 1.5fr .5fr 1fr 1.6fr;
  gap:1.5mm; font-size:8.5pt;
}
.nx-receipt .r-grid .r-num{ text-align:right; white-space:nowrap }
.nx-receipt .r-cols{ font-size:7.5pt; letter-spacing:.3px }
.nx-receipt .r-item{ font-weight:600; margin-top:1mm }
.nx-receipt .r-total{ font-size:11pt }
.nx-receipt .r-slogan{ font-size:8pt; margin-top:2mm; opacity:.85 }
.nx-receipt .r-contact{ font-size:7pt; opacity:.75 }

/* ============================================================================
   Shelf labels

   50 by 25 millimetres, the commonest self-adhesive stock, tiled across an A4
   sheet. Sized in millimetres for the same reason the receipt is: a label is a
   physical object, and getting it wrong wastes a roll rather than a pixel.
   ============================================================================ */

.nx-labels{
  display:grid; grid-template-columns:repeat(4,50mm); gap:0;
  justify-content:center; color:#000;
}
.nx-label{
  width:50mm; height:25mm; padding:1.5mm;
  display:flex; flex-direction:column; justify-content:space-between;
  overflow:hidden; text-align:center;
  /* A hairline is a cutting guide on plain paper and invisible on die-cut
     stock, where the label edge lands on it. */
  outline:.1mm dashed #bbb; outline-offset:-.05mm;
}
.nx-label-name{
  font-size:7pt; font-weight:600; line-height:1.1;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* Scaled down to fit if a long SKU makes the symbol wider than the label.
   Proportionally, so the bars keep their ratios and stay readable. */
.nx-label svg{ display:block; margin:0 auto; max-width:100%; height:auto }
.nx-label-foot{ display:flex; align-items:baseline; justify-content:center; gap:2mm }
.nx-label-sku{ font-family:"DM Mono",ui-monospace,monospace; font-size:6.5pt }
.nx-label-price{ font-size:9pt; font-weight:700; font-variant-numeric:tabular-nums }

/* ============================================================================
   Sales report

   A4, unlike the receipt. The named page on the receipt would otherwise put
   one day's sales onto a till roll.
   ============================================================================ */

.nx-report{
  font-size:10pt; line-height:1.35; color:#000; max-width:194mm;
}
.nx-report-letterhead{
  text-align:center; margin-bottom:5mm; padding-bottom:3mm;
  border-bottom:.4mm solid #000;
}
.nx-report-shop{ font-size:16pt; font-weight:700; letter-spacing:.5px }
.nx-report-branch{ font-weight:600; margin-bottom:.5mm }
.nx-report h1{ font-size:14pt; font-weight:700; margin:0 0 2mm }
.nx-report-filters{ font-size:9pt; margin-bottom:4mm }
.nx-report table{ width:100%; border-collapse:collapse }
.nx-report th, .nx-report td{ border-bottom:.2mm solid #ccc; padding:1.5mm 2mm; text-align:left }
.nx-report th{
  font-size:8pt; font-weight:600; text-transform:uppercase; letter-spacing:.3px;
  border-bottom:.4mm solid #000;
}
.nx-report .tnum{ text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap }
.nx-report .mono{ font-family:"DM Mono",ui-monospace,monospace }
.nx-report-void{ opacity:.55 }
.nx-report tfoot td{
  font-weight:600; border-bottom:0; border-top:.4mm solid #000; padding-top:2.5mm;
}
.nx-report-footer{
  text-align:center; margin-top:6mm; padding-top:3mm;
  border-top:.4mm solid #000; font-size:9pt;
}
.nx-report-printed{ font-size:8pt; opacity:.7; margin-bottom:3mm }
.nx-report-slogan{ font-size:8pt; opacity:.7 }
.nx-report-contact{ font-size:8pt; opacity:.65 }

/* Grouped reports (/reports): a category heading, a product or supplier
   heading under it, then subtotal and total rows. The same classes serve the
   screen table and the A4 print, so the two cannot be laid out differently. */
.nx-report-group th{ font-weight:700; padding-top:.6rem }
.nx-report-section th{ font-weight:600; padding-left:1rem }
.nx-report-indent{ padding-left:1rem !important }
.nx-report-subtotal td{ font-weight:600; border-top:1px solid currentColor }
.nx-report-total td{ font-weight:700; border-top:2px solid currentColor }
.nx-report-return td{ font-style:italic }
/* On paper the headings sit in the body, so they must not take the column
   header's small capitals and heavy rule, and a document number must stay on
   one line however narrow its column gets. */
.nx-report .nx-report-group th,
.nx-report .nx-report-section th{
  font-size:inherit; text-transform:none; letter-spacing:normal;
  border-bottom:.2mm solid #ccc;
}
.nx-report .mono{ white-space:nowrap }
.nx-report-table thead th{ position:sticky; top:0; background:var(--bs-body-bg); z-index:1 }

/* ============================================================================
   Printing

   Off-screen until printed. Kept in the page rather than opened in a window so
   printing never steals focus from the scan box.
   ============================================================================ */

#print-area{ position:absolute; left:-10000px; top:0 }

/* Two page sizes, because two different things get printed. A named page lets
   the receipt claim an 80mm roll while everything else stays on a sheet;
   without it, labels would print one to a till-roll page. */
@page{ size:A4; margin:8mm }
@page nx-receipt{ size:80mm auto; margin:3mm }

.nx-receipt{ page:nx-receipt }

@media print{
  /* Only the print area prints. Everything else is screen furniture. */
  body > *:not(#print-area){ display:none !important }

  #print-area{ position:static; left:auto }
  .nx-receipt{ width:auto }

  /* Browsers drop backgrounds by default, which would print white bars on
     white paper. */
  .nx-label{ break-inside:avoid; -webkit-print-color-adjust:exact; print-color-adjust:exact }
}
