/* The design system layer: scales, primitives, and the screens rebuilt on them.
 *
 * WHY A SECOND FILE. style.css grew to ~5k lines of per-screen rules written before there
 * was a scale to write them against, so spacing, radii and control heights were chosen per
 * component and drifted. This file holds the vocabulary — spacing, type, control sizes, one
 * focus ring, one motion curve — plus the primitives that replace every browser-native
 * control the app was still shipping. It loads AFTER style.css so the vocabulary wins;
 * legacy rules whose markup is gone get deleted as each screen is converted.
 *
 * Colour roles stay in style.css: those were already right (three surface tiers, semantic
 * danger/success/warning, both themes complete) and re-declaring them here would create two
 * sources of truth for the one thing that must not fork.
 *
 * MEASURED PROBLEMS THIS ANSWERS (from a pass through the running app):
 *   - 133 native <input type=number> on one results page; a native <select> and a native
 *     <input type=date> on the order page, the date rendering as 08/19/2026 (US) two
 *     centimetres from a pill reading 2026-08-19;
 *   - the results table trapped in a 610px inner scroller holding 6272px of rows;
 *   - card inside card inside card on /cart (page > cart > supplier > table), each with its
 *     own border and its own header band;
 *   - five decorative metric tiles above the search box, each with a differently tinted
 *     icon square, none of which answers a question anybody has;
 *   - prices as "4.37 EUR" in a table and "13,97 EUR" in that same table's total;
 *   - an order row that names a supplier and a number but not one thing that was ordered.
 */

:root {
  /* Spacing: a 4px base with the two half-steps that dense tables actually need. One
     scale, so a gap is chosen from a list instead of invented per component. */
  --s-1: 2px;
  --s-2: 4px;
  --s-3: 6px;
  --s-4: 8px;
  --s-5: 12px;
  --s-6: 16px;
  --s-7: 20px;
  --s-8: 24px;
  --s-9: 32px;
  --s-10: 40px;
  --s-11: 56px;

  /* Type: 6 sizes, and no component may use a seventh. 12.5px is the table/UI body size —
     small enough to be dense, large enough to read a part number off a screen. */
  --t-xs: 10.5px;
  --t-sm: 11.5px;
  --t-md: 12.5px;
  --t-base: 13.5px;
  --t-lg: 16px;
  --t-xl: 20px;
  --t-2xl: 26px;

  --lh-tight: 1.25;
  --lh-normal: 1.5;

  /* Weights as tokens: the app had 640/660/680/720/760 in use, which is five weights doing
     the work of three and no two headings agreeing. */
  --w-normal: 400;
  --w-medium: 520;
  --w-semi: 620;
  --w-bold: 700;

  /* Radii on a 2px grid. Was 6/9/13/18 — off-grid values that never nest cleanly.
   *
   * WHICH STEP A THING TAKES, because agreeing on the values was only half of it and the
   * other half is what the owner actually saw: a 12px card above a 6px table whose 8px
   * header band overhung its own outline, three radii in one screen.
   *
   *   --r-sm   a CONTROL: button, input, select, chip, the small square of an icon button.
   *   --r-md   a NESTED surface: a block inside a card — an alert, a row, a group, a table
   *            wrapper standing inside one of those.
   *   --r-lg   a SURFACE the page itself puts down: card, panel, popover, toast, the head
   *            band, a table that IS the page's own content rather than a block inside it.
   *   --r-xl   a full-surface OVERLAY — the login card, and the three modal dialogs. It is
   *            the one step that never appears inside the page's own flow.
   *
   * AND THE RULE THAT IS EASIEST TO BREAK: a table paints its own header corners, because
   * a rounded wrapper cannot clip them without also clipping the row menus that open past
   * its edge. So `thead th:first-child` / `:last-child` must carry the SAME step as the
   * wrapper around them. tests/test_design_tokens.py asserts exactly that pairing.
   */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 16px;
  /* A pill. Lives here with the rest of the scale so style.css's --radius-full can alias
     it and there is exactly one definition of every radius in the app. */
  --r-full: 9999px;

  /* Control heights. Every button, input, select and pill picks one of these three, which
     is what makes a row of mixed controls line up without per-case padding. */
  --h-sm: 26px;
  --h-md: 32px;
  --h-lg: 38px;

  /* Motion: one curve, three durations, all short. Anything above 200ms on a click feels
     like lag rather than polish. */
  --dur-1: 90ms;
  --dur-2: 140ms;
  --dur-3: 200ms;
  --ease-out: cubic-bezier(0.2, 0.8, 0.3, 1);

  /* One focus ring for the whole app, replacing the browser's. Two layers so it reads on
     both a light surface and a saturated button. */
  --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--ring);
  --focus-ring-tight: 0 0 0 2px var(--ring);

  /* WHERE THE PAGE ACTUALLY BEGINS — and it is the header's bottom edge EXACTLY.
     `.app-header` floats at `top: 14px` and is 64px + 2px of border tall, so it ends at 80.
     Every sticky thing under it was pinned at `top: 0`, which meant each one slid under the
     header and stopped being readable at exactly the moment it became useful. One token, so
     a header that changes height cannot leave four sticky offsets behind.

     It said 82, not 80. Those two pixels were a slot the width of the page through which
     rows scrolled in full view, between the header's bottom and the table header pinned
     below it — the "plyšys" in the report, and on a phone it was four pixels, not two. The
     value must be the header's bottom and nothing else: `.page-top-cover` is sized from
     this same token, so any daylight here becomes daylight there too. */
  --sticky-top: 80px;

  /* One column-header row: 39px of height plus its 1px top border. The group divider pins
     exactly this far below the column header, so the supplier a row belongs to is readable
     without either row covering the other.

     There used to be THREE numbers behind these two offsets: `--sticky-top` (82), a
     `--header-sticky-h` (86) further down this file, and a literal `+ 39px` in style.css.
     Each was introduced with a comment saying it had been measured; they were measured at
     different moments, drifted, and the largest won by being latest in the file. That is
     where the six-pixel slot under the header came from, and the seven-pixel one where the
     group divider tucked under the column names (33px against a 40px row). One token per
     fact, read by every rule that needs it, including `.page-top-cover`. */
  --thead-h: 40px;

  /* THE AIR BETWEEN THE BAR AND THE TABLE'S OWN TOP EDGE, when that edge is pinned.
     Pinning it flush (2026-09-17) fixed one fault and created another: with no daylight at
     all, the table's top border and its rounded top corners are behind the bar, so what
     stops under the bar reads as a square-edged slab rather than as the top of a card. A
     few pixels of the panel's own surface put the corner back on screen — the rounding is
     already there (`.results-table thead th:first-child`), it simply had nowhere to be
     seen. The strip itself is painted by the pinned row, not by a fixed band: see the
     `box-shadow` on `thead th` below. */
  --sticky-gap: 8px;
}

@media (max-width: 640px) {
  :root {
    /* The header shortens to 56px (+2 border) and floats at 8px on a phone: 8 + 58. */
    --sticky-top: 66px;
  }
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-1: 0ms;
    --dur-2: 0ms;
    --dur-3: 0ms;
  }
}

/* ---------------------------------------------------------------------------
 * Focus: visible, consistent, and never the browser's default double ring.
 * :focus-visible only — a mouse click on a button must not leave a ring behind.
 * --------------------------------------------------------------------------- */

.ui *:focus {
  outline: none;
}

.ui :is(button, a, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}

/* ONE RING, ON THE THING THAT LOOKS LIKE THE CONTROL. The header's search box is a
   bordered wrapper holding an icon, a borderless input and the `/` hint; the wrapper is
   what reads as the field. It already lit up on `:focus-within` — and then the rule above
   drew a SECOND ring around the bare input inside it, so focusing the search produced two
   concentric blue outlines with the magnifier and the key cap stranded between them. The
   inner one goes; the wrapper keeps the ring. */
.ui .search-input-wrap input:focus-visible {
  box-shadow: none;
}

/* THE FILTER MENUS' SEARCH FIELD, the same way and for a stronger reason. Focus is in that
   field for the whole time its menu is open — that is how the combobox works — so the ring
   was a permanent box inside an already-bordered popover, drawn around the input alone and
   not the row it sits in („focus box effect gali but tik ant viso field … arba visai focus
   outline panaikinam", 2026-09-24). A ring that is always on says nothing. The cue that
   remains is the magnifier taking the accent colour, beside a blinking caret. */
.ui .dropdown-search input:focus-visible {
  box-shadow: none;
}

.ui .dropdown-search:focus-within svg {
  color: var(--accent);
}

/* ---------------------------------------------------------------------------
 * Buttons. Four intents, three sizes, and real states — the old set had hover
 * and nothing else, so a pressed button and a disabled one looked identical.
 * --------------------------------------------------------------------------- */

.ui .btn {
  --btn-bg: transparent;
  --btn-fg: var(--text);
  --btn-bd: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  height: var(--h-md);
  padding: 0 var(--s-5);
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition:
    background var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out),
    transform var(--dur-1) var(--ease-out);
}

.ui .btn > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}

.ui .btn:active:not(:disabled) {
  /* A real press. 1px is enough to feel and too little to reflow anything. */
  transform: translateY(1px);
}

/* A DISABLED CONTROL STILL LOOKS LIKE A CONTROL. Stripping the border and the background
   left grey text floating in a row of buttons, and on the cart's own action bar that meant
   the most consequential press in the application — „Užsakyti viską", switched off because
   the gate is shut — read as a caption. The owner's words were that they could not find it.
   So the shape stays and only the ink goes quiet: an operator can see WHERE the control is,
   and its `title` says why it is off. */
.ui .btn:disabled,
.ui .btn[aria-disabled="true"] {
  color: var(--disabled);
  background: var(--surface-2);
  border-color: var(--border);
  cursor: not-allowed;
  transform: none;
}

.ui .btn--sm {
  height: var(--h-sm);
  padding: 0 var(--s-4);
  font-size: var(--t-sm);
}

.ui .btn--lg {
  height: var(--h-lg);
  padding: 0 var(--s-6);
  font-size: var(--t-base);
}

.ui .btn--icon {
  width: var(--h-md);
  padding: 0;
}

.ui .btn--icon.btn--sm {
  width: var(--h-sm);
}

.ui .btn--primary {
  --btn-bg: var(--accent);
  --btn-fg: var(--on-accent);
  --btn-bd: var(--accent);
}

.ui .btn--primary:hover:not(:disabled) {
  --btn-bg: var(--accent-hover);
  --btn-bd: var(--accent-hover);
}

.ui .btn--primary:disabled {
  --btn-bg: var(--surface-3);
  --btn-fg: var(--disabled);
  --btn-bd: transparent;
}

.ui .btn--default {
  --btn-bg: var(--surface);
  --btn-bd: var(--border-strong);
}

.ui .btn--default:hover:not(:disabled) {
  --btn-bg: var(--surface-2);
}

.ui .btn--ghost {
  --btn-fg: var(--text-secondary);
}

.ui .btn--ghost:hover:not(:disabled) {
  --btn-bg: var(--surface-2);
  --btn-fg: var(--text);
}

.ui .btn--danger {
  --btn-fg: var(--danger);
}

.ui .btn--danger:hover:not(:disabled) {
  --btn-bg: var(--danger-soft);
}

/* In-flight: HTMX sets .htmx-request on the element that issued the call. The label stays
   put and the button dims — swapping in a spinner would resize the control mid-click and
   move whatever the operator was about to press next. */
.ui .btn.htmx-request {
  color: transparent;
  pointer-events: none;
  position: relative;
}

.ui .btn.htmx-request::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 13px;
  height: 13px;
  border: 1.5px solid color-mix(in srgb, currentcolor 25%, transparent);
  border-top-color: var(--btn-fg);
  border-radius: var(--radius-full);
  animation: ui-spin 560ms linear infinite;
}

.ui .btn--primary.htmx-request::after {
  border-top-color: var(--on-accent);
}

@keyframes ui-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ---------------------------------------------------------------------------
 * Inputs. The app was shipping the browser's own text/number/date widgets; these
 * give them one height, one border and one focus treatment.
 * --------------------------------------------------------------------------- */

.ui .input {
  height: var(--h-md);
  padding: 0 var(--s-4);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-md);
  transition:
    border-color var(--dur-1) var(--ease-out),
    background var(--dur-1) var(--ease-out);
}

.ui .input::placeholder {
  color: var(--text-tertiary);
}

.ui .input:hover:not(:disabled) {
  border-color: color-mix(in srgb, var(--text-secondary) 40%, var(--border-strong));
}

.ui .input:focus-visible {
  border-color: var(--accent);
}

.ui .input:disabled {
  color: var(--disabled);
  background: var(--surface-2);
  cursor: not-allowed;
}

.ui .input--sm {
  height: var(--h-sm);
  font-size: var(--t-sm);
}

/* Kill the spin buttons: quantity is edited with the stepper below, and the native arrows
   are 12px tall, unlabelled, and different in every browser. */
.ui input[type="number"] {
  appearance: textfield;
}

.ui input[type="number"]::-webkit-outer-spin-button,
.ui input[type="number"]::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

/* ---------------------------------------------------------------------------
 * Select. A real <select> underneath (so it stays keyboard- and screen-reader-native)
 * with the platform chrome removed and our own chevron drawn on top.
 * --------------------------------------------------------------------------- */

.ui .select {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.ui .select > select {
  appearance: none;
  height: var(--h-md);
  padding: 0 calc(var(--s-7) + var(--s-2)) 0 var(--s-4);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-md);
  font-weight: var(--w-medium);
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease-out);
}

.ui .select > select:hover {
  border-color: color-mix(in srgb, var(--text-secondary) 40%, var(--border-strong));
}

.ui .select > select:focus-visible {
  border-color: var(--accent);
}

.ui .select > svg {
  position: absolute;
  right: var(--s-4);
  width: 13px;
  height: 13px;
  color: var(--text-tertiary);
  pointer-events: none;
}

.ui .select--sm > select {
  height: var(--h-sm);
  font-size: var(--t-sm);
}

/* ---------------------------------------------------------------------------
 * Stepper: the quantity control. Replaces "native number field + Atnaujinti button",
 * which made changing a quantity a type-then-click errand on every row.
 * --------------------------------------------------------------------------- */

.ui .stepper {
  display: inline-flex;
  align-items: center;
  height: var(--h-md);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  overflow: hidden;
}

.ui .stepper:focus-within {
  border-color: var(--accent);
  box-shadow: var(--focus-ring-tight);
}

.ui .stepper button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 100%;
  padding: 0;
  color: var(--text-secondary);
  background: transparent;
  border: 0;
  font: inherit;
  font-size: var(--t-base);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease-out);
}

.ui .stepper button:hover:not(:disabled) {
  color: var(--text);
  background: var(--surface-2);
}

.ui .stepper button:disabled {
  color: var(--disabled);
  cursor: not-allowed;
}

.ui .stepper input {
  width: 34px;
  height: 100%;
  padding: 0;
  color: var(--text);
  background: transparent;
  border: 0;
  border-inline: 1px solid var(--border);
  font: inherit;
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.ui .stepper input:focus {
  outline: none;
}

.ui .stepper--sm {
  height: var(--h-sm);
}

.ui .stepper--sm button {
  width: 21px;
}

.ui .stepper--sm input {
  width: 30px;
  font-size: var(--t-sm);
}

/* THE COUNT, where the page's stepper has a field. The dropdown's stepper is press-only (see
   `_cart_control.html`: a typed number inside a hover preview is a place to lose focus), so
   the middle is a figure drawn in the field's own box — same width, same rules either side —
   and the two steppers read as one control in two sizes of room. */
.ui .stepper .stepper-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 100%;
  padding: 0 var(--s-2);
  color: var(--text);
  border-inline: 1px solid var(--border);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  font-variant-numeric: tabular-nums;
}

/* In the dropdown the remove press is the quiet end of the row: it appears at full strength
   under the pointer, and the stepper beside it stays the thing the eye lands on. */
.ui .cart-popover-remove {
  color: var(--text-tertiary);
}

.ui .cart-popover-remove:hover:not(:disabled) {
  color: var(--danger);
  background: var(--danger-soft);
}

/* ---------------------------------------------------------------------------
 * Checkbox: a real input, visually replaced. Used by the bulk-marking workflow.
 * --------------------------------------------------------------------------- */

.ui .check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  position: relative;
  width: 16px;
  height: 16px;
}

.ui .check input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.ui .check span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  background: var(--surface);
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-xs);
  transition:
    background var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out);
  pointer-events: none;
}

.ui .check span::after {
  content: "";
  width: 8px;
  height: 4.5px;
  border-left: 1.8px solid var(--on-accent);
  border-bottom: 1.8px solid var(--on-accent);
  transform: rotate(-45deg) scale(0.5);
  opacity: 0;
  transition:
    transform var(--dur-2) var(--ease-out),
    opacity var(--dur-1) var(--ease-out);
}

.ui .check input:hover + span {
  border-color: var(--accent);
}

.ui .check input:focus-visible + span {
  box-shadow: var(--focus-ring-tight);
}

.ui .check input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
}

.ui .check input:checked + span::after {
  transform: rotate(-45deg) scale(1) translate(0.5px, -1px);
  opacity: 1;
}

.ui .check input:indeterminate + span {
  background: var(--accent);
  border-color: var(--accent);
}

.ui .check input:indeterminate + span::after {
  width: 8px;
  height: 0;
  border-left: 0;
  border-bottom: 1.8px solid var(--on-accent);
  transform: none;
  opacity: 1;
}

/* ---------------------------------------------------------------------------
 * Segmented control: one control for "which of these N", replacing four separate
 * pill buttons whose active state was a white box on a white page.
 * --------------------------------------------------------------------------- */

.ui .seg {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: var(--s-1);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.ui .seg > * {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  height: var(--h-sm);
  padding: 0 var(--s-4);
  color: var(--text-secondary);
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}

.ui .seg > *:hover {
  color: var(--text);
}

.ui .seg > [aria-current="true"],
.ui .seg > [aria-current="page"] {
  color: var(--text);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
}

/* ---------------------------------------------------------------------------
 * Tabs: the page-level "which orders" switch. An underline, not a box, so it reads
 * as navigation rather than as a group of buttons.
 * --------------------------------------------------------------------------- */

.ui .tabs {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  border-bottom: 1px solid var(--border);
}

.ui .tab {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-1) calc(var(--s-4) - 1px);
  margin-bottom: -1px;
  color: var(--text-secondary);
  border-bottom: 2px solid transparent;
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  text-decoration: none;
  white-space: nowrap;
  transition:
    color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out);
}

.ui .tab:hover {
  color: var(--text);
  border-bottom-color: var(--border-strong);
}

.ui .tab[aria-current="page"] {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* ---------------------------------------------------------------------------
 * Count: the small number beside a label. Never coloured unless it is a warning —
 * a blue pill on every tab makes all four look equally urgent.
 * --------------------------------------------------------------------------- */

.ui .count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 var(--s-2);
  color: var(--text-secondary);
  background: var(--surface-3);
  border-radius: var(--r-xs);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  font-variant-numeric: tabular-nums;
}

.ui [aria-current] .count {
  color: var(--on-accent);
  background: var(--accent);
}

.ui .count--warning {
  color: var(--on-accent);
  background: var(--warning);
}

/* ---------------------------------------------------------------------------
 * Status: text and a dot, not a filled pill. Twelve filled pills down a column is
 * twelve blocks of colour competing with the data; a dot carries the same state at a
 * fraction of the weight. Only "done" is allowed to be green (see order_admin.py).
 * --------------------------------------------------------------------------- */

.ui .status {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  white-space: nowrap;
}

.ui .status::before {
  content: "";
  width: 6px;
  height: 6px;
  flex: 0 0 auto;
  background: currentcolor;
  border-radius: 50%;
}

.ui .status--ok {
  color: var(--success);
}

/* A supplier's own „ready to collect": news that asks for a walk, so it is the accent —
   never the green that only a received line may wear. */
.ui .status--ready {
  color: var(--accent);
}

.ui .status--warning {
  color: var(--warning);
}

.ui .status--error {
  color: var(--danger);
}

.ui .status--muted {
  color: var(--text-tertiary);
}

.ui .status--muted::before {
  background: transparent;
  border: 1.5px solid currentcolor;
}

/* A pill is kept for the few places a status must survive on a coloured or busy field. */
.ui .tag {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  height: 19px;
  padding: 0 var(--s-3);
  color: var(--text-secondary);
  background: var(--surface-3);
  border-radius: var(--r-xs);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.ui .tag > svg {
  width: 11px;
  height: 11px;
}

.ui .tag--ok {
  color: var(--success);
  background: var(--success-soft);
}

.ui .tag--warning {
  color: var(--warning);
  background: color-mix(in srgb, var(--warning) 14%, transparent);
}

.ui .tag--error {
  color: var(--danger);
  background: var(--danger-soft);
}

.ui .tag--accent {
  color: var(--accent);
  background: var(--accent-soft);
}

/* ---------------------------------------------------------------------------
 * Menu: the contextual action list. Replaces a 24px unlabelled grey square that
 * opened a stack of full-width forms.
 *
 * Built on <details>: no JS, keyboard-operable, and it cannot fall out of step with
 * the server. Alpine closes it on outside-click where it is available.
 * --------------------------------------------------------------------------- */

.ui .menu {
  position: relative;
  display: inline-block;
}

.ui .menu > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  border-radius: var(--r-sm);
  cursor: pointer;
  list-style: none;
  transition:
    background var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}

/* THE SQUARE, AND ITS COLOURS, BELONG TO THE ICON TRIGGER ONLY. These four declarations
   used to sit in the rule above, where they also landed on `.menu--labelled > summary` —
   a trigger that carries `.button button--secondary` precisely so it matches the labelled
   controls beside it. `color` and `border` here outrank `.button--secondary` (two classes
   against one), so that press came out grey-texted with a transparent border next to a
   neighbour with real ink, a real border and a shadow: „vienas yra su apvadu, kitas yra su
   šešėliu". A labelled trigger is now simply a button, and nothing here reaches it. */
.ui .menu:not(.menu--labelled) > summary {
  width: var(--h-md);
  height: var(--h-md);
  color: var(--text-tertiary);
  border: 1px solid transparent;
}

.ui .menu > summary::-webkit-details-marker {
  display: none;
}

.ui .menu > summary > svg {
  width: 15px;
  height: 15px;
}

.ui .menu:not(.menu--labelled) > summary:hover,
.ui .menu:not(.menu--labelled)[open] > summary {
  color: var(--text);
  background: var(--surface-2);
  border-color: var(--border);
}

/* A menu whose trigger CARRIES ITS OWN NAME. The default `.menu > summary` is a fixed
   square for an icon, which is right for a row's overflow and wrong for a control an
   operator reaches for on purpose: "Atidėti krepšelį" sat behind a bare `···` square
   between two labelled buttons, and it was the most useful thing in that row. Here the
   summary is simply a button, so it takes `.button` and its modifiers and lines up with
   the labelled controls beside it. */
.ui .menu--labelled > summary {
  /* Nothing of the square survives here, INCLUDING its height. `height: var(--h-md)` stood
     in this rule and pinned the trigger to 32px whatever button class it carried, so beside
     a taller page-header action (48px at the time) it was visibly the short one. The
     button's own metrics decide now — the page-header action has since come down to the
     same 32px, and the trigger followed it without a change here — and the chevron just
     gets its own breathing room, without which the caret sat against the right border
     while the labelled buttons beside it had even padding. */
  gap: var(--s-3);
}

/* Open is a state of the BUTTON, so it looks like that button's hover rather than like the
   icon trigger's grey square. */
.ui .menu--labelled[open] > summary.button--secondary {
  background: var(--surface-2);
  border-color: var(--text-tertiary);
}

/* The chevron is the only part that says "this opens" — it turns when it has. */
.ui .menu--labelled > summary > svg:last-child {
  transition: transform var(--dur-1) var(--ease-out);
}

.ui .menu--labelled[open] > summary > svg:last-child {
  transform: rotate(180deg);
}

.ui .menu--sm > summary {
  width: var(--h-sm);
  height: var(--h-sm);
}

.ui .menu-panel {
  position: absolute;
  z-index: 40;
  top: calc(100% + var(--s-2));
  right: 0;
  min-width: 208px;
  padding: var(--s-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  animation: ui-pop var(--dur-2) var(--ease-out);
}

.ui .menu-panel--left {
  right: auto;
  left: 0;
}

@keyframes ui-pop {
  from {
    opacity: 0;
    transform: translateY(-3px) scale(0.985);
  }
}

.ui .menu-label {
  padding: var(--s-3) var(--s-4) var(--s-2);
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.ui .menu-item {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  width: 100%;
  padding: var(--s-4);
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-md);
  font-weight: var(--w-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease-out);
}

.ui .menu-item > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  color: var(--text-tertiary);
}

.ui .menu-item:hover {
  background: var(--surface-2);
}

.ui .menu-item:hover > svg {
  color: var(--text-secondary);
}

.ui .menu-item--danger {
  color: var(--danger);
}

.ui .menu-item--danger > svg {
  color: var(--danger);
}

.ui .menu-item--danger:hover {
  background: var(--danger-soft);
}

.ui .menu-sep {
  height: 1px;
  margin: var(--s-2) calc(-1 * var(--s-2));
  background: var(--border);
}

.ui .menu form {
  display: contents;
}

/* ---------------------------------------------------------------------------
 * Table. One treatment for every dense list in the app: no vertical rules, hairline
 * row separators, a sticky header, right-aligned numbers with tabular figures.
 * --------------------------------------------------------------------------- */

.ui .tbl {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--t-md);
}

/* NOT STICKY, and that is the fix for a real defect rather than a preference.
   `--sticky-top` is the PAGE header's bottom edge (80px). A `.tbl` is never the page — it is
   a small table inside a card, one per supplier in the cart and one per order elsewhere — and
   pinning its header 80px down a container shorter than 80px leaves the header floating over
   the only row it labels. Screenshotted 2026-09-15 on the cart: "PREKĖ · KAINA · KIEKIS ·
   SUMA" sat across the product line with the price and quantity cut in half behind it.
   The long scrolling table that genuinely wants a pinned header is `.results-table`, which
   has its own rule and its own offset in style.css and is untouched by this. */
.ui .tbl thead th {
  padding: var(--s-4) var(--s-5);
  color: var(--text-tertiary);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.ui .tbl tbody td {
  padding: var(--s-5);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.ui .tbl tbody tr:last-child td {
  border-bottom: 0;
}

/* THE HEADER IS OPAQUE AND THE PANEL IS ROUNDED, and nothing was clipping one against the
   other. `.panel` cannot use `overflow: hidden` — that is what would clip a row's action
   menu, which opens downward and is taller than its row — so the header's own background
   painted two square corners straight over the panel's rounded ones, and the panel's
   border appeared to stop and restart. The corner cells round themselves instead, which
   is the same shape and costs no clipping. */
.ui .panel > .tbl thead th:first-child,
.ui .panel > * > .tbl thead th:first-child {
  border-start-start-radius: var(--r-lg);
}

.ui .panel > .tbl thead th:last-child,
.ui .panel > * > .tbl thead th:last-child {
  border-start-end-radius: var(--r-lg);
}

.ui .tbl tbody tr {
  transition: background var(--dur-1) var(--ease-out);
}

.ui .tbl--hover tbody tr:hover {
  background: var(--surface-2);
}

.ui .tbl .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.ui .tbl .shrink {
  width: 1%;
  white-space: nowrap;
}

/* The actions cell: controls appear on hover/focus, so a 40-row table is not 40 rows of
   competing buttons — but they are always there for keyboard and touch. */
.ui .tbl .row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-3);
}

@media (hover: hover) {
  .ui .tbl--quiet .row-actions > .on-hover {
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-out);
  }

  .ui .tbl--quiet tr:hover .row-actions > .on-hover,
  .ui .tbl--quiet tr:focus-within .row-actions > .on-hover {
    opacity: 1;
  }
}

/* ---------------------------------------------------------------------------
 * Product cell: thumbnail + name + identifiers. The one component that answers
 * "what actually is this part", used by search results, the cart, saved copies,
 * order lines and a purchase's plan — so a part looks the same everywhere.
 * --------------------------------------------------------------------------- */

.ui .prod {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-width: 0;
}

.ui .prod-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}

.ui .prod-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  mix-blend-mode: multiply;
}

/* The thumb is a BUTTON wherever a photo exists (it opens the preview modal) and a span
   where none does. The rule above is shared, so only the button's own agent styles need
   undoing — padding, font and the UA border — and the two must render identically, because
   a row of parts where the photographed ones sit 2px lower than the rest reads as a bug. */
.ui button.prod-thumb {
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
  appearance: none;
  -webkit-appearance: none;
}

.ui button.prod-thumb:hover {
  border-color: var(--border-strong, var(--border));
}

/* Keyboard reach is the whole reason this is a button rather than a clickable span. */
.ui button.prod-thumb:focus-visible {
  outline: 2px solid var(--accent, currentColor);
  outline-offset: 2px;
}

/* A photo that 404s stops being a control: app.js disables it and drops its src. */
.ui button.prod-thumb[disabled],
.ui button.prod-thumb.is-broken {
  cursor: default;
}

/* multiply drops a supplier photo's white studio background onto our surface, which is
   exactly wrong on a dark one — there it would darken the part into the background. */
:root[data-theme="dark"] .ui .prod-thumb img {
  mix-blend-mode: normal;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ui .prod-thumb img {
    mix-blend-mode: normal;
  }
}

/* No photo is a state, not a hole: a muted glyph, at the same size, so rows stay aligned. */
.ui .prod-thumb--empty > svg {
  width: 15px;
  height: 15px;
  color: var(--text-tertiary);
  opacity: 0.55;
}

.ui .prod-body {
  display: grid;
  gap: var(--s-1);
  min-width: 0;
}

.ui .prod-name {
  color: var(--text);
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  line-height: var(--lh-tight);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui a.prod-name {
  text-decoration: none;
}

.ui a.prod-name:hover {
  color: var(--accent);
}

.ui .prod-meta {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  line-height: var(--lh-tight);
  min-width: 0;
}

.ui .prod-code {
  color: var(--text-secondary);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

/* A code or name that opens the supplier's page: it reads as the text it is until the
   pointer is on it, then as a link. Every table uses these two classes (and the results
   table its own), so one part is clickable the same way wherever it appears. */
.ui a.prod-code {
  text-decoration: none;
}

.ui a.product-link {
  color: inherit;
  text-decoration: none;
}

.ui a.prod-code:hover,
.ui a.product-link:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.ui .prod-brand {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
 * Money. Tabular, right-aligned, and one format everywhere: the app was printing
 * "4.37 EUR" in a table and "13,97 EUR" in that table's own total.
 * --------------------------------------------------------------------------- */

.ui .money {
  font-variant-numeric: tabular-nums;
  font-weight: var(--w-semi);
  white-space: nowrap;
}

.ui .money--strong {
  font-size: var(--t-base);
  font-weight: var(--w-bold);
}

.ui .money--muted {
  color: var(--text-tertiary);
  font-weight: var(--w-medium);
}

/* ---------------------------------------------------------------------------
 * Page furniture. A page is a title, a toolbar and content — not a stack of
 * bordered boxes each holding one sentence.
 * --------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
 * THE PAGE HEAD. One component, rendered by `app/templates/_page_head.html`, whose comment
 * carries the rules it enforces — how long the three texts are, when a number belongs on a
 * title, and how the actions are ordered.
 *
 * There were SIX implementations across ten pages, and two of them were the same design
 * under different names in different stylesheets: `.search-hero`/`.search-copy` here and
 * `.head.head--card` there, each with a comment saying it matched the other. They still
 * disagreed about the stacking breakpoint (900 vs 640), the phone padding, the title-block
 * gap (a literal 7px vs a token) and the subtitle size. Four templates carried the same
 * five-line „NO NAVIGATION HERE" comment verbatim.
 * --------------------------------------------------------------------------- */

.ui .head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-6);
}

/* THE CARD. Every page head sits in the same surface — the one thing that stopped these
   screens reading as separate little applications. */
.ui .head--card {
  padding: var(--s-8);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}

/* …EXCEPT where the head is already inside one. The results head sits at the top of
   `.results-panel`, so a second surface would be a card drawn on a card. */
.ui .head--flush {
  padding: 0;
  margin-bottom: var(--s-5);
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.ui .head-titles {
  display: grid;
  gap: var(--s-2);
  min-width: 0;
}

.ui .head h1 {
  margin: 0;
  font-size: var(--t-2xl);
  font-weight: var(--w-bold);
  letter-spacing: -0.02em;
  line-height: var(--lh-tight);
}

/* The identifier of the one thing a page is about, beside its name. Quieter than the name:
   „Detalė" is the label and the code is the value, and a code set in the title's own weight
   competes with it. */
.ui .head h1 .head-code {
  color: var(--text-secondary);
  font-weight: var(--w-semi);
}

.ui .head-sub {
  color: var(--text-secondary);
  font-size: var(--t-md);
}

/* A row of FIGURES rather than a sentence — the basket's head is four numbers. Same size and
   colour as `.head-sub` because it occupies the same line and the same role; tabular so the
   digits do not shuffle as the basket changes under them. */
.ui .head-facts {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  flex-wrap: wrap;
  color: var(--text-secondary);
  font-size: var(--t-md);
  font-variant-numeric: tabular-nums;
}

/* THE ANSWER, not one of its components. „12 poz. · 20 vnt · 3 tiek." are what the basket is
   made of; the money is what an eye comes to this line for, and it was the same grey as the
   counts beside it. One step up in weight and into the text colour — not a badge, not a
   colour of its own: it is still the same line, read in the same breath. */
.ui .head-total {
  color: var(--text);
  font-weight: var(--w-bold);
  font-size: var(--t-lg);
  letter-spacing: -0.01em;
}

.ui .head-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex: 0 0 auto;
}

/* A group inside the actions row (an Alpine scope, a form) must not break the row. */
.ui .head-actions-group {
  display: contents;
}

/* ONE BREAKPOINT for the head, where there were two. Below it the actions take a line of
   their own under the copy: at 900px one family stacked and the other did not, so the same
   control sat beside a title on one page and under it on the next. */
@media (max-width: 640px) {
  .ui .head {
    flex-direction: column;
    align-items: stretch;
  }

  .ui .head-actions {
    flex-wrap: wrap;
  }
}

.ui .back {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
  color: var(--text-secondary);
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  text-decoration: none;
}

.ui .back:hover {
  color: var(--text);
}

.ui .back > svg {
  width: 13px;
  height: 13px;
}

/* A panel is used where content genuinely is a surface (a table, a detail body) — not to
   fence off every paragraph. No panel may contain another panel. */
.ui .panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.ui .panel > .panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-6);
  border-bottom: 1px solid var(--border);
}

.ui .panel-title {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  font-size: var(--t-base);
  font-weight: var(--w-bold);
}

.ui .panel-sub {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
}

.ui .panel-scroll {
  overflow-x: auto;
}

/* A metric line, not metric cards: same numbers, a tenth of the furniture. */
.ui .facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-8);
}

.ui .facts > div {
  display: grid;
  gap: var(--s-1);
}

.ui .facts dt,
.ui .facts .facts-key {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.ui .facts dd,
.ui .facts .facts-val {
  margin: 0;
  font-size: var(--t-base);
  font-weight: var(--w-semi);
  font-variant-numeric: tabular-nums;
}

/* EVERY FIGURE IN A FACTS STRIP IS SET THE SAME: „Tikimės", „Gauta", „Liko", „Mūsų suma"
   are facts about one order, and singling one out is a claim about importance the strip has
   no business making — it is findable because it has a label, like the rest.

   „Liko" still differed (2026-09-26, „turi but vienodi visi"): `.od-strip-key dd` made it
   larger and bold, and an earlier attempt to undo that set `font-weight: inherit`, which
   inherits the <div>'s NORMAL weight — so it came out bigger and lighter than its
   neighbours instead of equal to them. Both overrides are gone; `facts-val--key` and
   `od-strip-key` stay in the markup only to say WHICH figure this is. */

/* A note is quiet by default: the receiving screen had four stacked alert boxes above the
   first row, all shouting equally. */
.ui .note {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  color: var(--text-secondary);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
}

.ui .note > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--text-tertiary);
}

.ui .note--warning {
  padding: var(--s-5);
  color: var(--text);
  background: color-mix(in srgb, var(--warning) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--warning) 24%, transparent);
  border-radius: var(--r-md);
}

.ui .note--warning > svg {
  color: var(--warning);
}

.ui .note--danger {
  padding: var(--s-5);
  color: var(--text);
  background: var(--danger-soft);
  border: 1px solid color-mix(in srgb, var(--danger) 22%, transparent);
  border-radius: var(--r-md);
}

.ui .note--danger > svg {
  color: var(--danger);
}

.ui .note strong {
  color: var(--text);
  font-weight: var(--w-semi);
}

/* An explanation nobody needs on every visit: collapsed, quiet, and out of the way. */
.ui .disclose {
  font-size: var(--t-sm);
}

.ui .disclose > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  cursor: pointer;
  list-style: none;
}

.ui .disclose > summary::-webkit-details-marker {
  display: none;
}

.ui .disclose > summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid currentcolor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
  transition: transform var(--dur-2) var(--ease-out);
}

.ui .disclose[open] > summary::before {
  transform: rotate(90deg) translateX(-1px);
}

.ui .disclose > summary:hover {
  color: var(--text-secondary);
}

.ui .disclose-body {
  padding: var(--s-2) 0 var(--s-5);
  color: var(--text-secondary);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
}

.ui .disclose-body p {
  margin: 0 0 var(--s-4);
  max-width: 82ch;
}

.ui .disclose-body p:last-child {
  margin-bottom: 0;
}

/* Empty state: a line of text where the content would be, not a 200px dashed box. */
.ui .empty {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-9) var(--s-6);
  text-align: center;
}

.ui .empty h2 {
  margin: 0;
  font-size: var(--t-base);
  font-weight: var(--w-semi);
}

.ui .empty p {
  margin: 0 auto;
  max-width: 62ch;
  color: var(--text-secondary);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
}

/* ---------------------------------------------------------------------------
 * Feedback: a change the operator made must be visible where they made it.
 * --------------------------------------------------------------------------- */

.ui .flash {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  color: var(--text);
  background: var(--accent-softer);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-radius: var(--r-md);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
  animation: ui-flash var(--dur-3) var(--ease-out);
}

.ui .flash > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--accent);
}

.ui .flash--ok {
  background: var(--success-soft);
  border-color: color-mix(in srgb, var(--success) 26%, transparent);
}

.ui .flash--ok > svg {
  color: var(--success);
}

.ui .flash--warning {
  background: color-mix(in srgb, var(--warning) 9%, transparent);
  border-color: color-mix(in srgb, var(--warning) 26%, transparent);
}

.ui .flash--warning > svg {
  color: var(--warning);
}

@keyframes ui-flash {
  from {
    opacity: 0;
    transform: translateY(-3px);
  }
}

/* A row that just changed: a brief wash, so a swap that lands below the fold or looks
   identical still reads as "that worked". */
@keyframes ui-settle {
  from {
    background: var(--accent-soft);
  }
}

.ui .settled {
  animation: ui-settle 620ms var(--ease-out);
}

.ui .settled--ok {
  animation-name: ui-settle-ok;
}

@keyframes ui-settle-ok {
  from {
    background: var(--success-soft);
  }
}

/* ===========================================================================
 * ONE ORDER (.od*) — the receiving screen.
 * =========================================================================== */

.ui .od {
  display: grid;
  gap: var(--s-6);
}

.ui .od-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-6);
}

.ui .od-head-main {
  display: grid;
  gap: var(--s-3);
  min-width: 0;
}

.ui .od-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-4);
}

.ui .od-title h1 {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  margin: 0;
  font-size: var(--t-xl);
  font-weight: var(--w-bold);
  letter-spacing: -0.01em;
  line-height: var(--lh-tight);
}

.ui .od-ref {
  color: var(--text-secondary);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--t-lg);
  font-weight: var(--w-semi);
}

.ui a.od-purchase {
  text-decoration: none;
}

.ui a.od-purchase:hover {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}

.ui .od-meta {
  margin: 0;
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .od-head-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex: 0 0 auto;
}

/* The numbers, on one line, above the table they describe. Was five bordered mini-cards. */
.ui .od-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-5) var(--s-8);
  padding: var(--s-5) 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  /* Inside a summary card the surface already separates it; two rules there would be
     fencing inside a fence. See `.card` below. */
}

.ui .od-strip-problem {
  color: var(--warning);
}

/* `flex: 0 0 auto` and a nowrap trigger: as a shrinkable item this collapsed to 26px and
   the date wrapped to "2026-" / "08-18" in a 26px column — measured on the order page. A
   date is one word. */
.ui .od-strip-date {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex: 0 0 auto;
}

/* What stands where the date control used to be, once nothing is outstanding. Quiet on
   purpose: it is a fact being reported, not a thing to press, and the header badge already
   carries the state. */
.ui .od-settled {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  white-space: nowrap;
}

.ui .od-settled-day {
  color: var(--text);
  font-weight: var(--w-bold);
}

.ui .dp-trigger,
.ui .dp-trigger > span {
  white-space: nowrap;
}

/* The date: one trigger showing the state, editing behind it. */
.ui .od-date {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
}

.ui .od-date-menu > summary.od-date-trigger {
  width: auto;
  gap: var(--s-3);
  padding: 0 var(--s-4);
  color: var(--text);
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  font-variant-numeric: tabular-nums;
  border-color: var(--border);
}

.ui .od-date-trigger > svg {
  width: 13px;
  height: 13px;
  color: var(--text-tertiary);
}

.ui .od-date-trigger--empty {
  color: var(--text-tertiary);
  font-weight: var(--w-medium);
}

.ui .od-date-trigger--overdue {
  color: var(--danger);
}

.ui .od-date-trigger--overdue > svg {
  color: var(--danger);
}

/* Where a stored date came from, said on the badge rather than in a tooltip. A tooltip
   does not survive being printed, and this page gets printed. */
.ui .dp-from-supplier {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-weight: var(--w-normal);
  white-space: nowrap;
}

.ui .od-date-late {
  font-weight: var(--w-bold);
}

/* THE DEFER MENU IS A LITTLE FORM, not a list of links, and it was styled as neither.
   Measured on screen: the name field sat 4px from the panel's top edge, its button hung
   underneath left-aligned so the two did not read as one control, the placeholder did not
   fit the 208px panel, and the row below it ("Atidėti ir pradėti naują") was a menu item —
   a different species of thing at a different height. One width, one rhythm, and the field
   and its button paired as a block. */
.ui .menu-panel--form {
  /* Wide enough for the field AND its press on one line: at 17rem the pair did not fit and
     the placeholder ("Pavadinimas (nebūtinas)") was clipped. */
  min-width: 21rem;
  padding: var(--s-5);
}

/* THE FIELD AND ITS BUTTON ARE ONE CONTROL, so they sit on one line. Stacked and both
   stretched to the panel's width, the blue press read as the heavier of the two and the
   field as an afterthought under it. */
.ui .menu-panel--form .menu-form {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: 0;
}

.ui .menu-panel--form .menu-form .input {
  flex: 1 1 auto;
  min-width: 0;
}

.ui .menu-panel--form .menu-form .btn {
  flex: 0 0 auto;
  padding-inline: var(--s-6);
}

.ui .menu-panel--form .menu-sep {
  margin: var(--s-5) calc(var(--s-5) * -1);
}

.ui .menu-panel--form .menu-item {
  /* ALIGNED WITH THE FIELD, BUT STILL PADDED. `padding-inline: 0` stood here and lined the
     text up correctly by taking the row's padding away entirely — so the label began exactly
     at its own hover box's edge: „tekstas yra iš karto, kur yra kontūras". The row keeps its
     padding and is pulled out by the same amount instead, which lines the TEXT up and leaves
     the box its breathing room. */
  padding-inline: var(--s-4);
  margin-inline: calc(var(--s-4) * -1);
  width: calc(100% + var(--s-4) * 2);
}

.ui .menu-form {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-4) var(--s-4);
}

.ui .od-date-input {
  width: 132px;
}

.ui .od-qty {
  width: 62px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Bulk bar: appears only when something is ticked, and takes the place of nothing. */
.ui .od-bulk {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-5);
  margin-bottom: var(--s-4);
  padding: var(--s-4) var(--s-5);
  background: var(--accent-softer);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  border-radius: var(--r-md);
  animation: ui-flash var(--dur-2) var(--ease-out);
}

.ui .od-bulk-count {
  font-size: var(--t-md);
  font-weight: var(--w-semi);
}

.ui .od-bulk-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.ui .od-line-qty strong {
  font-size: var(--t-base);
}

.ui .od-unit {
  margin-left: var(--s-2);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
}

.ui .od-line-qty small,
.ui .od-line-unit-price,
.ui .od-line-ordered-total {
  display: block;
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-weight: var(--w-medium);
}

.ui .od-line-note,
.ui .od-line-date {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-3);
  color: var(--text-tertiary);
  font-size: var(--t-xs);
}

.ui .od-line-note > svg {
  width: 11px;
  height: 11px;
}

/* THE PIECES ONE LINE ARRIVES IN, on the row itself. Chips rather than a nested table:
   there are normally two of them, they have to survive the row stacking into a card on a
   phone, and the only editing happens in the row's own menu. Arrived is the only one that
   may look positive — same rule as every other state in this app. */
.ui .od-parts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-3);
}

.ui .od-part {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 1px var(--s-2);
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  color: var(--text-secondary);
  font-size: var(--t-xs);
  white-space: nowrap;
}

.ui .od-part > svg {
  width: 11px;
  height: 11px;
}

.ui .od-part--got {
  color: var(--success);
  border-color: color-mix(in srgb, var(--success) 35%, var(--border));
}

.ui .od-part--late {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, var(--border));
}

/* A piece that arrived after the day it was promised for. Kept quiet — it is history, not
   something to act on — but it is the only place a supplier's reliability is visible. */
.ui .od-part em {
  font-style: normal;
  color: var(--warning);
}

/* One editable piece inside the row's menu: quantity, the shared date control, and the two
   one-press actions. The date control is an accordion here (see _date_field.html), so this
   row grows downwards inside the panel instead of opening a second popover. */
.ui .od-part-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--pad, 0);
}

.ui .od-part-row .dp {
  flex: 1 1 120px;
  min-width: 0;
}

/* AN OPEN CALENDAR TAKES THE WHOLE ROW.
 *
 * `.od-part-row` is one flex line — the "1 vnt" tag, the date control, a receive button
 * and a delete button — and the date control is an ACCORDION that expands where it stands.
 * Expanded it is ~200px tall, so `align-items: center` put the tag and both buttons at the
 * MIDDLE of that height: three controls floating on top of the calendar days. And the
 * calendar itself only got the width left over beside them, which inside a 320px panel was
 * narrow enough to clip its last weekday column.
 *
 * So while it is open it wraps to a line of its own, after the rest (`order`), at full
 * width. Closed, nothing changes: the trigger is one line and belongs beside the tag. */
.ui .od-part-row .dp--inline[open] {
  flex: 1 0 100%;
  order: 9;
}

.ui .od-part-tag {
  flex: 0 0 auto;
  padding: 1px var(--s-2);
  border-radius: var(--r-xs);
  background: var(--surface-2);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
}

.ui .od-part-tag--got {
  color: var(--success);
  background: color-mix(in srgb, var(--success) 12%, transparent);
}

/* "How many, and when" as ONE sentence: a quantity, the word, then the day. It used to be
   a quantity and a single button that created the piece with no date, so dating it was a
   second act on a control that had not existed a moment earlier. */
.ui .od-part-add {
  display: grid;
  grid-template-columns: 4rem 1fr;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--pad, 0);
}

/* The day chips are the second line of the same sentence, aligned under the quantity's
   label rather than indented past it — the row reads "1 vnt atvyks / rytoj". */
.ui .od-part-when {
  grid-column: 1 / -1;
}

.ui .od-part-add-label {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
}

.ui .od-part-when {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

/* "Be datos" is a real answer — a supplier who has not named a day has not named one — but
   it is the fallback, not the way through. Drawn quieter than the days beside it. */
.ui .dp-chip--quiet {
  color: var(--text-tertiary);
}

.ui .od-line-done {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--success);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  white-space: nowrap;
}

.ui .od-line-done > svg {
  width: 13px;
  height: 13px;
}

.ui .od-line-empty {
  padding: var(--s-8) var(--s-5);
  color: var(--text-secondary);
  text-align: center;
}

/* A ticked row and the keyboard cursor have to be distinguishable at a glance. */
.ui tr.is-picked {
  background: var(--accent-softer);
}

/* THE CURSOR HAS TO BE UNMISTAKABLE. A 2px inset line on the left edge was the whole
   indicator, which on a wide row is a mark you have to go looking for — and `x` acts on
   whatever it points at. Now the row is tinted, its left edge is a solid bar, and it
   carries aria-current so a screen reader says the same thing. Nothing else in this table
   looks like this. */
.ui tr.is-cursor {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}

.ui tr.is-cursor .prod-name {
  color: var(--text);
}

.ui .od-line--received .prod-name {
  color: var(--text-secondary);
}

.ui .od-line--cancelled .prod-name {
  color: var(--text-tertiary);
  text-decoration: line-through;
}

/* ---------------------------------------------------------------------------
 * Note threads (app/templates/_notes.html). One purchase's, or one order's.
 *
 * Every class here starts `notes-`, entries included: `.note` is already this app's
 * callout box a few hundred lines above, and a thread entry inheriting a callout's
 * padding and tint is a collision nobody finds by reading either file.
 *
 * The section is a CARD in both homes. It carries a composer and a list of things
 * people wrote; sitting on the page background it read as debris under whatever was
 * above it.
 * --------------------------------------------------------------------------- */

.ui .notes {
  display: grid;
  gap: var(--s-5);
  padding: var(--s-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.ui .notes-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-4);
}

.ui .notes-title {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0;
  font-size: var(--t-base);
  font-weight: var(--w-semi);
}

.ui .notes-title > svg {
  width: 14px;
  height: 14px;
  color: var(--text-tertiary);
}

.ui .notes-count {
  min-width: 18px;
  padding: 0 var(--s-2);
  color: var(--text-secondary);
  background: var(--surface-2);
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  font-weight: var(--w-medium);
  text-align: center;
}

.ui .notes-scope {
  font-size: var(--t-xs);
}

/* Composer above the list, because the list is newest-first. */
.ui .notes-new {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
}

.ui .notes-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: calc(var(--h-lg) + var(--s-5));
  padding: var(--s-4) var(--s-5);
  line-height: var(--lh-normal);
  resize: vertical;
}

.ui .notes-add {
  flex: 0 0 auto;
}

.ui .notes-empty {
  margin: 0;
  font-size: var(--t-sm);
}

.ui .notes-list {
  display: grid;
  gap: var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui .notes-item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "meta actions"
    "body body";
  gap: var(--s-2) var(--s-4);
  padding: var(--s-4) var(--s-5);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

/* A pin is marked on the LEFT EDGE rather than by tinting the whole card: several
   pinned notes tinted would swamp the thread, and the edge still reads at a glance. */
.ui .notes-item--pinned {
  border-inline-start: 3px solid var(--accent);
}

.ui .notes-meta {
  grid-area: meta;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
  min-width: 0;
  color: var(--text-tertiary);
  font-size: var(--t-xs);
}

.ui .notes-pin-flag > svg {
  width: 12px;
  height: 12px;
  color: var(--accent);
}

.ui .notes-author {
  color: var(--text);
  font-weight: var(--w-semi);
}

/* An adopted note from the single-field era: nobody recorded who wrote it. Said in
   words and drawn quiet, never left blank — a gap in a column of names reads as a
   rendering fault. */
.ui .notes-author--unknown {
  color: var(--text-tertiary);
  font-weight: var(--w-normal);
  font-style: italic;
}

/* A note this app wrote by itself (`order_admin.AUTHOR_APP`): what it concluded, not what
   anybody said, so its signature is quieter than a person's — and never italic, which is
   the unknown author's. */
.ui .notes-author--app {
  color: var(--text-secondary);
  font-weight: var(--w-medium);
}

.ui .notes-edited {
  font-style: italic;
}

.ui .notes-body {
  grid-area: body;
  min-width: 0;
  color: var(--text);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
  overflow-wrap: anywhere;
}

.ui .notes-body p {
  margin: 0;
}

.ui .notes-body p + p {
  margin-top: var(--s-2);
}

.ui .notes-actions {
  grid-area: actions;
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

/* The three controls are quiet until the entry is hovered or something inside it has
   focus. They are always in the DOM and always reachable by keyboard — this only takes
   them out of the reading order of a thread somebody is reading. */
.ui .notes-actions > * {
  opacity: 0.45;
  transition: opacity var(--dur-1) var(--ease-out);
}

.ui .notes-item:hover .notes-actions > *,
.ui .notes-item:focus-within .notes-actions > * {
  opacity: 1;
}

.ui .notes-item--pinned .notes-pin {
  opacity: 1;
  color: var(--accent);
}

/* EDITING is the same row as a field: the meta line stays, the actions row goes (its
   three controls are meaningless mid-edit), and the form takes the body's place. */
.ui .notes-edit-form {
  grid-area: body;
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  min-width: 0;
}

.ui .notes-edit-actions {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.ui .notes-item--editing {
  grid-template-columns: 1fr;
  grid-template-areas:
    "meta"
    "body";
  border-color: var(--border-strong);
}

.ui .od-removed {
  display: grid;
  gap: var(--s-4);
  margin: var(--s-4) 0 0;
  padding: 0;
  list-style: none;
}

.ui .od-removed li {
  display: flex;
  align-items: center;
  gap: var(--s-5);
}

.ui .od-removed-qty {
  margin-left: auto;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

.ui .od-confirm > summary {
  gap: var(--s-4);
}

.ui .od-confirm-when {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
}

.ui .od-confirm-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
}

.ui .od-confirm-facts {
  margin: 0 0 var(--s-5);
}

.ui .od-confirm-lines {
  margin-top: var(--s-5);
}

/* The unanswered submit. Above the confirmation disclosure and never inside it: it is a
   question that has to be answered before the rest of the card means much, and a collapsed
   <details> is where a reader puts things they have already decided about. */
.ui .od-unresolved {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  padding: var(--s-6);
  margin-bottom: var(--s-6);
  border-left: 3px solid var(--warning);
}

.ui .od-unresolved p {
  margin: 0;
}

/* What the app's own search of their listing found, inside the unanswered-submit panel:
   context for the question, not the question, so it is drawn quiet. */
.ui .od-claim {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .od-unresolved-head {
  font-weight: var(--w-semi);
}

.ui .od-unresolved-yes {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.ui .od-unresolved-yes label {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
}

.ui .od-unresolved-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
}

.ui .od-unresolved-row input {
  flex: 1 1 12ch;
  min-width: 12ch;
  font-variant-numeric: tabular-nums;
}

/* WHAT YOU CAN DO WITH THIS BASKET, ON THE RIGHT. It used to be a band under the title:
   „Grąžinti į krepšelį" sat a few pixels below „Išsaugota <data>" as if it belonged to that
   sentence, a card three words wide left its whole right half empty, and the checkbox —
   inside the switch form — set that form's width, so „Pirma atidėti dabartinį" opened a
   hole between the first two buttons. The card says what it is on the left and offers what
   can be done on the right, which is the shape the cart's own head already has. */
.ui .cart-copy-side {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-5);
}

.ui .cart-copy-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  /* The cart head's own gap (`.ui .head-actions`), since this row is that row's twin. */
  gap: var(--s-3);
}

.ui .cart-switch-keep {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

/* The title column may wrap its own words; the actions column never shrinks below its
   buttons, and the two stack rather than squeeze once there is not room for both. */
.ui .cart-panel--copy .cart-panel-header {
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--s-5) var(--s-6);
}

.ui .cart-panel--copy .cart-title {
  flex: 1 1 22rem;
  min-width: 0;
  /* THE TILE BELONGS TO THE NAME, so it lines up with the name. Centred against the whole
     column it drifted further down every time the card gained a line — the date, the
     automatic-snapshot note, the purchase-plan note — until a 38px square sat opposite the
     second paragraph of a heading block. Pinned to the top and then nudged so its centre
     meets the heading's own line, it stays put whatever the card says underneath. */
  align-items: flex-start;
}

.ui .cart-panel--copy .cart-title-icon {
  /* Measured: the heading's line box is 30px and the tile is 32px, so flush tops put the
     tile's centre 1px below the heading's. (It was 38px and a 4px nudge — four pixels is
     exactly the amount that reads as "not quite lined up" without looking like a mistake
     anyone could name. One pixel is still one pixel.) */
  margin-top: -1px;
}

@media (max-width: 900px) {
  .ui .cart-copy-side {
    align-items: flex-start;
    width: 100%;
  }

  .ui .cart-copy-actions {
    justify-content: flex-start;
  }
}

/* A diagnosis note that is an emergency: a test line we put in a supplier's basket and could
   not take back out. The only red on this page that is about US. */
.ui .diagnosis-note--alert {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-left: 3px solid var(--danger);
  color: var(--text-primary);
  font-weight: var(--w-medium);
}

.ui .cart-copy-auto {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  margin: var(--s-3) 0 0;
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .cart-copy-plan {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  margin: 0 0 var(--s-5);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .od-resolved-by {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0 0 var(--s-5);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .od--closed .od-title h1 {
  color: var(--text-secondary);
}

@media (max-width: 640px) {
  .ui .od-head {
    flex-direction: column;
  }

  .ui .od-head-actions {
    width: 100%;
  }

  .ui .od-head-actions .btn:not(.btn--icon) {
    flex: 1 1 auto;
  }
}

/* ===========================================================================
 * THE WORKING LIST (.olist / .ogroup / .orow) — which order to open, or mark here.
 * =========================================================================== */

.ui .olist {
  display: grid;
  gap: var(--s-9);
}

.ui .olist-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-5);
}

.ui .olist-bar .flash {
  flex: 0 1 auto;
  padding: var(--s-3) var(--s-5);
}

.ui .ogroup {
  display: grid;
  gap: var(--s-5);
  min-width: 0;
}

.ui .ogroup-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
}

.ui .ogroup-head h2 {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0;
  font-size: var(--t-base);
  font-weight: var(--w-bold);
}

.ui .ogroup-head h2 > a {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  color: inherit;
  text-decoration: none;
}

.ui .ogroup-head h2 > a:hover {
  color: var(--accent);
}

.ui .ogroup-head h2 svg {
  width: 14px;
  height: 14px;
  color: var(--text-tertiary);
}

.ui .ogroup-meta {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .ogroup-empty {
  margin: 0;
  padding: var(--s-5);
  color: var(--text-secondary);
  background: color-mix(in srgb, var(--warning) 6%, transparent);
  border: 1px dashed color-mix(in srgb, var(--warning) 38%, var(--border));
  border-radius: var(--r-md);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
}

.ui .orows {
  display: grid;
  gap: var(--s-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The row: a link across its whole width, with the action as a sibling outside it. */
.ui .orow {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding-right: var(--s-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  transition:
    border-color var(--dur-1) var(--ease-out),
    background var(--dur-1) var(--ease-out);
}

.ui .orow:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
}

.ui .orow:focus-within {
  border-color: var(--accent);
}

.ui .orow-main {
  display: grid;
  grid-template-columns: minmax(150px, 1fr) minmax(0, 2fr) minmax(140px, auto);
  align-items: center;
  gap: var(--s-6);
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--s-5);
  color: inherit;
  text-decoration: none;
}

.ui .orow-who {
  display: grid;
  gap: var(--s-1);
  min-width: 0;
}

.ui .orow-supplier {
  font-size: var(--t-base);
  font-weight: var(--w-bold);
}

.ui .orow-ref {
  color: var(--text-tertiary);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--t-sm);
}

.ui .orow-parts {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-width: 0;
}

.ui .orow-thumbs {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex: 0 0 auto;
}

/* Overlapped slightly: three thumbnails read as "this delivery" rather than as three
   separate controls, and it buys back horizontal room in a dense row. */
.ui .orow-thumbs .prod-thumb {
  width: 30px;
  height: 30px;
  margin-right: -6px;
  background: var(--surface);
  box-shadow: 0 0 0 2px var(--surface);
}

.ui .orow:hover .prod-thumb {
  box-shadow: 0 0 0 2px var(--surface-2);
}

.ui .orow-thumbs .prod-thumb:last-of-type {
  margin-right: 0;
}

.ui .orow-more {
  margin-left: var(--s-3);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

.ui .orow-names {
  display: grid;
  gap: var(--s-1);
  min-width: 0;
}

.ui .orow-first {
  color: var(--text);
  font-size: var(--t-md);
  font-weight: var(--w-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui .orow-counts {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .orow-problem {
  color: var(--warning);
  font-weight: var(--w-semi);
}

.ui .orow-state {
  display: grid;
  justify-items: end;
  gap: var(--s-1);
}

.ui .orow-date {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
}

.ui .orow-date--overdue {
  color: var(--danger);
  font-weight: var(--w-semi);
}

.ui .orow-actions {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex: 0 0 auto;
}

.ui .orow-go svg {
  width: 15px;
  height: 15px;
  color: var(--text-tertiary);
}

.ui .orow:hover .orow-go svg {
  color: var(--text-secondary);
}

/* Row actions appear on hover and on keyboard focus — always reachable, never twelve
   buttons competing down a list. */
@media (hover: hover) {
  .ui .orow-actions > .on-hover {
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-out);
  }

  .ui .orow:hover .orow-actions > .on-hover,
  .ui .orow:focus-within .orow-actions > .on-hover {
    opacity: 1;
  }
}

.ui .orow--closed .orow-supplier {
  color: var(--text-secondary);
  font-weight: var(--w-semi);
}

.ui .olist-removed {
  display: grid;
  gap: var(--s-4);
  margin: var(--s-4) 0 0;
  padding: 0;
  list-style: none;
}

.ui .olist-removed li {
  display: flex;
  align-items: center;
  gap: var(--s-5);
}

.ui .olist-removed-meta {
  margin-right: auto;
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

@media (max-width: 900px) {
  .ui .orow-main {
    grid-template-columns: minmax(0, 1fr) minmax(120px, auto);
    row-gap: var(--s-4);
  }

  .ui .orow-parts {
    grid-column: 1 / -1;
  }
}

/* ===========================================================================
 * CART (.cart2 / .cart-page) — the same panel at page width and in the popover.
 * =========================================================================== */

.ui .cart-page {
  display: grid;
  gap: var(--s-6);
}

.ui .cart2 {
  display: grid;
  gap: var(--s-7);
}

.ui .cart2-group {
  display: grid;
  gap: var(--s-4);
  min-width: 0;
}

.ui .cart2-group-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
}

.ui .cart2-group-head h3 {
  margin: 0;
  font-size: var(--t-base);
  font-weight: var(--w-bold);
}

.ui .cart2-group-meta {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .cart2-clear {
  margin-left: auto;
}

/* The per-supplier send control, at the end of the supplier's own heading. It is the last
   thing in the row on purpose: "Išvalyti" appears on hover and this one is always there,
   so the press that costs money is the one whose position never moves. */
.ui .cart2-send {
  flex: none;
}

/* Why the disabled one is disabled, when the app-wide notice at the bottom of the panel
   is NOT rendered — the gate is on and it is this supplier that was never enabled. */
.ui .cart2-send-why {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
}

/* Sent, and no answer came back. Stands exactly where the send button stood, because its
   whole job is to be the thing the operator finds instead of the button. */
.ui .cart2-awaiting {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--warning);
  font-size: var(--t-sm);
}

.ui .cart2-awaiting svg {
  flex: none;
  width: 1rem;
  height: 1rem;
}

/* Asking for more units than the supplier's own stock text accounted for. Deliberately a
   warning and not an error tone: buying two of something they show one of is a normal
   decision. It sits UNDER the product, inside the same cell, so it stays attached to the
   line it is about when the table stacks into cards on a phone. */
.ui .cart-gap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
  margin-top: var(--s-2);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  color: var(--warning);
  background: color-mix(in srgb, var(--warning) 10%, transparent);
  font-size: var(--t-sm);
  line-height: var(--lh-tight);
}

.ui .cart-gap > svg {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
}

.ui .cart-gap > span {
  min-width: 0;
}

.ui .cart-gap .btn {
  margin-left: auto;
}

/* What the supplier held and when it would arrive. The supplier's own wording is long
   ("Likutis (gamintojo sandėlis): 1 vnt.", "Pristatymas 1 d.d. užsakius iki 16:45"), so these
   two cells wrap inside a fixed measure instead of taking `.shrink`'s nowrap and pushing
   the money columns off the right edge. The day itself is the figure; the words sit under
   it, quieter, so it can be traced back to them. */
.ui .cart2-lines .cart-c-stock,
.ui .cart2-lines .cart-c-eta {
  width: 10rem;
  white-space: normal;
}

/* Every supplier is its own table, so a column only lines up with the one below it when
   its width does not depend on what happens to be in it. The part name takes whatever is
   left. The popover (.cart2--compact) keeps its content-sized columns: it has no room. */
.ui .cart2:not(.cart2--compact) .cart2-lines .cart-c-price,
.ui .cart2:not(.cart2--compact) .cart2-lines .cart-c-sum {
  width: 6.5rem;
}

.ui .cart2-lines .cart-fact {
  display: block;
  color: var(--text);
  font-size: var(--t-sm);
  line-height: var(--lh-tight);
}

.ui .cart2-lines .cart-fact-note {
  display: block;
  margin-top: 2px;
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  line-height: var(--lh-tight);
}

/* A line below the supplier's own minimum: it cannot be sent as it stands, so the note
   takes the warning tone and the one press that fixes it sits under it. */
.ui .cart2-lines .cart-min-short {
  color: var(--warning);
}

.ui .cart2-lines .cart-min-raise {
  margin-top: var(--s-2);
}

/* The supplier's minimum beside a search row's quantity stepper. */
.ui .min-qty-note {
  display: block;
  margin-top: 2px;
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  line-height: var(--lh-tight);
  white-space: nowrap;
}

/* The same fact in the header popover, where there is room for four words and no room for
   a button. Legacy popover markup (style.css), so it only borrows the colour. */
.cart-popover-item-gap {
  color: var(--warning);
  font-weight: var(--w-semi);
}

@media (hover: hover) {
  .ui .cart2-group .on-hover {
    opacity: 0;
    transition: opacity var(--dur-1) var(--ease-out);
  }

  .ui .cart2-group:hover .on-hover,
  .ui .cart2-group:focus-within .on-hover {
    opacity: 1;
  }
}

/* THE PAGE'S OWN ACTION BAR, and it had to become one. „Užsakyti viską" is the most
   consequential press in this application and it sat at the far right of a hairline rule,
   in the same quiet weight as „Išvalyti viską", smaller than the row's own remove buttons —
   the owner's words were that they could not find it at all. So the foot is a SURFACE now:
   its own panel, the figures on the left, the sum and the two presses on the right, and
   the one that sends is the only loud thing on the page. Wraps to one column on a phone,
   where the presses come last because the figures are what is read first. */
.ui .cart2-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4) var(--s-5);
  margin-top: var(--s-6);
  padding: var(--s-5) var(--s-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  font-size: var(--t-md);
}

.ui .cart2-foot-counts {
  color: var(--text-secondary);
}

.ui .cart2-foot-total {
  margin-left: auto;
  color: var(--text-secondary);
}

.ui .cart2-foot-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3);
}

@media (max-width: 640px) {
  .ui .cart2-foot {
    align-items: stretch;
  }

  .ui .cart2-foot-total {
    margin-left: 0;
  }

  .ui .cart2-foot-actions {
    width: 100%;
  }

  .ui .cart2-foot-actions > * {
    flex: 1 1 auto;
    justify-content: center;
  }
}

.ui .cart2-compact-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--border);
  font-size: var(--t-md);
}

.ui .cart2-compact-head .money {
  margin-left: auto;
}

/* In the popover there is no room for hover-revealed controls or a price column. */
.ui .cart2--compact {
  gap: var(--s-5);
}

.ui .cart2--compact .on-hover {
  opacity: 1;
}

.ui .cart2--compact .prod-thumb {
  width: 30px;
  height: 30px;
}

.ui .cart2--compact .tbl tbody td {
  padding: var(--s-4);
}

/* A summary trigger that has to look like a button. */
.ui .menu > summary.cart-menu-trigger {
  width: auto;
  height: var(--h-md);
  padding: 0 var(--s-5);
}

.ui .menu > summary.cart-menu-trigger:hover,
.ui .menu[open] > summary.cart-menu-trigger {
  color: var(--on-accent);
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.ui .menu > summary.cart-menu-trigger > svg:last-child {
  width: 12px;
  height: 12px;
  opacity: 0.7;
}

/* A <summary> that has to read as a primary button needs the button's colours at REST, not
   only on hover: .menu > summary sets a muted foreground, and a blue label on a blue field
   is how "Sudaryti pirkimą" ended up invisible. */
.ui .menu > summary.btn--primary {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

/* A supplier photo that 404s must not leave a broken-image glyph in a 38px box — worse than
   the honest placeholder. app.js swaps the state; this is what it swaps to. */
.ui .prod-thumb--broken > img {
  display: none;
}

/* The fallback glyph is rendered alongside the <img> so no request is needed to reveal it —
   it stays hidden until onerror marks the slot empty. */
.ui .prod-thumb:not(.prod-thumb--empty) > svg {
  display: none;
}

/* ===========================================================================
 * SEARCH RESULTS — the densest surface in the app.
 * =========================================================================== */

/* The table was trapped in a 610px inner scroller holding 6272px of rows, so scanning 133
   offers meant scrolling a box inside a scrolling page, and the page's own scrollbar lied
   about how much was left. The page scrolls now.
 *
 * The overflow container has to go with it, not just its height: a scroll container becomes
 * the scrollport that `position: sticky` resolves against, so a sticky <thead> inside a
 * non-scrolling box never sticks to anything. Measured — the column names simply were not
 * there while scrolling. Above 1240px the 1120px-wide table fits, so no container is needed;
 * below that, horizontal scrolling matters more than a sticky header and the box comes back. */
.ui .results-table-wrapper {
  max-height: none;
  overflow: visible;
}

@media (max-width: 1180px) {
  .ui .results-table-wrapper {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }
}

/* A SAVED search scrolls INSIDE ITS OWN BOX, and this rule lives HERE because the one above
   is what stopped it living anywhere else. `.ui .results-table-wrapper` sets `max-height:
   none; overflow: visible` for a good reason — the live results table is the page, and its
   sticky column row needs the page as its scrollport. That override also beat the cap
   `style.css` puts on saved snapshots (same specificity, later file), so a past search has
   been opening to its full height for as long as both rules have existed: scrolling through
   ninety rows to reach the next search in the list.
   A snapshot is not the page. It is one entry among many, it needs no page-sticky header,
   and its pinned rows re-pin to the box — `top: var(--sticky-top)` inside a scroll container
   would park the column names 120px down from its own top edge. */
.ui .results-table-wrapper--capped {
  /* THE BOX IS THE ANCHOR, not the rows. Every row carries absolutely positioned pieces (the
     visually hidden quantity labels, the thumbnails), and they are clipped by this box only if
     their containing block is inside it. The rows are `position: relative`, but a browser that
     does not let a table row anchor (WebKit has not always) hands them to an ancestor OUTSIDE
     the box — unclipped, at their place in a 20 000px table — and the page grew a screenful of
     empty scroll under the history list (owner, 2026-10-01). Measured: 1983px with this, 21345px
     without it once the rows stop anchoring. */
  position: relative;
  max-height: min(60vh, 520px);
  overflow: auto;
  overscroll-behavior: contain;
}

/* A SNAPSHOT'S OWN BOX IS THE ONE PLACE THESE STILL PIN, and it is the place where pinning
   costs nothing: the box is the scrollport, so the column row sits on its own top edge with
   the box's rounded corner around it, and nothing is being covered by anything. On the live
   results the page is the scrollport and that is what made a pinned row a permanent hard
   edge under the floating bar — see "NOT PINNED TO THE PAGE" below. */
.ui .results-table-wrapper--capped .results-table thead th {
  position: sticky;
  top: 0;
  z-index: 3;
}

/* `.results-table` in the selector is load-bearing. Without it this rule and the live table's
   `.ui .results-table .result-group-row td` (further down) weigh the same, the later one wins,
   and inside the box the supplier row pinned at the PAGE's offset — the floating bar's height
   plus the column row, ~180px down — in the middle of the table instead of under its header.
   `--thead-h` here is THIS box's own column row, set on the wrapper by app.js
   (`measureStickyBand`), because a snapshot arrives after the page-level measurement ran. */
.ui .results-table-wrapper--capped .results-table .result-group-row td {
  position: sticky;
  top: var(--thead-h, 40px);
  z-index: 2;
}

/* NOT PINNED TO THE PAGE, and that is the whole answer to the band.
   Three shapes were tried here. The column row pinned under the floating bar, which left a
   hard white slab permanently butted against a rounded card. The same with air and a hair
   of shadow under it, which only drew the eye to the join. Then the card became a window of
   its own, which fenced the results off from the page in a box with a gap around it.
   The fourth was to pin nothing: the page scrolls, the card goes UNDER the bar, and it is
   gone — it does not reappear above the bar, beside it, or through it (`.page-top-cover`
   sees to that).

   AND THAT FOURTH SHAPE COST TOO MUCH. It was written down at the time as a real cost —
   „on a long table you scroll back up to read them" — and on 2026-09-17 the owner named it:
   the column names and the supplier a row belongs to are both gone the moment you scroll,
   and on a table of ninety offers across eleven suppliers a price with no supplier over it
   is not information. So they pin again, and the difference from the first shape is the
   ONLY thing that was ever wrong with it: what they pin to.

   `--sticky-top` is the bar's own bottom edge, published by `measureStickyBand` in app.js
   from the bar's real box rather than guessed. The column row therefore lands flush against
   the bar instead of below it with a slab of page showing between the two — that gap is
   what read as a second, hard-edged bar, and there is none to read now. The row wears the
   card's surface and a hairline, which is what the table's top edge already looked like, so
   what stops under the bar is the table's own top and not a new object. And nothing else
   changed: a row still travels behind the bar and disappears. */
.ui .results-table thead th {
  position: sticky;
  top: calc(var(--sticky-top) + var(--sticky-gap));
  z-index: 3;
  background: var(--surface);
  box-shadow: inset 0 -1px 0 var(--border);
}

/* THE DAYLIGHT ITSELF, and it has to be SQUARE.
 *
 * A row scrolling up is visible in the gap above the pinned header — the panel's background
 * is painted below the rows, not above them — so something travelling with the header has to
 * paint that strip. A `box-shadow` did, and almost worked: an outer shadow takes the
 * element's own border-radius, so its top corners curved away exactly where the table's side
 * edge is, and the first thing visible through that notch was the side border of every row
 * sliding past. Two hairlines climbing out of the table and up to the bar — which is what
 * the owner saw.
 *
 * A square band, as wide as the cell and anchored to it, leaves no notch. It is a child of
 * the sticky cell, so it travels with it and needs nothing kept in step; at rest it paints
 * the panel's surface onto the panel's surface and cannot be seen. */
.ui .results-table thead th::before {
  content: "";
  position: absolute;
  /* ABOVE THE CELL'S BORDER BOX, not its padding box — which is what an absolutely
     positioned child measures against. `bottom: 100%` alone parks the band ON the header's
     own top border and rubs it out, leaving the rounded corner as a floating arc; the extra
     pixel puts it exactly above that border. The side insets are the same correction on the
     other axis: the first and last cells carry the table's side edge, so the band has to
     reach their border box or the very hairline it exists to cover survives at the edge. */
  bottom: calc(100% + 1px);
  right: -1px;
  left: -1px;
  height: var(--sticky-gap);
  background: var(--surface);
}

/* AND THE TWO CRESCENTS THE CURVE ITSELF LEAVES OPEN.
 *
 * A rounded corner is a hole. `border-radius` clips the cell's own background and border, so
 * the crescent OUTSIDE the curve — r by r at each end of the pinned row — paints nothing at
 * all, and what shows through it is whatever is behind the header: the rows. The first thing
 * a row carries at that exact x is the table's side edge, so every row sliding past lit a
 * 1px stub of border standing above the curve, where the table has already ended. That is
 * the one the owner was still seeing after the band went in: „corners still has this line
 * ... cuz there's a round end so it has to go across it".
 *
 * The band above cannot reach it. The crescent is INSIDE the cell's box, and a rectangle
 * that covered it would cover the corner with it. So the crescent is filled BY ITS OWN
 * SHAPE: an outer `box-shadow` is clipped by the border box's rounded outline and therefore
 * paints everywhere outside it, `clip-path` keeps only the part that falls inside the cell's
 * own top strip, and `border-radius: inherit` means the hole left behind is the cell's
 * corner and cannot drift from it by a pixel. A middle cell has no radius, nothing falls
 * outside its outline, and the same rule paints nothing at all.
 *
 * NOT the snapshot box, for the reason the rule below gives at greater length: there the
 * WRAPPER draws the outline, its border arc sits inside this very crescent, and filling it
 * would rub that arc out. */
.ui .results-table-wrapper:not(.results-table-wrapper--capped) .results-table thead th::after {
  content: "";
  position: absolute;
  /* The cell's border box, not its padding box — the same correction the band makes, and
     for the same reason: the corner is a fact about the border box. */
  inset: -1px -1px auto;
  height: var(--r-md);
  border-radius: inherit;
  box-shadow: 0 0 0 var(--r-md) var(--surface);
  clip-path: inset(0);
}

/* THE BOX IS PAINTED BY THE CELLS, AND THAT IS WHY IT STOPS AT THE HEADER.
 *
 * Two faults, one cause. `.results-table-wrapper` carried the border and the 8px radius,
 * and a wrapper is not part of what scrolls under the pinned row: its left and right
 * borders ran the full height of the table, so with daylight under the bar they were
 * visible in it — two hairlines climbing out of the table and up to the bar, which is not
 * a thing the table has. And its rounded corners were being painted over: `.results-table`
 * sets `background: var(--surface)` with no radius of its own, so the table's own square
 * white corner sat on top of the wrapper's curve — the bottom corners in particular ended
 * as a stub of vertical line, a gap, and a stub of horizontal line that never met.
 *
 * So the wrapper stops drawing a box and the CELLS draw it: first cell of every row the
 * left edge, last cell the right, the header the top, the last visible row the bottom, and
 * the four corner cells the curve (they already rounded themselves — see `.results-table
 * thead th:first-child` in style.css and `.is-last-visible` below). Every one of those is
 * inside the table, so every one of them scrolls under the pinned header and is covered by
 * the strip that header paints above itself. There is nothing left that can stand above it.
 *
 * The table keeps a radius too, because its background is a layer in its own right and was
 * the square one.
 *
 * NOT the snapshot box. `--capped` is a real scroll container with its own top edge, its
 * rows scroll INSIDE it, and there the border belongs to the box exactly as it did. */
.ui .results-table-wrapper:not(.results-table-wrapper--capped) {
  border: 0;
  border-radius: 0;
}

.ui .results-table-wrapper:not(.results-table-wrapper--capped) .results-table {
  border-radius: var(--r-md);
}

.ui .results-table-wrapper:not(.results-table-wrapper--capped)
  .results-table
  > :is(thead, tbody)
  > tr
  > :is(th, td):first-child {
  border-left: 1px solid var(--border);
}

.ui .results-table-wrapper:not(.results-table-wrapper--capped)
  .results-table
  > :is(thead, tbody)
  > tr
  > :is(th, td):last-child {
  border-right: 1px solid var(--border);
}

.ui .results-table-wrapper:not(.results-table-wrapper--capped)
  .results-table
  tbody
  tr.is-last-visible
  > :is(td, th) {
  border-bottom: 1px solid var(--border);
}

/* A supplier's group header also sticks, directly under the column row — `--thead-h` is
   that row's measured height — so the supplier a row belongs to is never off-screen while
   reading its prices. That was the half of this the owner asked about first. */
.ui .results-table .result-group-row td {
  position: sticky;
  top: calc(var(--sticky-top) + var(--sticky-gap) + var(--thead-h, 40px));
  z-index: 2;
  background: var(--surface-2);
}

/* BELOW 1180 THE PAGE IS NOT THE SCROLLPORT. `.ui .results-table-wrapper` takes
   `overflow-x: auto` there so a 1120px table can be read on a narrow screen, and an
   overflow on one axis makes the box a scroll container on BOTH — so `position: sticky`
   resolves against the wrapper, where an offset measured from the top of the PAGE means
   nothing and would park the column names 80px down inside the box. Horizontal scrolling
   matters more at that width than a pinned header, so the pinning simply stops. */
@media (max-width: 1180px) {
  .ui .results-table thead th,
  .ui .results-table .result-group-row td {
    position: static;
  }

  /* And the band goes with the pinning. A static cell is not a positioned ancestor, so the
     band would anchor to whatever is, and paint a strip of surface somewhere up the page. */
  .ui .results-table thead th::before {
    content: none;
  }
}

/* A menu that opens near the bottom of the page must not run off it. Capping the height and
   letting the panel scroll itself keeps every item reachable without measuring anything in
   JS — and the cap is generous enough that the common menus never scroll at all. */
.ui .menu-panel {
  max-height: min(64vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Panels that hold a popover cannot also be scroll containers: overflow establishes a
   clipping context, and the row menu is taller than its row. Stated here so the next person
   who reaches for .panel-scroll on a table with menus in it finds the reason. */
.ui .panel:not(.panel-scroll) {
  overflow: visible;
}

/* An element revealed on hover is hidden with opacity, and opacity applies to the whole
   subtree — so an OPEN menu on such a row stayed invisible the moment the pointer left it,
   and was completely invisible to the keyboard. Anything open, focused, or containing focus
   is always fully opaque. Found by opening a row menu and screenshotting it: the panel was
   there, in the right place, at opacity 0. */
.ui .on-hover:is([open], :focus, :focus-within),
.ui .row-actions > .on-hover:is([open], :focus-within),
.ui .orow-actions > .on-hover:is([open], :focus-within),
.ui .cart2-group .on-hover:is([open], :focus-within) {
  opacity: 1;
}

/* And a row whose menu is open stays highlighted, so it is obvious which line the panel
   belongs to once the pointer has moved into the panel itself. */
.ui tr:has(.menu[open]) {
  background: var(--surface-2);
}

.ui .menu-item-aside {
  margin-left: auto;
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
}

/* The results table's own conversions. The supplier's product page is reached from the
   product NAME now; the column that held one "Peržiūrėti" link per row is gone. */
.ui a.product-label {
  color: inherit;
  text-decoration: none;
}

.ui a.product-label:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The column header pins under the app header (the app header is sticky too, so `top: 0`
   put the two on the same line and the column names disappeared). The offsets used to be
   written here as literal 78px and 111px — a FOURTH pair of numbers for the same two facts,
   and the pair that actually won by being latest in the file. They live on
   `--sticky-top` / `--thead-h` now; the rules that read them are with the rest of the
   results-table styles above. */

.ui .add-to-cart form {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  justify-content: flex-end;
}

/* ===========================================================================
 * THE DATE COMPONENT (.dp) — one control, every date in the app.
 *
 * Two shapes, one body. Standalone it is a trigger with a popover; inside an action menu
 * it is an accordion that expands where it stands, because a popover opening inside a
 * popover clipped, escaped the viewport, and closed its own parent on any outside click.
 *
 * There is no `<input type="date">` anywhere in it. The native widget's DISPLAY format is
 * the browser's locale and cannot be set by the page, so on an en-US browser it printed
 * 08/19/2026 two centimetres from a badge reading 2026-08-19 — two formats for one date,
 * on one screen. What replaces it: the proposal read out of the supplier's own delivery
 * text, four chips, and four weeks of buttons.
 * =========================================================================== */

.ui .dp {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  min-width: 0;
}

.ui .menu > summary.dp-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  /* `width: auto` is load-bearing: the base `.menu > summary` is a SQUARE icon button, and
     without this the trigger stayed that square while its label ("2026-08-18") overflowed
     centred — measured at 500px, a 64px label inside a 26px control, spilling 6px past the
     left edge of the screen. Same line `.od-date-trigger` already carried. */
  width: auto;
  height: var(--h-sm);
  padding: 0 var(--s-4);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--t-md);
  font-weight: var(--w-semi);
}

.ui .dp-trigger > svg {
  width: 13px;
  height: 13px;
  color: var(--text-tertiary);
}

/* No date is NO DATE: muted, never today, never late. */
.ui .dp-trigger--empty {
  color: var(--text-tertiary);
  font-weight: var(--w-medium);
}

.ui .dp-trigger--overdue {
  color: var(--danger);
  border-color: var(--danger);
}

.ui .dp-trigger--overdue > svg {
  color: var(--danger);
}

.ui .dp-panel {
  width: 268px;
  min-width: 0;
  padding: var(--s-4);
}

/* --- the accordion shape, for use inside an open menu ------------------------ */

.ui .dp--inline {
  display: block;
  width: 100%;
}

.ui .dp-acc-trigger {
  display: flex;
  align-items: center;
  /* Wraps rather than clips. The value is right-aligned and drops to its own line when the
     label beside it is long ("Likusios dalies data"), which is what the panel's
     `overflow-x: hidden` used to turn into a cut-off word. */
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
  min-width: 0;
  padding: var(--s-3) var(--pad, var(--s-4));
  color: var(--text);
  border-radius: var(--r-sm);
  font-size: var(--t-md);
  font-weight: var(--w-medium);
  cursor: pointer;
  list-style: none;
  transition: background var(--dur-1) var(--ease-out);
}

.ui .dp-acc-trigger::-webkit-details-marker {
  display: none;
}

.ui .dp-acc-trigger:hover {
  background: var(--surface-2);
}

.ui .dp-acc-trigger > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  color: var(--text-tertiary);
}

.ui .dp-acc-value {
  margin-inline-start: auto;
  min-width: 0;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  text-align: end;
}

.ui .dp--inline[open] > .dp-acc-trigger {
  color: var(--text);
  font-weight: var(--w-semi);
}

.ui .dp-body {
  padding: 0 var(--pad, var(--s-2)) var(--s-3);
}

/* --- the shared body: proposal, chips, calendar ------------------------------ */

.ui .dp-row {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-4);
}

.ui .dp-origin {
  min-width: 0;
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  line-height: var(--lh-tight);
}

.ui .dp-quick {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-4);
}

/* A chip is the smallest thing that can still be pressed by a thumb: this is the control
   that turns "supplier said 2-3 days" into a stored date in one gesture. */
.ui .dp-chip {
  display: inline-flex;
  align-items: center;
  height: var(--h-sm);
  padding: 0 var(--s-4);
  color: var(--text-secondary);
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}

.ui .dp-chip:hover {
  color: var(--text);
  background: var(--surface-3);
  border-color: var(--border);
}

/* THE SELECTED CHIP. Two faults at once.
 *
 * The regression: the line editor repaints chips to `--surface` so they are visible on its
 * grey band, and that selector has the same specificity as this one while sitting later in
 * the file — so it won the BACKGROUND and left this rule's white text on white. The chip
 * read as an empty pill until hovered. Fixed by not repainting a chosen chip (see the
 * `:not(.is-chosen)` in the editor block below), and by not relying on white text here.
 *
 * The design: an accent fill made "chosen" the loudest thing in the panel, when all it has
 * to do is say which of four it is. A filled surface with a real border and ordinary text
 * says exactly that, in the app's own text colour, in either theme. */
.ui .dp-chip.is-chosen {
  color: var(--text);
  background: var(--surface-3);
  border-color: var(--border-strong);
  font-weight: var(--w-bold);
}

.ui .dp-grid {
  display: grid;
  gap: var(--s-2);
  min-width: 0;
  max-width: 100%;
}

/* `.menu-panel` scrolls vertically, which makes overflow-x compute to `auto` too: two
   stray pixels from the calendar were enough to put a horizontal scrollbar inside the
   product menu. Nothing in a date panel is ever meant to scroll sideways. */
.ui .dp-panel,
.ui .od-more-panel {
  overflow-x: hidden;
}

.ui .dp-grid-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
}

.ui .dp-grid-label {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

.ui .dp-shift-back {
  transform: rotate(180deg);
}

.ui .dp-week-names,
.ui .dp-days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
}

.ui .dp-week-names > span {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  text-align: center;
}

.ui .dp-day {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 28px;
  color: var(--text);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  font: inherit;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition:
    background var(--dur-1) var(--ease-out),
    color var(--dur-1) var(--ease-out);
}

.ui .dp-day:hover {
  background: var(--surface-2);
  border-color: var(--border);
}

.ui .dp-day.is-weekend {
  color: var(--text-tertiary);
}

/* Quieter, never disabled: an order can already be late, and dating one to last Friday is
   a legitimate correction of a delivery nobody wrote down. */
.ui .dp-day.is-past {
  opacity: 0.45;
}

.ui .dp-day.is-today {
  color: var(--accent);
  font-weight: var(--w-bold);
  box-shadow: inset 0 0 0 1px var(--accent);
}

.ui .dp-day.is-chosen {
  color: var(--on-accent);
  background: var(--accent);
  font-weight: var(--w-bold);
  box-shadow: none;
}

.ui .dp-clear {
  margin-top: var(--s-3);
}

/* ===========================================================================
 * THE COMPACT PRODUCT MENU (.od-more) — four short sections, states first.
 * =========================================================================== */

/* ONE LEFT EDGE. Every section in here used to pick its own inline padding — the state
 * chips and the quantity rows sat at 10px, the "DALIMIS" label, the date accordion and the
 * delete item at 14px — so reading down the panel the text stepped in and out four times.
 * `--pad` is that edge, set once and used by every section below, including the ones this
 * panel borrows from the shared menu (`.menu-label`, `.menu-item`), which already used it.
 *
 * And the width is no longer a fixed 276px. The panel's own `overflow-x: hidden` (which is
 * there to stop the calendar putting a horizontal scrollbar inside a popover) turned "too
 * narrow" into "silently cut off" rather than into a wrap — a clipped word reads as a
 * rendering fault and the operator cannot tell what it was going to say. It now sizes to
 * its content within a bound, and never past the viewport on a phone. */
.ui .od-more-panel {
  --pad: var(--s-4);
  /* 288px, near Material's 280dp ceiling for a menu. It was 320 for one release because
     the quantity fields were stretching to fill whatever they were given; they have a
     width of their own now, so the panel does not have to carry theirs. */
  width: min(288px, calc(100vw - 2rem));
  padding: var(--s-2) 0;
}

/* Dividers are INSET to the same edge as the text, not run wall to wall. Full-bleed rules
   turned a floating menu into a striped table — Carbon and Atlassian both inset a menu's
   group rules for exactly this reason, and with five groups the stripes were the loudest
   thing in the panel. */
.ui .od-more-panel > .menu-sep {
  margin-inline: var(--pad);
}

.ui .od-more-states {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  padding: var(--s-2) var(--pad);
}

/* A QUANTITY IS THREE CHARACTERS WIDE. This row was `4.5rem minmax(0, 1fr) auto`, so a
   field that will only ever hold "1", "2" or "12" stretched to whatever was left — 188px
   of a 320px panel, per row, twice. That single decision is most of why the panel read as
   bloated: two thirds of its width was empty input. */
.ui .od-more-row {
  display: grid;
  grid-template-columns: 1fr 4rem auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--pad);
}

.ui .od-more-row > label {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

.ui .od-more-row .od-qty {
  width: 100%;
}

/* ===========================================================================
 * THE LINE EDITOR (.od-editor) — a form, in a row, at the table's own width.
 *
 * It used to live in the `…` popover: eight decision points, three number inputs, a
 * section header and a four-week calendar, inside 288px. Every reference says the same
 * thing about that shape — a menu is a short list of choices, and a popover holds one
 * decision and no fields — and each round of tidying only produced a tidier version of the
 * wrong container.
 *
 * So it opens under its own row instead, spanning every column. The width was always
 * there; the popover was the only thing hiding it.
 * =========================================================================== */

.ui .od-editor-row > td {
  padding: 0;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

.ui .od-editor {
  display: grid;
  gap: var(--s-5) var(--s-8);
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) minmax(0, auto);
  align-items: start;
  padding: var(--s-5) var(--s-6) var(--s-5) var(--s-9);
}

/* Each group is a label above its controls — the three read as one row of the same thing,
   rather than as three unrelated widgets that happen to be adjacent. */
.ui .od-editor-group {
  display: grid;
  gap: var(--s-3);
  min-width: 0;
}

.ui .od-editor-label {
  color: var(--text-tertiary);
  font-size: var(--t-xs);
  font-weight: var(--w-bold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.ui .od-editor-fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5);
}

/* A quantity is three characters wide. In the popover these stretched to fill whatever was
   left — 188px of a 288px panel, twice — which was most of why it read as bloated. */
.ui .od-field {
  display: flex;
  align-items: center;
  gap: var(--s-3);
}

/* „iš 2" beside a typed count: the bound the field enforces, stated where it is typed. */
.ui .od-field-of {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ui .od-field > label {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

.ui .od-field .od-qty {
  width: 4rem;
}

.ui .od-editor-parts {
  display: grid;
  gap: var(--s-3);
  min-width: 0;
}

/* The date control inside a piece row STOPS STRETCHING here. Its two rules — `flex: 1 1
   120px` on the control and `margin-inline-start: auto` on its value — were written for a
   288px popover, where filling the width was the whole point. In a row that now spans the
   table they threw the date and its two buttons ~700px away from the "1 vnt" they belong
   to, which is the same fact split across half a screen. */
.ui .od-editor .od-part-row .dp {
  flex: 0 1 auto;
}

.ui .od-editor .od-part-row .dp-acc-value {
  margin-inline-start: var(--s-4);
}

/* GROUPS ARE AS WIDE AS THEY ARE, laid left to right, and the slack is left over at the
   end of the row. Giving any column `1fr` handed it the rest of the table, and these
   controls were all written for a 288px panel where filling the width was the point: the
   accordion pushes its value with `margin-inline-start: auto`, so in a 1fr column the date
   flew to the far edge of the screen away from the label it belongs to.
   `.dp--inline` is capped for the same reason — it fills what it is given, so it has to be
   given something sane rather than a table's worth. */
.ui .od-editor {
  grid-template-columns: repeat(3, minmax(0, max-content));
  justify-content: start;
  column-gap: var(--s-11);
}

.ui .od-editor .dp--inline {
  max-width: 20rem;
}

/* "Kita data" is a chip that happens to open a calendar, so it is a <details> whose summary
   is drawn as one — and it takes the whole row when open, for the same reason the piece's
   own control does: a calendar wedged beside three chips is a calendar with a clipped
   column. */
.ui .od-part-cal {
  display: contents;
}

.ui .od-part-cal > summary {
  list-style: none;
}

.ui .od-part-cal > summary::-webkit-details-marker {
  display: none;
}

.ui .od-part-cal[open] > .dp-body {
  flex-basis: 100%;
  padding: var(--s-4) 0 0;
}

/* One piece is one line. Wrapping split "1 vnt", its date and its two buttons across three
   rows — the same fact read as three. */
.ui .od-editor .od-part-row {
  flex-wrap: nowrap;
  padding-inline: 0;
}

/* THE CHIPS WERE INVISIBLE. `.dp-chip` and `.od-part-tag` are filled with `--surface-2`,
   which is exactly the colour this row is painted — so on the grey band the day chips read
   as four words of plain text with nothing to suggest they could be pressed, and the piece's
   quantity lost its badge. They take the panel colour here and keep an edge. */
.ui .od-editor .dp-chip:not(.is-chosen),
.ui .od-editor .od-part-tag {
  background: var(--surface);
  border: 1px solid var(--border);
}

.ui .od-editor .dp-chip:not(.is-chosen):hover {
  background: var(--surface-3, var(--surface-2));
  border-color: var(--border-strong);
}

/* The row that opened it stays marked while it is open, so a long table never leaves an
   editor stranded under a row nobody can pick out. */
.ui .od-line.is-editing > td {
  background: var(--surface-2);
}

.ui .od-edit-toggle > svg {
  transition: transform var(--dur-2) var(--ease-out);
}

.ui .od-edit-toggle.is-open > svg {
  transform: rotate(180deg);
}

@media (max-width: 900px) {
  .ui .od-editor {
    grid-template-columns: minmax(0, 1fr);
    padding: var(--s-5);
  }
}

/* ===========================================================================
 * ONE PURCHASE (.pu) — the act of buying, and the orders that answer it.
 * =========================================================================== */

.ui .pu {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-6);
}

.ui .pu-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-6);
}

.ui .pu-titles {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3);
  min-width: 0;
}

.ui .pu-title {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin: 0;
  min-width: 0;
  font-size: var(--t-xl);
  font-weight: var(--w-bold);
  letter-spacing: -0.01em;
  line-height: var(--lh-tight);
}

.ui .pu-title > svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: var(--text-tertiary);
}

/* --- inline rename ----------------------------------------------------------
 * The name IS the field. Double-click makes this exact text editable in place — same
 * font, same position, same line — instead of revealing a separate input underneath it.
 * The ring and the background are the only things that appear, and they fade in, so the
 * change reads as "you may type here" rather than as a new control. */
.ui .pu-name {
  min-width: 2ch;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: var(--r-sm);
  outline: none;
  cursor: text;
  transition:
    background var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}

.ui .pu-name.is-editing {
  background: var(--surface);
  box-shadow: var(--focus-ring-tight);
  cursor: text;
}

/* The deferred basket's own name, edited where it is shown — the same control a purchase
   has (`.pu-name` above), so the two things in this app that carry an operator's own label
   behave identically. Sharing the `.pu-*` classes was tempting and wrong: those are the
   purchase card's, and a later change to one would silently move the other. */
.ui .cart-copy-title {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0;
  font-size: var(--t-xl);
}

.ui .cart-copy-name {
  min-width: 2ch;
  padding: 2px 4px;
  margin: -2px -4px;
  border-radius: var(--r-sm);
  outline: none;
  cursor: text;
  transition:
    background var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}

.ui .cart-copy-name.is-editing {
  background: var(--surface);
  box-shadow: var(--focus-ring-tight);
}

.ui .cart-copy-rename-hint {
  opacity: 0;
  transition: opacity var(--dur-1) var(--ease-out);
}

.ui .cart-copy-title:hover .cart-copy-rename-hint,
.ui .cart-copy-rename-hint:focus-visible {
  opacity: 1;
}

.ui .cart-copy-when {
  margin: var(--s-2) 0 0;
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

.ui .pu-num {
  padding: 0 var(--s-3);
  color: var(--text-secondary);
  background: var(--surface-2);
  border-radius: var(--radius-full);
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  font-variant-numeric: tabular-nums;
}

.ui .pu-rename-hint {
  opacity: 0;
  transition: opacity var(--dur-1) var(--ease-out);
}

.ui .pu-title:hover .pu-rename-hint,
.ui .pu-rename-hint:focus-visible {
  opacity: 1;
}

.ui .pu-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-5);
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--t-md);
}

.ui .pu-head-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex: 0 0 auto;
}

.ui .pu-date {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--t-md);
}

.ui .pu-date > svg {
  width: 13px;
  height: 13px;
  color: var(--text-tertiary);
}

.ui .pu-date small {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
}

/* An unfinished job, drawn as one: what is missing, then the buttons that finish it. */
.ui .pu-todo {
  display: grid;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-6);
  background: color-mix(in srgb, var(--warning) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--warning) 26%, transparent);
  border-radius: var(--r-lg);
}

.ui .pu-todo-head {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  font-size: var(--t-md);
}

.ui .pu-todo-head > svg {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--warning);
}

.ui .pu-todo-head > div {
  display: grid;
  gap: var(--s-2);
  min-width: 0;
}

.ui .pu-todo-head span {
  color: var(--text-secondary);
}

.ui .pu-todo-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.ui .pu-suppliers {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui .pu-supplier {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-5);
  padding: var(--s-6) var(--s-7);
}

/* `.pu-supplier--missing` is gone with the amber panels it belonged to. A supplier we
   have not sent an order to yet is not a fault and does not get a coloured surface; it
   says "Dar nepateikta" in muted text and offers the press that fixes it. */

.ui .pu-supplier-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
}

.ui .pu-supplier-name {
  font-size: var(--t-base);
  font-weight: var(--w-bold);
}

/* This supplier's own subtotal, pushed to the far end of the head so a column of supplier
   cards reads down the right edge as a column of figures. It is the last child, never the
   first: the name and the state badge are what the row is FOR, and money that arrives
   before either would make the page look like an invoice rather than a delivery board. */
.ui .pu-supplier-money {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-3);
  margin-inline-start: auto;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

/* Why a sum is not shown, in words rather than in a title= — a tooltip does not survive
   being printed, and these pages get printed. */
.ui .money-note {
  font-size: var(--t-xs);
  font-weight: var(--w-medium);
}

.ui .pu-todo-price {
  font-size: var(--t-xs);
}

/* „Grąžinti į krepšelį" for a supplier nothing was sent to. Its own row above the plan,
   wrapping on a narrow screen so the reassurance beside it never squeezes the button. */
/* The basket's own age, above the re-check report. Quiet by construction: it is a fact,
   not a warning, and a warning on every basket would be ignored by the time one mattered. */
.ui .cart-freshness {
  margin: 0;
  font-size: var(--t-sm);
}

.ui .pu-retry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
}

.ui .pu-retry > span {
  font-size: var(--t-sm);
}

.ui .pu-plan {
  display: grid;
  gap: var(--s-3);
}

.ui .pu-plan-label {
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
}

.ui .pu-plan-items {
  display: grid;
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ui .pu-plan-items > li {
  display: flex;
  align-items: center;
  gap: var(--s-5);
}

.ui .pu-plan-qty {
  margin-left: auto;
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* "užsakyta 1" beside "2 vnt": the plan and the orders, per line. Warning-toned because it
   is a difference worth reading, not error-toned because ordering fewer than planned is
   usually the right call when the supplier has fewer. */
.ui .pu-plan-diff {
  margin-left: var(--s-2);
  padding: 1px var(--s-2);
  border-radius: var(--r-xs);
  color: var(--warning);
  background: color-mix(in srgb, var(--warning) 12%, transparent);
  font-weight: var(--w-medium);
}

.ui .pu-plan-diff--none {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.ui .pu-plan-items > li.is-unordered .prod-code {
  text-decoration: line-through;
}

/* The codes named inside an actionable block. */
.ui .pu-todo-list {
  display: grid;
  gap: var(--s-1);
  margin: var(--s-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--t-sm);
}

.ui .pu-todo-list > li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
}

.ui .pu-orders {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Wraps. Five items — number, state, counts, date, chevron — do not fit a 320px phone in
   one line, and measured at 320 the row ran 64px past the screen. */
.ui .pu-order {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-5);
  padding: var(--s-4) var(--s-5);
  color: inherit;
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  text-decoration: none;
  transition:
    border-color var(--dur-1) var(--ease-out),
    background var(--dur-1) var(--ease-out);
}

.ui .pu-order:hover {
  background: var(--surface);
  border-color: var(--border-strong);
}

.ui .pu-order-ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--t-md);
  font-weight: var(--w-semi);
}

.ui .pu-order-meta {
  color: var(--text-secondary);
  font-size: var(--t-sm);
}

.ui .pu-order-date {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-left: auto;
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ui .pu-order-date > svg {
  width: 12px;
  height: 12px;
  color: var(--text-tertiary);
}

.ui .pu-order-date--overdue {
  color: var(--danger);
}

.ui .pu-order-date--overdue > svg {
  color: var(--danger);
}

.ui .pu-order-go,
.ui .pu-order-chev {
  display: inline-flex;
  color: var(--text-tertiary);
}

.ui .pu-order-go svg,
.ui .pu-order-chev svg {
  width: 14px;
  height: 14px;
}

.ui .pu-order-date + .pu-order-go,
.ui .pu-order-date + .pu-order-chev {
  margin-left: 0;
}

/* Exactly ONE element in the trailing group pushes: whichever of money / date / chevron
   comes first. Two auto margins split the free space between them, which left the row's
   figures floating in the middle and the chevron adrift after them. */
.ui .pu-order:not(:has(.pu-order-money)):not(:has(.pu-order-date)) .pu-order-go,
.ui .pu-order:not(:has(.pu-order-money)):not(:has(.pu-order-date)) .pu-order-chev {
  margin-left: auto;
}

.ui .pu-order:has(.pu-order-money) .pu-order-date {
  margin-left: 0;
}

.ui .pu-register {
  justify-self: start;
}

/* ---------------------------------------------------------------------------
 * AN ORDER THAT OPENS IN PLACE
 *
 * The row was a link to the order's own page. Reading a purchase therefore meant leaving
 * it five times, and nothing on the page it came back to carried a part or a price. Now
 * the row is a <summary> and the order's own card unfolds beneath it.
 *
 * <details>, not a scripted panel — the same choice as every menu here. It survives an
 * htmx swap of the article around it, it works with no JavaScript at all, and its open
 * state is the browser's rather than something we have to keep in step with the server.
 * ------------------------------------------------------------------------ */

/* Kill the default disclosure triangle in both engines: the chevron on the right is the
   affordance, and two markers on one row read as two different controls. */
.ui .pu-order-open > summary {
  list-style: none;
  cursor: pointer;
}

.ui .pu-order-open > summary::-webkit-details-marker {
  display: none;
}

.ui .pu-order-open[open] > summary {
  background: var(--surface);
  border-color: var(--border-strong);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

.ui .pu-order-open[open] .pu-order-chev {
  transform: rotate(180deg);
}

.ui .pu-order-chev {
  transition: transform var(--dur-1) var(--ease-out);
}

/* This supplier order's own money, before the date. Tabular so a column of orders lines
   its decimal points up down the card. */
.ui .pu-order-money {
  margin-left: auto;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  font-variant-numeric: tabular-nums;
}

.ui .pu-order-money + .pu-order-date {
  margin-left: 0;
}

/* The opened card sits in a bare well under the summary — no border of its own, because
   the summary above it and the supplier card around it are already two edges, and a third
   would take the width the part numbers need. One hairline joins the two. */
.ui .pu-order-body,
.ui .pu-order-open[open] > .od {
  padding: var(--s-6) var(--s-5) var(--s-5);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-top: 0;
  border-end-start-radius: var(--r-md);
  border-end-end-radius: var(--r-md);
}

.ui .pu-order-skeleton {
  display: grid;
  gap: var(--s-3);
}

/* Every order already open: the whole purchase as one document. */
.ui .pu-expand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--text-secondary);
  font-size: var(--t-sm);
  font-weight: var(--w-medium);
  text-decoration: none;
}

.ui .pu-expand:hover {
  color: var(--text);
  text-decoration: underline;
}

.ui .pu-expand svg {
  width: 13px;
  height: 13px;
}

/* ---------------------------------------------------------------------------
 * AN ORDER CARD, EMBEDDED (.od--flat)
 *
 * The same fragment as the order's own page, minus its surfaces. Its summary is not a
 * card and its table is not a panel (both switched off in the template), so what is left
 * here is the spacing: a standalone card gets page-level breathing room, and the same
 * block inside a supplier card inside a page would be that room three times over.
 * ------------------------------------------------------------------------ */

.ui .od--flat {
  gap: var(--s-5);
}

/* ONE band above the table, not three.
 *
 * Standing on its own page the card is a heading, then a row of numbers, then the table,
 * and each deserves its own line. Embedded, the heading is gone (the summary row above
 * says all of it) and what is left is a single fact or two, a date and one button — which
 * still occupied three stacked rows of mostly empty space, roughly 110px per opened order.
 * So the header and the strip become one flex line: facts on the left, date and the
 * frequent action on the right. */
.ui .od--flat .od-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4) var(--s-6);
  padding: 0;
}

.ui .od--flat .od-head {
  order: 2;
  gap: var(--s-4);
  margin-inline-start: auto;
}

.ui .od--flat .od-strip {
  order: 1;
  flex: 1 1 auto;
  padding: 0;
  border: 0;
}

/* Its facts and its date belong to the two ends of that same line now, so the strip must
   not also space them apart as if it owned the full width. */
.ui .od--flat .od-strip .facts {
  gap: var(--s-6);
}

/* THE TABLE IS A SLAB, not a white rectangle laid on a rounded card.
 *
 * On its own page the table sits inside `.panel`, and the panel is what supplies the
 * border and the radius that its header's opaque background gets clipped by. Embedded
 * there is deliberately no panel — a third nested edge would cost the part-number column
 * its width — so nothing clipped anything: the white block began at the header row and
 * ended four square corners later, on a card whose every other corner is round.
 *
 * The fix is the radius, not another border. Corner cells round themselves, the table
 * paints the middle, and the header keeps a heavier bottom rule so it still reads as a
 * header now that it has an edge of its own to be part of. */
.ui .od--flat .od-lines table {
  background: var(--surface);
  border-radius: var(--r-md);
  /* AND THE SAME CORNER UNDER A POINTER. Its last row rounds itself to --r-md at rest (the
     four corner-cell rules below), while the hover/focus family hands the last row
     `--row-r-last`, which is --r-sm everywhere it is not overridden — so the corner shrank
     from 8px to 6px the moment somebody moved a mouse over it, and grew back when they
     moved away. One corner, one radius, in every state. */
  --row-r-last: var(--r-md);
}

.ui .od--flat .od-lines thead th {
  background: var(--surface);
  border-bottom-color: var(--border-strong);
}

.ui .od--flat .od-lines thead th:first-child {
  border-start-start-radius: var(--r-md);
}

.ui .od--flat .od-lines thead th:last-child {
  border-start-end-radius: var(--r-md);
}

.ui .od--flat .od-lines tbody tr:last-child td:first-child {
  border-end-start-radius: var(--r-md);
}

.ui .od--flat .od-lines tbody tr:last-child td:last-child {
  border-end-end-radius: var(--r-md);
}

/* The way back to everything the embedded card does not draw: verifying against the
   supplier, the note, the document, deleting the order. */
.ui .od-open {
  color: var(--text-tertiary);
}

/* Never lose a row in silence, at a tenth of the furniture the drawer costs. */
.ui .od-removed-note {
  font-size: var(--t-sm);
}

.ui .od-purchase-num {
  margin-left: var(--s-2);
  opacity: 0.8;
  font-variant-numeric: tabular-nums;
}

/* ===========================================================================
 * REGISTRATION RESULT (.reg) — and what a failure looks like.
 *
 * A failed read is an error state with actions, not a sentence with a colour. The three
 * things worth trying are always the same three (again / re-read the list / go and look),
 * so they are always all there, and the technical reason sits under the headline where it
 * informs the choice instead of being the message.
 * =========================================================================== */

.ui .reg {
  display: grid;
  gap: var(--s-5);
  padding: var(--s-5) var(--s-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
}

.ui .reg--failed {
  background: color-mix(in srgb, var(--danger) 6%, transparent);
  border-color: color-mix(in srgb, var(--danger) 28%, transparent);
}

.ui .reg-head {
  display: flex;
  align-items: flex-start;
  gap: var(--s-4);
  font-size: var(--t-base);
}

.ui .reg-head > svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  margin-top: 1px;
  color: var(--success);
}

.ui .reg--failed .reg-head > svg {
  color: var(--danger);
}

.ui .reg-head > div {
  display: grid;
  gap: var(--s-2);
  min-width: 0;
}

.ui .reg-reason {
  color: var(--text-secondary);
  font-size: var(--t-md);
}

.ui .reg-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

/* ===========================================================================
 * RESPONSIVE — one app from 320px to a wide desktop.
 *
 * The overhaul was built and reviewed at desktop width, and at phone width it was
 * broken in a way that source code does not show. MEASURED, in the running app:
 *
 *   - EVERY page scrolled sideways on a phone. Not the tables — the whole document.
 *     `.app-shell` is a grid, and a grid's single implicit column is sized `auto`,
 *     whose MINIMUM is the min-content of the widest child. One 444px-wide child (the
 *     order tabs) therefore stretched the column to 444px inside a 370px page, and
 *     every sibling — including the sticky header — stretched with it. The header was
 *     what stuck out, so the header looked like the bug; it was a symptom.
 *     /cart at 390px overflowed by 205px, /orders/history by 64px, an order by 109px.
 *   - At 1024px (an iPad landscape, and any half-screen desktop window) every page
 *     overflowed by 27px: the header's text labels come back above 900px, and they
 *     need ~1180px. So the one breakpoint that was tuned was tuned to the wrong number.
 *   - The results/cart/order-line/supplier tables have 5-8 columns. Below ~700px there
 *     is no honest way to keep them as columns, and shrinking them silently dropped the
 *     product NAME from an order line, leaving a bare part number.
 *   - The sticky column headers were pinned at a hardcoded `top: 78px` — the DESKTOP
 *     header height. On a phone the header is taller, so the column names sat behind it.
 *   - Every control was sized for a mouse: 26px steppers, a 16px checkbox, a 26px-tall
 *     tab. On a touch screen these are below every platform's minimum target.
 *
 * BREAKPOINTS. Four, named, used by everything, so no component invents a fifth:
 *   1180px  header goes icon-only (measured: labels need this much)
 *    900px  tablet — side-by-side layouts become stacks
 *    640px  phone  — tables stop being tables
 *    420px  small phone — trim the last of the chrome
 * Plus `(hover: none)`, which is not a width: it asks whether a finger is doing the
 * pointing, and it is the only correct question to ask before sizing a target.
 * =========================================================================== */

/* ---------------------------------------------------------------------------
 * 1. NO CHILD MAY WIDEN THE PAGE.
 *
 * Every one of these is a single-column stack. Declaring the column as
 * `minmax(0, 1fr)` instead of leaving it `auto` caps it at the container's width,
 * so a wide child overflows ITSELF — where it can be given a scroller or a phone
 * layout — instead of pushing the whole document sideways.
 * --------------------------------------------------------------------------- */

.ui .app-shell,
.ui .olist,
.ui .ogroup,
.ui .orows,
.ui .od,
.ui .od-head-main,
.ui .cart-page,
.ui .cart2,
.ui .cart2-group,
.ui .head-titles,
.ui .orow-who,
.ui .orow-names {
  grid-template-columns: minmax(0, 1fr);
}

/* The same guarantee for the flex chain the header sits in. */
.ui .page,
.ui .cart-page-content {
  min-width: 0;
}

/* A grid gap applies to an empty child too. The cart page keeps an empty div as the flash
   target, so every render spent a gap on nothing — 16px of dead space above the first
   supplier, at every width. */
.ui .cart-page > div:empty,
.ui .olist > div:empty {
  display: none;
}

/* A grid item stretches to its column, so a badge inside one of those single-column
   stacks was drawn the full width of the column — the test-mode tag came out 344px wide
   on a phone and the width of the supplier column on a desktop. A label that wide reads
   as a banner. Pills shrink to their text wherever they sit in a stack. */
.ui .orow-who > .tag,
.ui .orow-who > .status,
.ui .orow-who > .count,
.ui .od-head-main > .tag,
.ui .head-titles > .tag {
  justify-self: start;
}

/* ---------------------------------------------------------------------------
 * 2. THE TAB / SEGMENT STRIPS SCROLL THEMSELVES.
 *
 * Four tabs and four grouping options do not fit a phone, and neither may be
 * abbreviated: "Reikia priimti" and "Šiandien ir vėluoja" are different questions.
 * So the strip scrolls horizontally, inside itself, with the scrollbar hidden and
 * snap points on the items — the page never moves.
 * --------------------------------------------------------------------------- */

@media (max-width: 900px) {
  .ui .tabs,
  .ui .seg {
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    -ms-overflow-style: none;
  }

  .ui .tabs::-webkit-scrollbar,
  .ui .seg::-webkit-scrollbar {
    display: none;
  }

  .ui .tab,
  .ui .seg > * {
    scroll-snap-align: start;
    flex: 0 0 auto;
  }

  /* A scroller cannot be `inline-flex` and still be capped by its parent's width. */
  .ui .seg {
    display: flex;
    width: max-content;
  }
}

/* ---------------------------------------------------------------------------
 * 4. TOUCH SIZING.
 *
 * `(hover: none)` is the honest test: a 26px stepper is right for a mouse and
 * wrong for a thumb. The scale is redefined once, so every button, input, tab and
 * pill built on it grows together and rows still line up.
 * --------------------------------------------------------------------------- */

@media (hover: none) {
  :root {
    --h-sm: 36px;
    --h-md: 40px;
    --h-lg: 44px;
  }

  /* An icon-only trigger has no label to widen it, so the token alone leaves a 32px
     square — measured on the order page: every `⋯` and every small icon button. These
     are the controls the receiving workflow taps, so they get a finger. */
  .ui .btn--icon,
  .ui .menu > summary,
  .ui .icon-button {
    min-width: 40px;
    min-height: 40px;
  }

  /* The pre-design-system `.button` (style.css) is still used by page-level links such as
     "← Visi užsakymai"; a bare one has no height token to follow. (`.head-action` does —
     it reads --h-md and grows with it — so this floor only matters for the bare ones.) */
  .ui .button {
    min-height: 40px;
  }

  /* The checkbox is the target the receiving workflow taps most. */
  .ui .check {
    width: 40px;
    height: 40px;
  }

  .ui .check span {
    width: 20px;
    height: 20px;
  }

  /* A row menu that only appears on hover can never be opened by a finger. These are
     already unconditionally visible (the reveal rules are inside `hover: hover`), but
     say it here too so a future `.on-hover` cannot regress it. */
  .ui .on-hover {
    opacity: 1;
  }
}

/* ---------------------------------------------------------------------------
 * 5. THE HEADER.
 *
 * Two rows on a phone — the icon strip, then the search bar at full width — because
 * a part number is typed far more often than the nav is used, and a truncated search
 * field is the one thing that makes this app unusable on a phone.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .app-header {
    padding: 6px 8px;
    row-gap: 0;
  }

  .ui .header-actions {
    gap: var(--s-1);
    margin-left: auto;
  }

  .ui .app-header .header-nav-link,
  .ui .app-header .cart-trigger,
  .ui .app-header .icon-button,
  .ui .app-header .logout-button {
    width: 40px;
    min-width: 40px;
    height: 40px;
    padding: 0;
  }

  .ui .header-search {
    margin-top: var(--s-2);
  }

  .ui .header-search .search-input-wrap {
    min-height: 40px;
  }

  .ui .header-search .search-input-wrap input {
    height: 38px;
    font-size: 15px; /* 16px-ish: below 16px iOS zooms the page on focus. */
  }

  /* The submit becomes its arrow. The label is redundant beside a magnifier and an
     arrow, and it was costing the input 60px of the 340 it had. */
  .ui .header-search .search-submit {
    width: 40px;
    min-width: 40px;
    min-height: 40px;
    padding: 0;
    justify-content: center;
  }

  .ui .header-search-label {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
 * 5b. POPOVERS BECOME SHEETS ON A PHONE.
 *
 * A `.menu-panel` is anchored to the RIGHT edge of its trigger, which is correct on a
 * desktop, where a `⋯` sits at the end of a row. On a phone the action rows wrap, so the
 * same trigger can end up on the left of a new line — and the panel then opens leftwards,
 * off the screen. Measured on /cart at 360px: a panel at left −158px, i.e. entirely
 * invisible with no way to reach it.
 *
 * No amount of static CSS can know which side a trigger will land on, so on a phone the
 * panel stops being anchored to the trigger at all and becomes a sheet at the bottom of
 * the viewport: always fully on screen, always within thumb reach, whichever control
 * opened it. `<details>` still drives it, so this needs no JavaScript and cannot get out
 * of step with the markup.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .menu > .menu-panel,
  .ui .menu > .menu-panel--left {
    position: fixed;
    top: auto;
    right: var(--s-4);
    bottom: var(--s-4);
    left: var(--s-4);
    z-index: 60;
    width: auto;
    min-width: 0;
    max-height: min(72vh, 560px);
    padding: var(--s-3) var(--s-3) max(var(--s-3), env(safe-area-inset-bottom));
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
    animation: ui-sheet var(--dur-3) var(--ease-out);
  }

  /* Sheet items are tapped, so they get a finger-sized row. */
  .ui .menu-item {
    min-height: 44px;
    font-size: var(--t-base);
  }

  .ui .menu-item > svg {
    width: 16px;
    height: 16px;
  }
}

@keyframes ui-sheet {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

@media (max-width: 420px) {
  /* Seven icon buttons plus the brand stop fitting somewhere under 320px. Wrapping is the
     honest degradation: the nav takes a second line instead of leaving the page scrollable
     sideways. (There is no `min-width` floor on <html> any more — see style.css.) */
  .ui .header-actions {
    flex-wrap: wrap;
    justify-content: flex-end;
  }

  .ui .app-header .header-nav-link,
  .ui .app-header .cart-trigger,
  .ui .app-header .icon-button,
  .ui .app-header .logout-button {
    width: 36px;
    min-width: 36px;
  }
}

/* ---------------------------------------------------------------------------
 * 6. PAGE HEADS AND ACTION ROWS STACK.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .head,
  .ui .pu-head {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-5);
    margin-bottom: var(--s-6);
  }

  /* Left as a row, the purchase's actions sat beside a heading that then wrapped to three
     lines around them — measured at 390px. */
  .ui .pu-head-actions {
    flex-wrap: wrap;
  }

  .ui .pu-title {
    flex-wrap: wrap;
    font-size: var(--t-lg);
  }

  /* One line per fact, not a paragraph broken across four columns. */
  .ui .pu-date {
    flex-wrap: wrap;
  }

  .ui .pu-date small {
    flex: 1 1 100%;
  }

  .ui .head-actions {
    flex-wrap: wrap;
  }

  .ui .olist-bar {
    align-items: stretch;
  }

  /* Four numbers in a line become two by two, so they stay readable instead of
     collapsing into a single squeezed column. */
  /* auto-fit, not a fixed two columns: "TIKIMĖS 3 vnt" needs about 84px, so four fit
     across a 390px phone and two across a 320px one, without a second breakpoint. The
     max has to be `1fr`, not `auto` — with `auto` the track is indefinite, auto-fit can
     compute no repetition count, and the four figures come out one per line.

     The grid goes on `.facts` alone. Putting it on `.od-strip` too made the strip a
     three-track grid whose FIRST track held the whole `.facts` list at 110px wide, which
     stacked the four figures vertically — a 298px-tall strip. `.od-strip` stays the flex
     row it already is; it only stops trying to sit on one line. */
  .ui .facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
    gap: var(--s-5) var(--s-7);
    width: 100%;
  }

  /* The date is a control, not a fifth number: its own line under the figures. */
  .ui .od-strip {
    align-items: stretch;
    justify-content: flex-start;
  }

  .ui .od-strip > .od-strip-date {
    width: 100%;
  }

  /* The date state and the one-press "accept this date" sit side by side, and together
     they are wider than a phone (measured: 64px over at 320px). They wrap instead. */
  .ui .od-strip-date,
  .ui .od-date {
    flex-wrap: wrap;
    min-width: 0;
  }
}

/* ---------------------------------------------------------------------------
 * 7. TABLES STOP BEING TABLES (≤640px).
 *
 * `.tbl--stack` turns one row into a small two-line block: what the part IS on the
 * first line, the numbers and the action on the second. The column headers are
 * removed from the flow but stay in the accessibility tree via each cell's own
 * label, so nothing that was announced stops being announced.
 *
 * Rows keep their grid areas assigned explicitly per table rather than by nth-child:
 * the cart's price column is conditional (the popover omits it), so counting
 * children would silently mis-place cells in one of the two renderings.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .tbl--stack thead {
    /* Not display:none — that removes the header row from the a11y tree too. */
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .ui .tbl--stack,
  .ui .tbl--stack tbody,
  .ui .tbl--stack tr,
  .ui .tbl--stack td {
    display: block;
  }

  .ui .tbl--stack tr {
    display: grid;
    align-items: center;
    gap: var(--s-4) var(--s-5);
    padding: var(--s-5);
  }

  .ui .tbl--stack tr + tr {
    border-top: 1px solid var(--border);
  }

  /* `tbody` in the base rule (`.ui .tbl tbody td`) counts as a type selector, so a
     two-class selector loses to it. Matching the same shape and coming later is what
     actually wins — the first attempt at this rule was silently ignored, leaving 12px
     of padding and a border under every stacked cell. */
  .ui .tbl--stack tbody td {
    padding: 0;
    border: 0;
    white-space: normal;
  }

  /* `.shrink` means "this column takes only what it needs" and does it with `width: 1%`,
     which is three classes deep and therefore beats the reset above. Once the row is a
     grid, 1% resolves against the grid TRACK — the cells came out 0.48px wide and the
     remove button sat on top of the line total. Undone explicitly, at equal depth. */
  .ui .tbl--stack tbody td.shrink {
    width: auto;
    white-space: normal;
  }

  .ui .tbl--stack .num {
    text-align: left;
  }

  /* A stacked cell states what it is, because two money values one under the other
     with no labels is a guess. Only where the value is not self-evident: a stepper
     is obviously a quantity, a checkbox is obviously a selection. */
  .ui .tbl--stack td[data-label]::before {
    display: block;
    margin-bottom: var(--s-1);
    color: var(--text-tertiary);
    font-size: var(--t-xs);
    font-weight: var(--w-semi);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    content: attr(data-label);
  }

  /* --- Order lines. Three lines: the part, its numbers, its state and action. The
     checkbox keeps a gutter of its own down the left, and EVERYTHING else shares one
     left edge in column 2 — when the state cell started in column 1 instead, the row
     read as three unrelated fragments at three different indents. --- */
  .ui .od-lines tr {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .ui .od-lines .od-c-pick {
    grid-area: 1 / 1 / 4 / 2;
    align-self: start;
  }

  .ui .od-lines .od-c-prod {
    grid-area: 1 / 2 / 2 / 4;
  }

  .ui .od-lines .od-c-qty {
    grid-area: 2 / 2 / 3 / 3;
  }

  .ui .od-lines .od-c-price {
    grid-area: 2 / 3 / 3 / 4;
    text-align: right;
  }

  .ui .od-lines .od-c-state {
    grid-area: 3 / 2 / 4 / 3;
  }

  .ui .od-lines .od-c-act {
    grid-area: 3 / 3 / 4 / 4;
  }

  .ui .od-lines .row-actions {
    justify-content: flex-end;
  }

  .ui .od-lines .od-line-empty {
    grid-column: 1 / -1;
  }

  /* --- Cart lines: the part, then price · quantity · sum · remove. --- */
  .ui .cart2-lines tr {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
  }

  .ui .cart2-lines .cart-c-prod {
    grid-column: 1 / -1;
  }

  .ui .cart2-lines .cart-c-price {
    grid-area: 2 / 1 / 3 / 2;
  }

  .ui .cart2-lines .cart-c-qty {
    grid-area: 2 / 2 / 3 / 3;
  }

  .ui .cart2-lines .cart-c-sum {
    grid-area: 2 / 3 / 3 / 4;
    text-align: right;
  }

  .ui .cart2-lines .cart-c-del {
    grid-area: 2 / 4 / 3 / 5;
  }

  /* Stock and arrival go UNDER the money, as a third line: the popover omits both cells,
     and a row the popover leaves empty must not be one the numbers depend on. */
  .ui .cart2-lines .cart-c-stock {
    grid-area: 3 / 1 / 4 / 3;
  }

  .ui .cart2-lines .cart-c-eta {
    grid-area: 3 / 3 / 4 / 5;
  }

  /* Side by side, so their labels share a line even when one value wraps and the other
     does not; the row's own `align-items: center` would drop the shorter one. */
  .ui .cart2-lines .cart-c-stock,
  .ui .cart2-lines .cart-c-eta {
    width: auto;
    align-self: start;
  }

  .ui .cart2-lines .stepper {
    justify-content: center;
  }
}

/* ---------------------------------------------------------------------------
 * 8. THE ORDER ROW (the working list) — two lines on a phone.
 *
 * The desktop row is supplier | parts | state+date in three columns. Below 900px the
 * parts drop to their own line; below 640px the state and date follow, and the
 * "Viską gavau" action becomes full-width — it is the reason the screen exists.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .orow {
    flex-wrap: wrap;
    padding-right: 0;
  }

  .ui .orow-main {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-4);
    padding: var(--s-5);
    width: 100%;
  }

  .ui .orow-state,
  .ui .orow-date {
    justify-self: start;
  }

  .ui .orow-actions {
    width: 100%;
    padding: 0 var(--s-5) var(--s-5);
  }

  .ui .orow-actions .btn {
    flex: 1 1 auto;
    /* Full width and thumb-height: this is the action the whole screen exists for. */
    height: var(--h-md);
  }

  /* The chevron said "this row is a link" to a mouse. A finger taps the row. */
  .ui .orow-go {
    display: none;
  }

  .ui .ogroup-head {
    flex-wrap: wrap;
    gap: var(--s-3);
  }
}

/* ---------------------------------------------------------------------------
 * 9. THE RESULTS TABLE — the one screen that is genuinely eight columns.
 *
 * A phone gets one block per offer: the part, its price, who has it, and the
 * stock/delivery facts on their own line with labels. The same information; the only
 * thing lost is the column alignment, which a 360px screen cannot have anyway.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .results-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .ui .results-table,
  .ui .results-table tbody,
  .ui .results-table tr,
  .ui .results-table td {
    display: block;
  }

  /* A flex row, not a grid: the four supplier-stated facts have to share ONE wrapped
     line. In a grid each cell owns a track, and the first attempt put every fact on its
     own line — an offer became 500px tall, on the one screen in this app whose whole
     job is comparing eleven of them at a glance. Order is set explicitly because the
     visual order (part, price, supplier + facts, add) is not the column order. */
  .ui .results-table tbody tr.result-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3) var(--s-5);
    padding: var(--s-5);
    border-bottom: 1px solid var(--border);
  }

  .ui .results-table td {
    padding: 0;
    border: 0;
    width: auto;
  }

  .ui .results-table td.code {
    order: 1;
    flex: 1 1 180px;
    min-width: 0;
  }

  .ui .results-table td.price {
    order: 2;
    flex: 0 0 auto;
    margin-left: auto;
    /* Never flush against the part's name when the two land on one line. */
    padding-left: var(--s-4);
    text-align: right;
  }

  .ui .results-table td.provider {
    order: 3;
    flex: 0 0 auto;
    color: var(--text-secondary);
    font-size: var(--t-sm);
    font-weight: var(--w-semi);
  }

  /* The four facts, TWO TO A LINE. They used to flow as one wrapping line, which on a
     390px card read as a run-on sentence — "VIETOJE 2 KIEKIS Sandėliuose: 4 vnt.
     PRISTATYMAS 1-2 d.d. GAMINTOJAS BOSCH" — with nothing separating a label from its
     value or one fact from the next. A pair per line keeps the card as short as it was
     and makes it scannable. */
  .ui .results-table td.local-stock,
  .ui .results-table td.amount,
  .ui .results-table td.delivery,
  .ui .results-table td.manufacturer {
    order: 4;
    flex: 1 1 40%;
    min-width: 0;
    color: var(--text-secondary);
    font-size: var(--t-sm);
  }

  .ui .results-table td.add-to-cart {
    order: 5;
    flex: 1 1 100%;
  }

  /* The label sits ABOVE its value rather than running into it: "VIETOJE 2" was one token
     to the eye, and the row's four facts had no boundary between them. */
  .ui .results-table td[data-label]::before {
    display: block;
    color: var(--text-tertiary);
    font-size: var(--t-xs);
    font-weight: var(--w-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    content: attr(data-label);
  }

  /* A row that failed carries one message across the whole block. */
  .ui .results-table td.status-cell {
    grid-column: 1 / -1;
  }

  /* The supplier divider does not stick on a phone: it would spend the scroll pinned
     under a header whose height changes with wrapping, and each offer already names its
     supplier on its own line. */
  .ui .results-table tbody tr.provider-group-row,
  .ui .results-table tbody tr.result-group-row {
    display: block;
    position: static;
  }

  /* `min-width: 1120px` is what keeps eight columns from crushing each other on a
     desktop. Once the rows are blocks there are no columns to protect, and the figure
     becomes a 1120px-wide block inside a 370px page — measured: the rows ran 550px past
     the panel that was supposed to contain them. */
  .ui .results-table {
    min-width: 0;
  }

  /* No sideways scroll to manage once the rows are blocks. */
  .ui .results-table-wrapper {
    overflow-x: visible;
  }
}

/* ---------------------------------------------------------------------------
 * 9b. THE LEGACY `.cart-table` (saved copies, and a supplier's own order list).
 *
 * Five and seven columns respectively, inside a wrapper with `overflow-x: auto`. So they
 * never broke the page — they just required dragging a table sideways on a phone to read
 * a price, which is not the same thing as working. Each row becomes a labelled block.
 * --------------------------------------------------------------------------- */

@media (max-width: 640px) {
  .ui .cart-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .ui .cart-table,
  .ui .cart-table tbody,
  .ui .cart-table tr,
  .ui .cart-table td {
    display: block;
    width: auto;
  }

  /* Same trap as the results table: `min-width: 760px` protects seven columns from
     crushing each other, and once the rows are blocks it is just a 760px-wide box inside
     a 370px page. */
  .ui .cart-table {
    min-width: 0;
  }

  .ui .cart-table tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-2);
    padding: var(--s-5);
    border-bottom: 1px solid var(--border);
  }

  .ui .cart-table tbody td {
    padding: 0;
    border: 0;
    white-space: normal;
    text-align: left;
  }

  .ui .cart-table tbody td[data-label]::before {
    display: inline;
    margin-right: var(--s-3);
    color: var(--text-tertiary);
    font-size: var(--t-xs);
    font-weight: var(--w-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    content: attr(data-label);
  }

  /* Nothing to drag once the rows are blocks. */
  .ui .cart-table-wrapper {
    overflow-x: visible;
  }
}

/* ---------------------------------------------------------------------------
 * 10. /suppliers — an eleven-row diagnosis table, 1100px wide at rest.
 *
 * On a phone each supplier becomes a block: name, then its facts, then the
 * diagnosis. The developer-facing module path is the one column that is dropped —
 * it is a code pointer, and it is in the page's own markup for anyone who needs it.
 * --------------------------------------------------------------------------- */

@media (max-width: 900px) {
  .ui .suppliers-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .ui .suppliers-table,
  .ui .suppliers-table tbody,
  .ui .suppliers-table tr,
  .ui .suppliers-table td {
    display: block;
    width: auto;
  }

  .ui .suppliers-table tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-3);
    padding: var(--s-5);
    border-bottom: 1px solid var(--border);
  }

  .ui .suppliers-table td {
    padding: 0;
    border: 0;
    white-space: normal;
  }

  .ui .suppliers-table td[data-label]::before {
    color: var(--text-tertiary);
    font-size: var(--t-xs);
    font-weight: var(--w-semi);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    content: attr(data-label) " ";
  }

  .ui .suppliers-table .module-column {
    display: none;
  }

  /* `min-width: 20rem` reserves room for six verdict badges plus a button while the
     diagnosis column is a COLUMN. Stacked, it is a 320px floor inside a 300px page —
     measured: 23px of page overflow at a 320px viewport. */
  .ui .suppliers-table .diagnosis-column {
    min-width: 0;
  }

  .ui .diagnosis-cell {
    flex-wrap: wrap;
  }
}

/* A per-branch figure the row SAID it had and did not return. Deliberately not styled as
   an error: it is an unknown, and the operator's action is to look again rather than to
   treat the part as unavailable. Distinguishable from a plain "—" at a glance, which is
   the entire point — the two used to render identically. */
.ui .local-stock--lost {
  color: var(--text-secondary);
  font-weight: var(--w-semi);
  cursor: help;
  border-bottom: 1px dotted currentColor;
}

/* ===========================================================================
 * CARD — one surface, used where a block of the page is a THING.
 *
 * The order screens were built out of bare bands separated by horizontal rules,
 * while the search, history and supplier screens were built out of surfaces. Two
 * visual languages, one app, and the receiving screens were in the one that reads
 * as a document rather than as a tool.
 *
 * This is the history card's own recipe (surface + 1px border + large radius +
 * the smallest shadow), stated once in the design-system layer so the two halves
 * of the app agree without either importing the other's classes.
 *
 * NO CARD MAY CONTAIN A CARD. That is the same rule `.panel` states above, and it
 * is the reason "put everything on cards" is not what happened here: a lines table
 * inside a supplier card inside the page is three nested surfaces, and each one
 * takes horizontal space away from the part numbers that are the actual content.
 * A card wraps a section. Inside it, spacing does the work.
 * =========================================================================== */

.ui .card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}

/* The summary card: who/what this is, and where it stands. One thought, so one
   surface — these were two separate bands, and the facts strip was rendered as a
   pair of rules, which made the most important numbers on the screen look like a
   divider between other things. */
.ui .od-summary,
.ui .pu-summary {
  display: grid;
  gap: var(--s-6);
  padding: var(--s-7) var(--s-8);
}

/* Inside the summary card the strip is just a row of numbers: the card's own edge
   already separates it from the page, so it drops the rules and the padding that
   existed to hold them apart. */
.ui .od-strip--bare {
  padding: 0;
  border-top: 0;
  border-bottom: 0;
}

/* The repeating cards get the same shadow as the history list, which is what makes a
   stack of them read as a stack rather than as a ruled table. */
.ui .orow,
.ui .pu-supplier {
  box-shadow: var(--shadow-xs);
}

/* Mobile: the padding scale steps down one notch rather than being invented again.
   24 -> 16 horizontal, 20 -> 16 vertical, and the inner gap follows. */
@media (max-width: 640px) {
  .ui .od-summary,
  .ui .pu-summary {
    gap: var(--s-5);
    padding: var(--s-6);
  }
}

/* The one remaining "something is missing" block on a purchase: basket codes that no
   order of ours holds. A card like everything else on the page — no warning wash, no
   coloured border. It reports a fact with a consequence, not a fault. */
.ui .pu-todo {
  display: grid;
  gap: var(--s-3);
  padding: var(--s-6) var(--s-7);
  font-size: var(--t-md);
}

.ui .pu-todo-list {
  display: grid;
  gap: var(--s-2);
  margin: var(--s-2) 0 0;
  padding: 0;
  list-style: none;
}

.ui .pu-todo-list > li {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
}

/* An action inside a card grid must not stretch to the card's width. `.pu-send` is the
   press that finishes an unsent supplier, and a full-bleed button read as a section
   header rather than as something to click. */
.ui .pu-send {
  justify-self: start;
}

/* The lines table and the two blocks under it are surfaces like everything else on the
   page — the table already had a border, and the note and the supplier-side verdict sat
   directly on the page background looking like leftovers below it. */
/* The lines table's own surface. Scoped to the order screen rather than given to every
   `.panel`, because a panel elsewhere may sit inside something that already has one. */
.ui .od .panel {
  box-shadow: var(--shadow-xs);
}

@media (max-width: 640px) {
  .ui .head--card {
    padding: var(--s-6);
  }
}


/* --- a highlighted row is rounded, on every table, at both ends -------------------------
 *
 * Reported: "kai kur lentelės ant selecto arba ant hoverio turi vis dar neraundintus
 * kampus, ir jeigu tai yra paskutinis elementas arba pats pirmas, tas roundingas gali
 * nepasirodyti ant hover ar ant focus."
 *
 * Both halves have one cause. A highlight is `tr:hover { background: ... }`, and a <tr>
 * cannot be rounded: under `border-collapse: separate` — which all three tables in this app
 * use — the background belongs to the CELLS, so a radius must go on the row's first and
 * last cell or it does nothing at all. Every hover, every ticked row and the keyboard
 * cursor was therefore a hard-cornered band.
 *
 * At the first and last row that band also painted square corners straight over the panel's
 * rounded ones. That is the same fault the sticky table header had a few rules up, and it
 * gets the same answer for the same reason: `.panel` cannot clip with `overflow: hidden`
 * (a row's action menu opens downward and is taller than its row), so the corner cells
 * round themselves instead. Same shape, no clipping.
 *
 * `--row-r` is the ordinary end radius. `--row-r-last` is what the LAST row uses on its
 * bottom corners, and it only differs where the row's own edge IS the panel's edge — inside
 * `.panel` the table runs to the border, while `.results-panel` keeps 22px of padding below
 * its table, so there the last row is nowhere near the corner and stays at `--row-r`.
 */

.ui .tbl,
.ui .results-table,
.ui .cart-table {
  --row-r: var(--r-sm);
  --row-r-last: var(--r-sm);
}

/* THE RESULTS TABLE IS ITS OWN BOX, SO A ROW INSIDE IT DOES NOT ROUND.
 *
 * Every other table in this app sits in a padded panel: a highlighted row floats clear of
 * the panel's edge, and rounding it is what stops a tinted band having harder corners than
 * anything around it. The results table is the opposite — its own cells carry its side
 * edges, so a row's end IS the table's end, and a 6px curve on the tint pulled it away from
 * a border running dead straight past it. A sliver of panel showed between the two at every
 * row the pointer touched. „ant hover nei vienas produktas negali but rounded, tiesiog
 * proper kvadratinis."
 *
 * The one row where the curve is real is the LAST one, because there the row's bottom edge
 * is the table's bottom edge — and `--row-r-last` is the table's own corner, so the tint
 * ends in exactly the shape the border does. Nothing else in the table curves in any state:
 * hover, cheapest, keyboard focus, all of them read `--row-r`. */
.ui .results-table {
  --row-r: 0;
  --row-r-last: var(--r-md);
}

.ui .panel > .tbl,
.ui .panel > * > .tbl {
  --row-r-last: var(--r-lg);
}

.ui :is(
    .tbl--hover tbody tr:hover,
    .tbl tbody tr.is-picked,
    .tbl tbody tr.is-cursor,
    .results-table tbody tr.result-row:hover,
    .results-table tbody tr.result-row.is-cheapest,
    .cart-table tbody tr:hover
  ) > :is(td, th):first-child {
  border-start-start-radius: var(--row-r);
  border-end-start-radius: var(--row-r);
}

.ui :is(
    .tbl--hover tbody tr:hover,
    .tbl tbody tr.is-picked,
    .tbl tbody tr.is-cursor,
    .results-table tbody tr.result-row:hover,
    .results-table tbody tr.result-row.is-cheapest,
    .cart-table tbody tr:hover
  ) > :is(td, th):last-child {
  border-start-end-radius: var(--row-r);
  border-end-end-radius: var(--row-r);
}

/* THE TABLE'S OWN BOTTOM EDGE. Not a state — this is the shape the table has at rest, the
   pair to the rounded top corners its header has carried all along. Keyed off the class the
   renderer sets rather than `:last-child`, for the reason written at that rule in style.css:
   the last child is a hidden row whenever anything is filtered. */
.ui .results-table tbody tr.is-last-visible > :is(td, th):first-child {
  border-end-start-radius: var(--r-md);
}

.ui .results-table tbody tr.is-last-visible > :is(td, th):last-child {
  border-end-end-radius: var(--r-md);
}

/* The last row's bottom corners follow the container, not the row.
   `:last-child` OR `.is-last-visible`: the first is right for a table whose rows are all
   rendered, the second for one whose rows can be filtered out and left in place. A table
   that has both simply agrees with itself. */
.ui :is(
    .tbl--hover tbody tr:is(:last-child, .is-last-visible):hover,
    .tbl tbody tr:is(:last-child, .is-last-visible).is-picked,
    .tbl tbody tr:is(:last-child, .is-last-visible).is-cursor,
    .results-table tbody tr.result-row.is-last-visible:hover,
    .results-table tbody tr.result-row.is-last-visible.is-cheapest,
    .cart-table tbody tr:is(:last-child, .is-last-visible):hover
  ) > :is(td, th):first-child {
  border-end-start-radius: var(--row-r-last);
}

.ui :is(
    .tbl--hover tbody tr:is(:last-child, .is-last-visible):hover,
    .tbl tbody tr:is(:last-child, .is-last-visible).is-picked,
    .tbl tbody tr:is(:last-child, .is-last-visible).is-cursor,
    .results-table tbody tr.result-row.is-last-visible:hover,
    .results-table tbody tr.result-row.is-last-visible.is-cheapest,
    .cart-table tbody tr:is(:last-child, .is-last-visible):hover
  ) > :is(td, th):last-child {
  border-end-end-radius: var(--row-r-last);
}

/* A row reached by KEYBOARD gets the same shape as one reached by pointer. `:focus-within`
 * had no highlight of its own, so tabbing into a row's controls moved the focus ring but
 * left the row itself unmarked — and where it did tint (`.orow`), the tint was already
 * rounded because that one is an <li> with its own radius, not a table row. */
.ui :is(
    .tbl--hover tbody tr,
    .results-table tbody tr.result-row,
    .cart-table tbody tr
  ):focus-within {
  background: var(--surface-2);
}

.ui :is(
    .tbl--hover tbody tr,
    .results-table tbody tr.result-row,
    .cart-table tbody tr
  ):focus-within > :is(td, th):first-child {
  border-start-start-radius: var(--row-r);
  border-end-start-radius: var(--row-r);
}

.ui :is(
    .tbl--hover tbody tr,
    .results-table tbody tr.result-row,
    .cart-table tbody tr
  ):focus-within > :is(td, th):last-child {
  border-start-end-radius: var(--row-r);
  border-end-end-radius: var(--row-r);
}

.ui :is(
    .tbl--hover tbody tr,
    .results-table tbody tr.result-row,
    .cart-table tbody tr
  ):is(:last-child, .is-last-visible):focus-within > :is(td, th):first-child {
  border-end-start-radius: var(--row-r-last);
}

.ui :is(
    .tbl--hover tbody tr,
    .results-table tbody tr.result-row,
    .cart-table tbody tr
  ):is(:last-child, .is-last-visible):focus-within > :is(td, th):last-child {
  border-end-end-radius: var(--row-r-last);
}

/* WHAT WE CHOSE AT CHECKOUT (_order_detail.html).
 *
 * A record, not a control: nothing here is pressable, so it is drawn quieter than the
 * receiving table it sits under, and it stays collapsed until somebody asks. The one thing
 * that is NOT quiet is the rehearsal warning — a body that was never sent, rendered like one
 * that was, is the single way this block can lie. */
.ui .od-checkout {
  margin-top: var(--s-5);
}

.ui .od-checkout > summary .tag {
  margin-left: var(--s-4);
}

.ui .od-checkout-warn {
  display: flex;
  gap: var(--s-4);
  align-items: flex-start;
  padding: var(--s-5);
  color: var(--text);
  background: color-mix(in srgb, var(--warning) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--warning) 24%, transparent);
  border-radius: var(--r-md);
}

.ui .od-checkout-warn > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--warning);
}

/* The endpoint, on one line, wrapping rather than pushing the page sideways: several of
 * these paths carry a cart id and one of them is a whole query string. */
.ui .od-checkout-endpoint {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  font-size: var(--t-sm);
  overflow-wrap: anywhere;
}

.ui .od-checkout-why {
  font-size: var(--t-sm);
}

.ui .od-checkout-fields {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

.ui .od-checkout-fields th {
  text-align: left;
  padding: var(--s-2) var(--s-4);
  color: var(--text-tertiary);
  font-weight: var(--w-medium);
}

.ui .od-checkout-fields td {
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--border);
  vertical-align: top;
  overflow-wrap: anywhere;
}

.ui .od-checkout-fields code {
  font-size: var(--t-sm);
}

/* The words the supplier's own page printed: the field's NAME in the first column, what its
 * VALUE means in the third. These are the whole point of the block — a stored
 * `shippingMethod=2` tells nobody anything — so both are drawn as text a person reads, and
 * the raw wire name shrinks to evidence under it rather than being replaced. */
.ui .od-checkout-name,
.ui .od-checkout-meaning {
  display: inline-block;
  color: var(--text);
  font-weight: var(--w-medium);
}

/* The wire name, under the Lithuanian one. It is what actually went on the request, so it
 * never disappears — but it stops being the thing the eye lands on. On a field with no label
 * at all it is the only content of the cell, at full size, honestly unexplained. */
.ui .od-checkout-wire {
  display: block;
  color: var(--text-tertiary);
}

.ui .od-checkout-name + .od-checkout-wire {
  font-size: var(--t-xs);
}

/* Where a word came from, when it did not come from the supplier's own page. Small, quiet,
 * and never on the ordinary case — a provenance badge on every row of every card is a
 * footnote farm, and the whole point is that the EXCEPTIONS stand out. */
.ui .od-checkout-src {
  margin-left: 2px;
  font-size: var(--t-xs);
  font-weight: var(--w-normal);
  color: var(--text-tertiary);
  cursor: help;
}

.ui .od-checkout-unknown,
.ui .od-checkout-secret {
  display: inline-block;
  margin-left: var(--s-3);
  font-size: var(--t-sm);
  font-style: italic;
}

/* Wide content scrolls inside itself: a field name like `checkout_form[deliveryAddress]`
 * beside a full branch address is wider than an embedded card, and the page body must never
 * be the thing that scrolls sideways. */
.ui .od-checkout-wrap {
  overflow-x: auto;
}

/* WHAT THE SUPPLIER'S CHECKOUT ASKS FOR — a description, not this order's record.
 *
 * Nested one level inside the block above and drawn quieter than it, because the danger is
 * only ever in one direction: nobody mistakes the record for the description. The inset left
 * border is the visual half of the same sentence its heading says in words — this is a
 * different kind of thing from what sits above it. (The structural half is that its table
 * has no value column at all; see `_order_detail.html`.) */
.ui .od-checkout-asks {
  margin-top: var(--s-5);
  padding-left: var(--s-5);
  border-left: 2px solid var(--border);
}

.ui .od-checkout-asks > summary {
  color: var(--text-secondary);
  font-size: var(--t-sm);
  cursor: pointer;
}

.ui .od-checkout-asks > summary .tag {
  margin-left: var(--s-4);
}

.ui .od-checkout-asks-body {
  padding-top: var(--s-4);
}

.ui .od-checkout-asks-note {
  display: flex;
  gap: var(--s-4);
  align-items: flex-start;
  margin-bottom: var(--s-4);
  font-size: var(--t-sm);
  color: var(--text-secondary);
}

.ui .od-checkout-asks-note > svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  margin-top: 2px;
  color: var(--text-tertiary);
}

/* ===========================================================================
 * THE CONFIRMATION DIALOG — a choice that cannot be taken back, asked in words.
 *
 * Built like `.diagnosis-dialog` (a native <dialog>, a transparent shell so the surface
 * can carry the radius, an opaque backdrop), and deliberately NOT like `confirm()`: the
 * two answers are two submit buttons, each with the sentence that says what it costs
 * under it, so the destructive one is the one you have to read to reach.
 * =========================================================================== */

.ui .confirm-dialog {
  width: min(94vw, 34rem);
  max-width: 94vw;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--r-xl);
}

.ui .confirm-dialog::backdrop {
  background: rgba(2, 6, 23, 0.72);
  backdrop-filter: blur(2px);
}

.ui .confirm-dialog-surface {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
  padding: var(--s-6);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
}

.ui .confirm-dialog-header {
  display: flex;
  align-items: flex-start;
  gap: var(--s-5);
}

/* The same tile as a deferred basket's own mark (`.cart-title-icon`), because it IS that
   mark: this dialog opens from those cards and repeats the thing you pressed. One size, so
   the two cannot drift — it was 38px/19px on both, and both came down together. */
.ui .confirm-dialog-icon {
  display: grid;
  /* A LITERAL, not the token, and the same literal `.cart-title-icon` carries. This is a
     tile, not a target: nobody presses it, so it must NOT grow with `--h-md` on touch —
     written as the token it would be 40px in the dialog against 32px on the card behind
     it, which is the very drift this rule is here to prevent. */
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: var(--r-md);
  place-items: center;
}

.ui .confirm-dialog-icon > svg {
  width: 16px;
  height: 16px;
}

.ui .confirm-dialog-header h2 {
  margin: 0;
  font-size: var(--t-xl);
  font-weight: var(--w-bold);
  letter-spacing: -0.02em;
}

.ui .confirm-dialog-lede {
  margin: var(--s-3) 0 0;
  color: var(--text-secondary);
  font-size: var(--t-md);
  line-height: var(--lh-normal);
}

/* Each answer stands over its own consequence, so the two cannot be told apart by
   position alone — which is how a destructive choice gets pressed. Each is as wide as its
   own words, not as the dialog: a 44px bar the full width of the surface was the largest
   control left in the app once the page heads came down (2026-09-17, „visur per dideli
   mygtukai"), and the same shape the owner had already refused inside the defer menu —
   a solid blue band as wide as the thing it acts on. */
.ui .confirm-dialog-choices {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-3);
}

/* One height for every press in the dialog, the answers and „Atšaukti" alike: the large
   step, because a decision that cannot be undone deserves a little more than a row control
   and no more than that. On touch the token grows with the rest of the scale.

   BOTH BUTTON FAMILIES, because the dialog now has two homes: the cart-switch question was
   written with style.css's `.button`, and the generic one answers call sites that are all
   `.btn`. Naming both here is what stops the same dialog standing 44px tall on one screen
   and 40px on another. */
.ui .confirm-dialog .button,
.ui .confirm-dialog .btn {
  min-height: var(--h-lg);
}

.ui .confirm-dialog-hint {
  margin: 0 0 var(--s-4);
  color: var(--text-tertiary);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
}

.ui .confirm-dialog-hint:last-child {
  margin-bottom: 0;
}

/* The cart-switch question puts its two answers in the body and leaves „Atšaukti" alone
   down here; the generic yes/no puts both in this row. Cancel comes FIRST in the markup —
   it is what the focus lands on and what Esc does — and the gap is what stops the two
   reading as one wide control when they sit side by side. */
.ui .confirm-dialog-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid var(--border);
}

/* WHEN THE ANSWER DESTROYS SOMETHING, the tile says so before the button does. Read off
   the pressed control's own `btn--danger` (app.js `confirmTone`) rather than declared a
   second time in an attribute, so the dialog cannot disagree with the thing that opened
   it. */
.ui .confirm-dialog--danger .confirm-dialog-icon {
  color: var(--danger);
  background: var(--danger-soft);
}

/* AND THE ANSWER IS A BUTTON, not a word. `btn--danger` is deliberately quiet everywhere
   else — red text, no fill — because a table row carries twenty controls and a row of red
   slabs is unreadable. A dialog carries one, it is the whole reason the dialog is on
   screen, and beside „Atšaukti" a red word reads as the second of two links rather than as
   the press that acts. Scoped to the footer so the quiet version keeps every other home.

   `--on-accent` is not borrowed from the accent by accident: it is the token for "the
   colour that reads on a FILLED control", which is why it is white in the light theme and
   a dark glyph in the dark one — the same two answers a filled `--danger` needs, since
   that token is a deep red on one and a pale salmon on the other. Photographed in both. */
.ui .confirm-dialog-foot .btn--danger {
  --btn-bg: var(--danger);
  --btn-fg: var(--on-accent);
  --btn-bd: var(--danger);
}

.ui .confirm-dialog-foot .btn--danger:hover:not(:disabled) {
  --btn-bg: color-mix(in srgb, var(--danger) 88%, black);
  --btn-bd: color-mix(in srgb, var(--danger) 88%, black);
}

/* „Klaidos" (failures.html): the secondary line under a failure, and its traceback. */
.ui .failure-sub {
  color: var(--text-secondary);
  font-size: var(--t-sm);
}

.ui .failure-detail {
  max-height: 20rem;
  margin: var(--s-2) 0 0;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: var(--t-xs, var(--t-sm));
}

/* „Visi krepšeliai" (carts_all.html): one panel per browser's basket. */
.ui .all-cart {
  display: grid;
  gap: var(--s-3);
  margin-top: var(--s-4);
}

.ui .all-cart-head {
  display: grid;
  gap: var(--s-2);
}

.ui .all-cart-head h2 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  font-size: var(--t-base);
}

.ui .all-cart-facts,
.ui .all-cart-sub {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--t-sm);
}

.ui .all-cart-name {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  max-width: 36rem;
}

.ui .all-cart-name .input {
  flex: 1 1 14rem;
}
