/* ============================================================================
   Vertical-business POS layout — Resto / Service / Laundry
   ----------------------------------------------------------------------------
   Every rule is scoped under html.tp-vertical, which home.php sets only for
   business types 2, 6 and 7. Retail, Groceries, Pharmacy, Hardware and Gas never
   match a selector in this file, so the retail screen is untouched.

   Colours come from the tokens in theme.css. --primary-color is overwritten at
   runtime from pos_settings.color_pallete, so nothing here may hardcode an
   accent. Light mode inherits automatically because every token flips.
   ========================================================================== */

html.tp-vertical,
html.tp-vertical body {
    overflow: hidden;
}

/* ── Shell ───────────────────────────────────────────────────────────────── */

html.tp-vertical .container-fluid.home-container {
    max-width: none !important;
    width: 100% !important;
    height: 100vh !important;
    padding: 0 !important;
    margin: 0 !important;
    gap: 0 !important;
    overflow: hidden !important;
    display: block !important;
}

/* The retail screen is a draggable two-column split. Here it is a column:
   a full-width top bar over the order / grid row. */
html.tp-vertical .d-flex-home {
    display: flex !important;
    flex-direction: column !important;
    align-items: stretch !important;
    height: 100vh !important;
    min-height: 100vh !important;
    width: 100% !important;
    gap: 0 !important;
}

html.tp-vertical .tpv-body {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
}

/* ── Top bar ─────────────────────────────────────────────────────────────── */

html.tp-vertical .tpv-topbar {
    flex: 0 0 auto;
    box-sizing: border-box;
    height: 62px;
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 0 18px;
    background: var(--secondary-color);
    border-bottom: 1px solid var(--border-color);
}

html.tp-vertical .tpv-topbar .tpv-context:not(.tpv-rail-btn) {
    display: flex;
    align-items: center;
    gap: 9px;
    height: 44px;
    padding: 0 14px;
    border: 1px solid var(--primary-color);
    border-radius: 10px;
    background: transparent;
    color: var(--text-color);
    font-family: inherit;
    cursor: pointer;
    flex: 0 0 auto;
}

html.tp-vertical .tpv-context-static {
    border-color: var(--border-color);
    cursor: default;
}

/* Held while an order is on its way to the kitchen. Dimmed rather than hidden:
   the cashier still needs to read which table they are on. */
html.tp-vertical .dine_in_btn:disabled,
html.tp-vertical .dine_in_btn.is-busy,
html.tp-vertical .order_type_btn:disabled,
html.tp-vertical .order_type_btn.is-busy {
    opacity: 0.5;
    cursor: not-allowed;
    border-color: var(--border-color);
}

html.tp-vertical .tpv-context-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.2;
}

html.tp-vertical .tpv-context-main {
    font-size: 14px;
    font-weight: 600;
}

html.tp-vertical .tpv-context-sub {
    font-size: 10px;
    color: var(--information-color);
    font-weight: 500;
}
/* The sub-line now carries the diner's name, and .tpv-context is content-sized
   with no max-width — so a long name pushed the chip wider and the search box,
   which is flex: 1 1 auto beside it, gave up the room. Worst at 1024x768.

   Scoped to .dine_in_btn, which pos-vertical.php renders only under
   $isRestoLayout: the .tpv-context-sub class itself is shared with the
   Laundry and Service header, which shows a shop name and must not clip. */
html.tp-vertical .dine_in_btn .tpv-context-text,
html.tp-vertical .order_type_btn .tpv-context-text {
    min-width: 0;
}

html.tp-vertical .dine_in_btn .tpv-context-main,
html.tp-vertical .dine_in_btn .tpv-context-sub,
html.tp-vertical .order_type_btn .tpv-context-main,
html.tp-vertical .order_type_btn .tpv-context-sub {
    max-width: 190px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A table is open on this terminal. The chip is the only place the POS says
   which party it is ringing into, so it fills in rather than just changing its
   text — glanceable from across the counter, and unmistakably different from
   "no table chosen". table-management.js toggles the class. */
html.tp-vertical .tpv-context.is-active {
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
}

html.tp-vertical .tpv-context.is-active .tpv-context-main {
    color: var(--primary-ink, var(--primary-color));
    font-weight: 700;
}

html.tp-vertical .tpv-context.is-active .tpv-context-sub {
    color: color-mix(in srgb, var(--primary-ink, var(--primary-color)) 78%, transparent);
}

/* Table / Order Type as the first action button. In a side rail it is a tile
   like the others, with the table name as its label; the topbar version is
   further down with the other topbar action rules. */
html.tp-vertical .tpv-rail-context .tpv-context-text {
    align-items: center;
    max-width: 100%;
}

html.tp-vertical .tpv-rail-context .tpv-context-main,
html.tp-vertical .tpv-rail-context .tpv-context-sub {
    max-width: 100%;
}

html.tp-vertical .tpv-rail-context .tpv-context-main {
    font-size: 12.5px;
}

html.tp-vertical .tpv-rail-context.is-active .tpv-rail-icon {
    color: var(--primary-ink, var(--primary-color));
}

/* Search. pos-vertical.php puts it in the top bar while the action buttons
   are there, where it follows the order panel's drag handle; with them on the
   side there is no top bar, and it is a full-width row at the top of the menu
   column, in line with the price-list chips. */
html.tp-vertical .tpv-search {
    position: relative;
    display: flex;
    align-items: center;
}

html.tp-vertical .tpv-topbar .tpv-search {
    min-width: 200px;
}

/* In one row its edge facing the tiles lines up with the drag handle below
   (--tpv-search-w, kept in step by the resizer in pos-vertical.php), so the
   bar splits where the panels do. When the tiles need more room it gives way
   first, down to its minimum: the huge shrink factor keeps the tiles from
   giving up even a fraction of a pixel until then. */
html.tp-vertical .tpv-topbar:not(.tpv-topbar-wrap) .tpv-search {
    flex: 0 100000 var(--tpv-search-w, 300px);
}

html.tp-vertical #container2 .tpv-search {
    flex: 0 0 auto;
    margin: 0 20px 10px;
}

@media (max-width: 1100px) {
    html.tp-vertical .tpv-topbar .tpv-search {
        min-width: 160px;
    }
}

@media (max-width: 1100px), (max-height: 820px) {
    html.tp-vertical #container2 .tpv-search {
        margin: 0 14px 8px;
    }
}

html.tp-vertical .tpv-search-icon {
    position: absolute;
    left: 14px;
    color: var(--information-color);
    pointer-events: none;
    transition: color 0.15s ease;
    z-index: 1;
}

html.tp-vertical .tpv-search:focus-within .tpv-search-icon {
    color: var(--primary-ink, var(--primary-color));
}

/* home-redesign.css declares .search_input { padding: 10px 14px !important },
   which collapsed the left inset and printed the magnifier on top of the
   placeholder. Every box property is therefore re-stated at the same weight. */
html.tp-vertical .tpv-search .search_input {
    width: 100% !important;
    height: 42px !important;
    box-sizing: border-box !important;
    padding: 0 38px 0 42px !important;
    border: 1px solid var(--border-color) !important;
    border-radius: 10px !important;
    background: var(--background-color) !important;
    color: var(--text-color) !important;
    font-family: inherit;
    font-size: 13.5px !important;
    text-overflow: ellipsis;
    outline: none !important;
    transition: border-color 0.15s ease, box-shadow 0.15s ease !important;
}

html.tp-vertical .tpv-search .search_input::placeholder {
    color: var(--information-color) !important;
    opacity: 0.8;
}

html.tp-vertical .tpv-search .search_input:focus {
    border-color: var(--primary-color) !important;
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent) !important;
}

/* Right-hand affordances: a barcode mark while empty, a clear button once there
   is something to clear. Exactly one is visible at a time. */
html.tp-vertical .tpv-search-scan,
html.tp-vertical .tpv-search-clear {
    position: absolute;
    right: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--information-color);
    z-index: 1;
}

html.tp-vertical .tpv-search-scan {
    pointer-events: none;
    opacity: 0.55;
}

html.tp-vertical .tpv-search-clear {
    width: 24px;
    height: 24px;
    right: 10px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: color-mix(in srgb, var(--text-color) 12%, transparent);
    cursor: pointer;
    display: none;
}

html.tp-vertical .tpv-search-clear:hover {
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

/* .is-filled is toggled by the script in pos-vertical.php on every input. */
html.tp-vertical .tpv-search.is-filled .tpv-search-scan {
    display: none;
}

html.tp-vertical .tpv-search.is-filled .tpv-search-clear {
    display: flex;
}

html.tp-vertical .tpv-search #search-results {
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    min-width: min(380px, calc(100vw - 32px));
    z-index: 1000;
    background: var(--background-color);
    outline: none;
}

/* Bound by index.js on every page, but not part of this layout's chrome. */
html.tp-vertical .tpv-hidden-ctl {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
    padding: 0;
}

html.tp-vertical .tpv-status,
html.tp-vertical .tpv-user,
html.tp-vertical .tpv-clock {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11.5px;
    color: var(--information-color);
    white-space: nowrap;
}

/* The cashier reads as a name, not as another status readout — and it gives up
   width before the clock does, because a truncated name is still recognisable
   and a truncated time is not. */
html.tp-vertical .tpv-user {
    min-width: 0;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
    font-weight: 600;
    color: var(--text-color);
}

html.tp-vertical .tpv-clock .pos-clock-src {
    font-size: 10px;
    opacity: 0.75;
}

html.tp-vertical .tpv-icon-btn {
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border-color);
    border-radius: 9px;
    background: transparent;
    color: var(--text-color);
    cursor: pointer;
}

html.tp-vertical .tpv-icon-btn:hover {
    border-color: var(--primary-color);
}

html.tp-vertical .tpv-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* ── Order panel (#container1) ───────────────────────────────────────────── */

html.tp-vertical #container1 {
    /* 460 counts the action rail, which now sits beside the panel, so the
       panel gives the rail's width back (see --tpv-rail-reserve). The resizer
       script sets the real width inline the same way. */
    flex: 0 0 calc(460px - var(--tpv-rail-reserve)) !important;
    width: calc(460px - var(--tpv-rail-reserve)) !important;
    min-width: calc(460px - var(--tpv-rail-reserve)) !important;
    max-width: calc(460px - var(--tpv-rail-reserve)) !important;
    height: auto !important;
    min-height: 0 !important;
    display: flex !important;
    /* A row now: the order column, then the action rail beside it. */
    flex-direction: row !important;
    padding: 0 !important;
    background: var(--secondary-color) !important;
    color: var(--text-color) !important;
    /* No border-right: the .tpv-resizer beside it draws the separation, and two
       adjacent borders read as a double rule. */
    border-right: 0 !important;
}

/* ── Swapped sides ───────────────────────────────────────────────────────── */

/* html.tp-cart-right puts the tile grid first and the order panel last. Only the
   visual order changes: the DOM keeps #container1 before #container2, so every
   selector, handler and tab sequence is unaffected. */
html.tp-vertical.tp-cart-right .tpv-body {
    flex-direction: row-reverse;
}

/* The handle's rule has to hug the order panel, so it flips with it. */
html.tp-vertical .tpv-resizer {
    border-right: 1px solid var(--border-color);
    border-left: 0;
}

html.tp-vertical.tp-cart-right .tpv-resizer {
    border-right: 0;
    border-left: 1px solid var(--border-color);
}

/* ── Resizer ─────────────────────────────────────────────────────────────── */

html.tp-vertical .tpv-resizer {
    flex: 0 0 9px;
    width: 9px;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: col-resize;
    background: var(--secondary-color);
    border-right: 1px solid var(--border-color);
    /* Stops the browser panning the page instead of dragging the handle. */
    touch-action: none;
    user-select: none;
}

html.tp-vertical .tpv-resizer span {
    width: 2px;
    height: 30px;
    border-radius: 2px;
    background: var(--border-color);
    pointer-events: none;
}

html.tp-vertical .tpv-resizer:hover span,
html.tp-vertical .tpv-resizer:focus-visible span {
    background: var(--primary-color);
    height: 46px;
}

html.tp-vertical .tpv-resizer:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: -2px;
}

/* While dragging, the cursor stays col-resize everywhere and nothing
   text-selects under the pointer. */
body.tpv-resizing,
body.tpv-resizing * {
    cursor: col-resize !important;
    user-select: none !important;
}

html.tp-vertical .tpv-order-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 16px 10px;
}

html.tp-vertical .tpv-order-title {
    font-size: 13px;
    font-weight: 600;
}

/* ── Whose order this is ─────────────────────────────────────────────────── */

/* The diner the order was opened under — "Customer 0028", or a name the
   cashier typed. Deliberately NOT the attached-customer chip beside it: that
   one is the registered customer on the sale, and conflating them would put a
   loyalty member's name on a walk-in's table.

   Plain text, no icon and no hit area, so the two never read as the same
   control. */
html.tp-vertical .tpv-order-diner {
    margin-left: auto;
    min-width: 0;
    max-width: 60%;
    display: flex;
    align-items: center;
    height: 32px;
    padding: 0 11px;
    border-radius: 16px;
    background: color-mix(in srgb, var(--text-color) 7%, transparent);
    color: color-mix(in srgb, var(--text-color) 72%, transparent);
    font-size: 12px;
    font-weight: 600;
    animation: tpvChipIn 0.18s ease;
}

html.tp-vertical .tpv-order-diner[hidden] {
    display: none;
}

html.tp-vertical .tpv-order-diner-name {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Both carry margin-left:auto, and two auto margins SPLIT the free space —
   which would strand one in the middle of the row. With the diner shown it
   owns the push and the chip sits beside it. */
html.tp-vertical .tpv-order-diner:not([hidden]) ~ .tpv-customer-chip {
    margin-left: 0;
    max-width: 48%;
}

/* ── Attached-customer chip ──────────────────────────────────────────────── */

/* Sits on the right of the "Order" heading. margin-left:auto pushes it across
   even when it is the only other item in the row. */
html.tp-vertical .tpv-customer-chip {
    margin-left: auto;
    min-width: 0;
    max-width: 72%;
    display: flex;
    align-items: center;
    gap: 7px;
    height: 32px;
    padding: 0 11px 0 10px;
    border: 1px solid color-mix(in srgb, var(--primary-color) 45%, transparent);
    border-radius: 16px;
    background: color-mix(in srgb, var(--primary-color) 10%, transparent);
    color: var(--text-color);
    font-family: inherit;
    cursor: pointer;
    animation: tpvChipIn 0.18s ease;
}

/* The [hidden] attribute must win over the display:flex above. */
html.tp-vertical .tpv-customer-chip[hidden] {
    display: none;
}

html.tp-vertical .tpv-customer-chip:hover {
    border-color: var(--primary-color);
}

html.tp-vertical .tpv-customer-chip:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

html.tp-vertical .tpv-customer-chip > svg {
    flex: 0 0 auto;
    color: var(--primary-color);
}

/* The name gives way before the discount badge does: losing "…" off a long
   surname costs less than losing the fact that a PWD discount is on. */
html.tp-vertical .tpv-customer-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
    font-weight: 600;
}

html.tp-vertical .tpv-customer-type {
    flex: 0 0 auto;
    padding: 2px 7px;
    border-radius: 9px;
    background: var(--primary-color);
    color: #fff;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

html.tp-vertical .tpv-customer-type[hidden] {
    display: none;
}

@keyframes tpvChipIn {
    from { opacity: 0; transform: translateX(6px); }
    to   { opacity: 1; transform: translateX(0); }
}

/* Rail echo: a dot on the Customer icon, so the attachment is visible even
   when the eye is on the rail rather than the header. */
html.tp-vertical .tpv-rail-btn.is-attached .tpv-rail-icon::after {
    content: "";
    position: absolute;
    top: -2px;
    right: -3px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--primary-color);
    box-shadow: 0 0 0 2px var(--secondary-color);
}

@media (prefers-reduced-motion: reduce) {
    html.tp-vertical .tpv-customer-chip { animation: none; }
}


/* home-redesign.css gives .table-container a !important background, height and
   flex — the class has to stay because process.js scrolls and focuses it
   (process.js:2668, :2854, :3812, :4217), so every property is re-stated here
   at the same weight. Without this the cart renders as a flat grey slab. */
html.tp-vertical .tpv-lines.table-container {
    flex: 1 1 auto !important;
    min-height: 0;
    overflow-y: auto !important;
    overflow-x: hidden;
    padding: 0 12px !important;
    outline: none;
    background: transparent !important;
    height: auto !important;
    max-height: none !important;
}

/* process.js injects its own watermark into the tbody when the cart empties, but
   rows added inline never remove it, so it lingered under the items. The
   .tpv-cart-empty state below replaces it. */
html.tp-vertical .tpv-lines .img-logo {
    display: none !important;
}

/* Empty cart: the logo, large and centred in the list area. Shown only while
   the table has no rows — keyed to the rows themselves, not to any one script. */
html.tp-vertical .tpv-cart-empty {
    display: none;
    position: absolute;
    inset: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 24px;
    pointer-events: none;
}

html.tp-vertical .tpv-lines {
    position: relative;
}

html.tp-vertical .tpv-lines:not(:has(#tbody-cashiering > tr)) .tpv-cart-empty {
    display: flex;
}

html.tp-vertical .tpv-cart-empty-logo {
    width: min(62cqw, 300px);
    max-height: 45%;
    object-fit: contain;
    /* A watermark: present, but never competing with the order. */
    opacity: 0.18;
}

/* No logo yet (fresh profile, request pending): no broken-image box. */
html.tp-vertical .tpv-cart-empty-logo:not([src]),
html.tp-vertical .tpv-cart-empty-logo[src=""] {
    visibility: hidden;
}

html.tp-vertical .tpv-cart-empty-text {
    font-size: 12.5px;
    color: var(--information-color);
    opacity: 0.8;
    text-align: center;
}

/* The cashier and the clock, under the empty-cart line. Gone with the rest of
   the empty state as soon as the order has a line. */
html.tp-vertical .tpv-cart-empty-who {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    max-width: 100%;
    margin-top: 6px;
}

html.tp-vertical .tpv-cart-empty-who .tpv-user {
    max-width: 100%;
    font-size: 15px;
    text-align: center;
}

html.tp-vertical .tpv-cart-empty-who .tpv-clock {
    font-size: 13px;
}

html.tp-vertical .tpv-lines::-webkit-scrollbar {
    width: 7px;
}

html.tp-vertical .tpv-lines::-webkit-scrollbar-thumb {
    background: var(--border-color);
    border-radius: 4px;
}

/* Auto layout, not fixed. Fixed + hard pixel widths meant a long value like
   18,990.00 overflowed its box and painted over the next column, and the
   columns never grew when the panel was dragged wider. With auto layout the
   numeric cells take exactly the width their content needs (they are nowrap and
   width:1%) and the description absorbs whatever is left, at any panel width. */
html.tp-vertical .tpv-lines table {
    width: 100%;
    margin: 0;
    color: var(--text-color);
    table-layout: auto;
}

html.tp-vertical .tpv-lines td {
    padding: 9px 4px !important;
    border: 0;
    border-bottom: 1px solid var(--hairline);
    font-size: 12.5px;
    vertical-align: middle;
    color: var(--text-color);
    background: transparent;
    height: auto !important;
}

/* process.js emits seven columns for a full-width retail cart. Three of them —
   SKU, the empty dot cell and the discount-arrow cell — do not fit a 384px
   order panel, and squeezing them in is what made qty, price and total overlap.
   Hidden here only; the cells still exist, so nothing that reads them breaks. */
html.tp-vertical .tpv-lines #table-sku,
html.tp-vertical .tpv-lines td.first-col,
html.tp-vertical .tpv-lines #iconDiscount {
    display: none !important;
}

html.tp-vertical .tpv-lines td.prod_desc {
    width: auto;
    max-width: none !important;
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
    font-size: 13px !important;
    line-height: 1.3;
    padding-right: 8px !important;
}

/* The barcode line is absolutely positioned at the bottom of a tall retail row.
   In this compact row it overlapped the product name, so it flows normally. */
html.tp-vertical .tpv-lines td.prod_desc #extraText {
    position: static !important;
    display: block;
    margin: 3px 0 0;
    font-size: 10.5px !important;
    font-weight: 500 !important;
    line-height: 1.3;
    white-space: normal;
    color: var(--information-color) !important;
}

/* width:1% + nowrap is the shrink-to-fit idiom for an auto table: the cell is
   never narrower than its number, and never wider than it needs to be. */
html.tp-vertical .tpv-lines td.cus-col-qty,
html.tp-vertical .tpv-lines td.cus-col-price,
html.tp-vertical .tpv-lines #totalRowPrice {
    width: 1%;
    white-space: nowrap !important;
    text-align: right;
    font-variant-numeric: tabular-nums;
    overflow: visible !important;
    text-overflow: clip !important;
}

html.tp-vertical .tpv-lines td.cus-col-qty {
    font-weight: 600;
    padding-left: 8px !important;
}

html.tp-vertical .tpv-lines td.cus-col-price {
    color: var(--information-color);
    padding-left: 8px !important;
}

html.tp-vertical .tpv-lines #totalRowPrice {
    font-weight: 700;
    padding-left: 8px !important;
    padding-right: 2px !important;
}

/* home-redesign.css and responsive-1024.css pin the retail cart with
   table-layout: fixed and 6-8% column widths on #tableTransactions /
   #tbody-cashiering. Those id selectors outrank the .tpv-lines rules above, so
   here the money columns stayed ~40px wide at any panel width and 4-5 digit
   values ran into each other (or were cut off with an ellipsis at 1024px).
   Restated at id specificity so the auto layout above actually applies. */
html.tp-vertical #tableTransactions {
    table-layout: auto !important;
}

/* width:100% + max-width:0 lets the name take whatever the numbers leave and
   truncate there, instead of its one-line width stretching the table. */
html.tp-vertical #tbody-cashiering tr td.prod_desc {
    width: 100% !important;
    max-width: 0 !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}

html.tp-vertical #tbody-cashiering tr td.prod_desc #extraText {
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}

html.tp-vertical #tbody-cashiering tr td.cus-col-qty,
html.tp-vertical #tbody-cashiering tr td.cus-col-price,
html.tp-vertical #tbody-cashiering tr td#totalRowPrice {
    width: 1% !important;
    white-space: nowrap !important;
    overflow: visible !important;
    text-overflow: clip !important;
    text-align: right !important;
    font-size: 12.5px !important;
}

html.tp-vertical #tbody-cashiering tr td.cus-col-qty,
html.tp-vertical #tbody-cashiering tr td.cus-col-price {
    padding-left: 10px !important;
    padding-right: 4px !important;
}

/* Narrow panel: the unit price is the least useful of the three numbers —
   quantity and line total carry the meaning — so it is the first to go rather
   than letting all three crush together.

   Keyed to the LIST's own width, not the viewport, because the operator drags
   the panel independently of the window. The container is .tpv-lines rather
   than #container1 deliberately: container-type also applies layout
   containment, which would make #container1 a containing block for any
   position:fixed descendant. Where container queries are unsupported the block
   is simply ignored and the price column stays — the auto table layout above
   already guarantees the columns never overlap. */
html.tp-vertical .tpv-lines {
    container-type: inline-size;
    container-name: tpvorder;
}

@container tpvorder (max-width: 316px) {
    html.tp-vertical .tpv-lines td.cus-col-price {
        display: none !important;
    }
}

html.tp-vertical .tpv-lines tr.selectable-row.selected td,
html.tp-vertical .tpv-lines tr.selected td {
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
}

/* ── Totals ──────────────────────────────────────────────────────────────── */

html.tp-vertical .tpv-totals {
    flex: 0 0 auto;
    padding: 12px 20px 0;
    border-top: 1px solid var(--border-color);
    /* Its own container, so the grand total's cqi font size measures THIS block
       and not the viewport — the totals sit outside .tpv-lines, so without this
       the units would resolve against the window and never track the panel. */
    container-type: inline-size;
}

html.tp-vertical .tpv-total-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 3px 0;
    font-size: 12.5px;
    color: var(--information-color);
}

/* The label yields, the number never wraps or truncates. */
html.tp-vertical .tpv-total-row > span:first-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

html.tp-vertical .tpv-total-row > span:last-child {
    flex: 0 0 auto;
    white-space: nowrap;
    color: var(--info-val-color);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Retail hides VAT in the summary too; kept in the DOM because process.js writes it. */
html.tp-vertical .tpv-total-vat {
    display: none;
}

/* The grand total is shown on the Payment button (mirrored by the script in
   pos-vertical.php). The row stays in the DOM because every script writes
   #totalPayment; set display back to flex to show it here as well. */
html.tp-vertical .tpv-total-grand {
    display: none;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 11px 0 14px;
    margin-top: 6px;
    border-top: 1px solid var(--hairline);
}

html.tp-vertical .tpv-total-grand > span:first-child {
    flex: 0 0 auto;
    font-size: 13px;
    font-weight: 600;
}

/* Scales with the panel so a seven-figure total cannot overflow a narrow one,
   and never wraps onto a second line. */
html.tp-vertical .tpv-grand-value {
    flex: 1 1 auto;
    min-width: 0;
    text-align: right;
    font-size: clamp(19px, 6.2cqi, 28px);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.05;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* ── Actions ─────────────────────────────────────────────────────────────── */

/* Send and Payment share one row. A container, so the Payment total and the
   F-key hints can size to the row rather than to the window. */
html.tp-vertical .tpv-actions {
    flex: 0 0 auto;
    padding: 12px 14px 14px;
    display: flex;
    gap: 8px;
    container-type: inline-size;
    container-name: tpvactions;
}

html.tp-vertical .tpv-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex: 1 1 0;
    min-width: 0;
    height: 58px;
    padding: 0 10px;
    border-radius: 9px;
    font-family: inherit;
    line-height: 1.15;
    text-align: center;
    cursor: pointer;
}

/* Outline: secondary to Payment, which is the only solid control on screen. */
html.tp-vertical .tpv-btn-send {
    border: 1px solid color-mix(in srgb, var(--text-color) 26%, transparent);
    background: transparent;
    color: var(--text-color);
    font-size: 13.5px;
    font-weight: 600;
}

html.tp-vertical .tpv-btn-send:hover {
    border-color: var(--primary-color);
}

/* ── Sending to the kitchen ─────────────────────────────────────────────
   Held from the moment the send starts until the till has actually cleared,
   which is a round trip later than the server saying yes. Without it the
   cashier watches a cart that still has everything in it and presses again. */

html.tp-vertical .tpv-btn-send.is-sending {
    position: relative;
    color: transparent;
    border-color: var(--primary-color);
    cursor: progress;
}

/* The label is hidden by `color: transparent`, but the count chip and the
   F-key pill carry their own colours, so they are hidden outright. */
html.tp-vertical .tpv-btn-send.is-sending > * {
    visibility: hidden;
}

html.tp-vertical .tpv-btn-send.is-sending::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 17px;
    height: 17px;
    margin: -9px 0 0 -9px;
    border: 2px solid color-mix(in srgb, var(--primary-color) 28%, transparent);
    border-top-color: var(--primary-color);
    border-radius: 50%;
    animation: tpvSendSpin 0.6s linear infinite;
}

@keyframes tpvSendSpin {
    to { transform: rotate(360deg); }
}

/* The cart while its order is on its way. Dimmed and inert — the items are
   still real until the send lands, so they stay readable rather than being
   swapped for a skeleton. */
html.tp-vertical.tpv-cart-sending #tableTransactions,
html.tp-vertical.tpv-cart-sending .tpv-totals {
    opacity: 0.45;
    pointer-events: none;
    transition: opacity 0.12s ease;
}

html.tp-vertical.tpv-cart-sending .tpv-cart-main {
    cursor: progress;
}

/* ── Order lock ─────────────────────────────────────────────────────────
   An occupied table opens read-only, so a stray scan cannot change food that
   is already cooking. Editing only: Payment and Send to kitchen stay live. */

html.tp-vertical .tpv-rail-lock.is-open {
    color: var(--primary-color);
}

/* The shackle swings open when the order is editable. */
html.tp-vertical .tpv-rail-lock.is-open .tpv-lock-shackle {
    transform: translateX(3px) rotate(-18deg);
    transform-origin: 16px 10px;
}

html.tp-vertical .tpv-rail-lock .tpv-lock-shackle {
    transition: transform 0.16s ease;
}

html.tp-vertical .tpv-rail-btn:disabled {
    opacity: 0.38;
    cursor: not-allowed;
}

/* A hairline down the cart while it is locked: present enough to explain why a
   scan did nothing, quiet enough not to shout during service. */
/* The lock watermark.

   Shown only while the order can actually be locked: table-management.js puts
   is-locked or is-unlocked on .tpv-cart-main and clears BOTH when no lock
   applies, so an ordinary draft draws nothing. That also keeps it off an empty
   cart, where the store logo already owns this box -- and .tpv-cart-empty
   shows only with zero rows, while a lock needs sent ones, so the two can
   never appear together.

   Same idiom as that logo: absolutely positioned over the panel, out of the
   hit-testing, and faint enough never to compete with the order. */
html.tp-vertical .tpv-cart-lock {
    display: none;
    position: absolute;
    inset: 0;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    z-index: 0;
}

html.tp-vertical .tpv-cart-main.is-locked .tpv-cart-lock,
html.tp-vertical .tpv-cart-main.is-unlocked .tpv-cart-lock {
    display: flex;
}

html.tp-vertical .tpv-cart-lock-mark {
    /* Not cqw: container-name: tpvorder lives on .tpv-lines, and this element
       sits OUTSIDE it, so a container unit here would resolve against the
       wrong box. A percentage of the panel plus a ceiling is exact either
       way. */
    width: min(58%, 280px);
    max-height: 52%;
    color: var(--tm-billout, #b8860b);
    opacity: 0.14;
    transition: opacity 120ms ease;
}

/* Unlocked reads as the quieter of the two: the shackle is open, and an order
   anyone may edit is the ordinary state, so it should not shout. */
html.tp-vertical .tpv-cart-main.is-unlocked .tpv-cart-lock-mark {
    opacity: 0.09;
}

/* The same swing the rail's padlock uses, so the two marks never disagree. */
html.tp-vertical .tpv-cart-main.is-unlocked .tpv-cart-lock-mark .tpv-lock-shackle {
    transform: rotate(-22deg);
    transform-origin: 8px 10.5px;
}

/* Everything in the cart sits above the watermark.

   All four in-flow children are named, and each needs position too: a
   z-index on a static box is inert, so listing .tpv-totals alone (which has
   no position of its own) would have let the mark paint over the grand
   total. None of them has an absolutely-positioned descendant that was
   anchored to .tpv-cart-main, so becoming a containing block changes no
   layout -- the one absolute in this subtree,
   .tpv-btn-send.is-sending::after, is anchored by its own element. */
html.tp-vertical .tpv-order-head,
html.tp-vertical .tpv-lines,
html.tp-vertical .tpv-totals,
html.tp-vertical .tpv-actions {
    position: relative;
    z-index: 1;
}

/* A short till has no room for a watermark behind four buttons and a total. */
@media (max-height: 700px) {
    html.tp-vertical .tpv-cart-lock {
        display: none !important;
    }
}

html.tp-vertical .tpv-cart-main.is-locked {
    box-shadow: inset 3px 0 0 color-mix(in srgb, var(--tm-billout, #b8860b) 65%, transparent);
}

/* ── Cart line marks ────────────────────────────────────────────────────
   What has happened to each line: new, already with the kitchen, frozen by
   the lock, or edited since it was cooked.

   The pill rides at the FRONT of the small barcode line inside td.prod_desc,
   which is nowrap + ellipsis at id specificity — so the barcode is what gets
   cut when the two do not fit, never the status. */

html.tp-vertical .tpv-line-mark {
    display: inline-block;
    margin-right: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.5px;
    vertical-align: middle;
    white-space: nowrap;
}

/* The badge palette has to survive BOTH themes, and theme.css defaults :root
   to the dark one. A single amber or green is never right for both — the
   bright version washes out on the light background and the dark version
   disappears on the dark one, which is exactly why theme.css ships paired
   --stock-ok-color / --stock-low-color values. Same idea, same pattern. */
html.tp-vertical {
    --cart-mark-added: var(--stock-ok-color, #2ecc71);
    --cart-mark-reduced: #f0a020;
    --cart-mark-removed: var(--stock-low-color, #ff6b6b);
}

html.tp-vertical.light-mode {
    --cart-mark-reduced: #a86000;
}

html.tp-vertical .tpv-line-mark.is-line-new {
    background: color-mix(in srgb, var(--primary-color) 18%, transparent);
    color: color-mix(in srgb, var(--primary-color) 80%, var(--text-color));
}

html.tp-vertical .tpv-line-mark.is-line-sent {
    background: color-mix(in srgb, var(--text-color) 10%, transparent);
    color: color-mix(in srgb, var(--text-color) 65%, transparent);
}

/* Frozen by the lock. Red, because the cashier needs to know at a glance why
   a tap did nothing — a quiet grey badge read as decoration.

   An OUTLINE rather than a filled pill, which is what keeps it apart from
   DELETED just below: that one is a red tint, this one is a red edge. Locked
   is a state the line is resting in, not something that happened to it. */
html.tp-vertical .tpv-line-mark.is-line-locked {
    background: color-mix(in srgb, var(--cart-mark-removed) 10%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cart-mark-removed) 65%, transparent);
    color: color-mix(in srgb, var(--cart-mark-removed) 90%, var(--text-color));
}

/* Quantity moved since the kitchen was told. Up is a new instruction to cook,
   down is a retraction, so they do not share a colour. */
html.tp-vertical .tpv-line-mark.is-line-added {
    background: color-mix(in srgb, var(--cart-mark-added) 20%, transparent);
    color: color-mix(in srgb, var(--cart-mark-added) 85%, var(--text-color));
}

html.tp-vertical .tpv-line-mark.is-line-reduced {
    background: color-mix(in srgb, var(--cart-mark-reduced) 22%, transparent);
    color: color-mix(in srgb, var(--cart-mark-reduced) 85%, var(--text-color));
}

/* Same quantity, different instructions — an add-on added or removed, a
   note, a sugar level. It is a change the kitchen has not been told about
   yet, so it must not wear the settled badge; it borrows the "moved since
   the kitchen was told" colour rather than a fifth one. */
html.tp-vertical .tpv-line-mark.is-line-amended {
    background: color-mix(in srgb, var(--cart-mark-reduced) 20%, transparent);
    color: color-mix(in srgb, var(--cart-mark-reduced) 88%, var(--text-color));
}
/* Taken off the bill. Deleted is the cashier tidying up, voided is a formal
   act with a reason recorded against it — so voided carries the heavier fill. */
html.tp-vertical .tpv-line-mark.is-line-deleted {
    background: color-mix(in srgb, var(--cart-mark-removed) 20%, transparent);
    color: color-mix(in srgb, var(--cart-mark-removed) 85%, var(--text-color));
}

html.tp-vertical .tpv-line-mark.is-line-voided {
    background: var(--tm-occupied, #e74c3c);
    color: #fff;
}

html.tp-vertical tr.is-line-sent td,
html.tp-vertical tr.is-line-locked td {
    color: color-mix(in srgb, var(--text-color) 72%, transparent);
}

/* ── Removed items ──────────────────────────────────────────────────────
   Deleting or voiding a line the kitchen already has drops it out of the
   cart query, so it would simply vanish. It reappears here until the ticket
   goes out — outside the cart table, so nothing that counts cart rows sees
   it (see the note in layout/pos-vertical.php). */

html.tp-vertical .tpv-removed {
    margin: 6px 4px 10px;
    padding: 6px 8px 4px;
    border: 1px dashed color-mix(in srgb, var(--cart-mark-removed) 45%, transparent);
    border-radius: 8px;
    background: color-mix(in srgb, var(--cart-mark-removed) 8%, transparent);
}

html.tp-vertical .tpv-removed[hidden] {
    display: none;
}

html.tp-vertical .tpv-removed-head {
    margin-bottom: 4px;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--cart-mark-removed) 88%, var(--text-color));
}

html.tp-vertical .tpv-removed-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 0;
    font-size: 12px;
    color: color-mix(in srgb, var(--text-color) 70%, transparent);
}

html.tp-vertical .tpv-removed-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-decoration: line-through;
    text-decoration-color: color-mix(in srgb, var(--cart-mark-removed) 70%, transparent);
}

html.tp-vertical .tpv-removed-row .tpv-line-mark {
    flex: 0 0 auto;
    margin-right: 0;
}

html.tp-vertical .tpv-removed-qty {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

/* Disabled now means two different things. A send in flight keeps the button
   solid, because the spinner and the dimmed cart already say what is
   happening. A LOCKED order has no spinner, so a solid button would read as
   live and simply not respond — worse than an obviously unavailable one. */
html.tp-vertical .tpv-btn-send:disabled {
    opacity: .45;
    cursor: not-allowed;
}

html.tp-vertical.tpv-cart-sending .tpv-btn-send:disabled {
    opacity: 1;            /* the lock has no spinner; a send does */
    cursor: default;
}

html.tp-vertical .tpv-count:not(:empty) {
    min-width: 20px;
    padding: 1px 7px;
    border-radius: 9px;
    background: var(--primary-color);
    color: var(--on-primary, #fff);
    font-size: 11px;
    font-weight: 700;
}

/* Restaurant: no count badge on Send to kitchen. Hidden, not removed — the
   number is still written and read (auto-zread.js). */
html.tp-vertical .tpv-count.tpv-count-hidden {
    display: none !important;
}

/* Two sections side by side, split by a divider: PAYMENT F5 on the left, the
   grand total on the right. The amount is what the cashier reads aloud, so it
   gets the larger share and the weight. */
html.tp-vertical .tpv-btn-pay {
    flex-grow: 1.25;
    flex-direction: row;
    align-items: stretch;
    gap: 0;
    padding: 0;
    border: none;
    background: var(--primary-color);
    color: var(--on-primary, #fff);
    /* The total is sized from this button's own width, not the whole row. */
    container-type: inline-size;
    container-name: tpvpay;
}

html.tp-vertical .tpv-pay-label {
    flex: 0 0 38%;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    align-content: center;
    justify-content: center;
    gap: 2px 6px;
    padding: 0 8px;
    border-right: 2px solid color-mix(in srgb, var(--on-primary, #fff) 38%, transparent);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* A coupon note, when there is one, sits under PAYMENT F5. */
html.tp-vertical .tpv-pay-label .tpv-coupon:not(:empty) {
    flex-basis: 100%;
    text-align: center;
    text-transform: none;
    letter-spacing: 0;
}

html.tp-vertical .tpv-pay-total {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 12px;
    overflow: hidden;
    /* ~62% of the button, room for "₱ 12,345.67" at this size. */
    font-size: clamp(12px, calc(9cqi - 4px), 24px);
    font-weight: 800;
    letter-spacing: -0.01em;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Beside Send to Kitchen on a narrow order panel the two sections would
   squeeze the amount below reading size, so Payment stacks the label over the
   total instead. Pay-only (no Send button) always keeps the split. */
@container tpvactions (max-width: 390px) {
    html.tp-vertical .tpv-actions:not(.tpv-actions-pay-only) .tpv-btn-pay {
        flex-direction: column;
        justify-content: center;
        gap: 2px;
    }

    html.tp-vertical .tpv-actions:not(.tpv-actions-pay-only) .tpv-pay-label {
        flex: 0 0 auto;
        padding: 0 6px;
        border-right: 0;
        font-size: 11px;
    }

    html.tp-vertical .tpv-actions:not(.tpv-actions-pay-only) .tpv-pay-total {
        flex: 0 0 auto;
        padding: 0 8px;
        font-size: clamp(12px, calc(14cqi - 3px), 20px);
    }
}

@container tpvactions (max-width: 300px) {
    html.tp-vertical .tpv-fkey {
        display: none;
    }
}

html.tp-vertical .tpv-btn-pay:hover {
    filter: brightness(1.07);
}

html.tp-vertical .tpv-coupon:not(:empty) {
    font-size: 11px;
    font-weight: 600;
    opacity: 0.85;
}

/* F-key hints. Present because a cashier working keyboard-first should not have
   to remember which key sends the order — but quiet enough not to compete. */
html.tp-vertical .tpv-fkey {
    font-size: 10px;
    font-weight: 600;
    color: var(--information-color);
    letter-spacing: 0.02em;
}

html.tp-vertical .tpv-fkey-on-solid {
    color: var(--on-primary, #fff);
    opacity: 0.6;
}

/* ── Order column + action rail ─────────────────────────────────────────── */

/* The order column: list, totals, commit buttons. Takes whatever the rail
   leaves, and must be allowed to shrink or the rail gets pushed out. */
html.tp-vertical .tpv-cart-main {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    /* The lock watermark anchors here. Deliberately NOT container-type:
       inline-size as well -- that would also apply layout containment and make
       this a containing block for any position:fixed descendant, which is the
       trap the note above .tpv-lines records. */
    position: relative;
}

/* Vertical rail of order actions. Always visible, one tap. It costs width
   rather than height, so the item list keeps its full length. */
html.tp-vertical .tpv-rail {
    flex: 0 0 var(--tpv-rail-w);
    width: var(--tpv-rail-w);
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 12px 0;
    /* The tiles run edge to edge, so these two rules are their side borders. */
    border-left: 1px solid var(--border-color);
    border-right: 1px solid var(--border-color);
    background: color-mix(in srgb, var(--background-color) 55%, var(--secondary-color));
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none;
}

html.tp-vertical .tpv-rail::-webkit-scrollbar {
    display: none;
}

html.tp-vertical .tpv-rail-btn {
    flex: 0 0 auto;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 100%;
    min-height: 70px;
    padding: 8px 6px 7px;
    /* Flat square tiles. box-shadow: none drops theme.css's global button shadow.
       Top and bottom only: the rail's own borders are the sides. */
    border: 1px solid var(--border-color);
    border-width: 1px 0;
    border-radius: 0;
    background: var(--secondary-color);
    box-shadow: none;
    color: var(--text-color);
    font-family: inherit;
    cursor: pointer;
    text-align: center;
    transition: background 0.12s ease, border-color 0.12s ease;
}

/* Touching tiles share one hairline instead of doubling their borders. */
html.tp-vertical .tpv-rail-btn + .tpv-rail-btn {
    margin-top: -1px;
}

/* An inset ring, since the tiles have no side borders to colour. Raised so the
   neighbour it overlaps does not cover it. */
html.tp-vertical .tpv-rail-btn:hover {
    z-index: 1;
    background: color-mix(in srgb, var(--text-color) 5%, var(--secondary-color));
    box-shadow: inset 0 0 0 1px var(--primary-color);
}

html.tp-vertical .tpv-rail-btn:focus-visible {
    z-index: 1;
    outline: none;
    box-shadow: inset 0 0 0 1px var(--primary-color);
    background: var(--secondary-color);
}

html.tp-vertical .tpv-rail-btn:active {
    transform: scale(0.96);
}

html.tp-vertical .tpv-rail-icon {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    color: var(--text-color);
}

/* Current default quantity, pinned to the Qty icon so it reads without a label.
   #defQty is written by process.js whenever the default changes. */
html.tp-vertical .tpv-rail-badge {
    position: absolute;
    top: -6px;
    right: -10px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: 9px;
    background: var(--primary-color);
    color: var(--on-primary, #fff);
    font-size: 10px;
    font-weight: 700;
    line-height: 17px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* theme.css colours #defQty as a page label (retail); here it sits on the accent. */
html.tp-vertical #defQty.tpv-rail-badge {
    color: var(--on-primary, #fff);
}

/* The label can be rewritten by the app (#c_name becomes the customer's name,
   #numPax becomes "4 PERSON DINING"), so a long one wraps onto a second line
   and only then truncates, instead of growing the rail. */
html.tp-vertical .tpv-rail-label {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    max-width: 100%;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: normal;
    word-break: normal;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.2;
}

html.tp-vertical .tpv-rail-key {
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--information-color);
    opacity: 0.85;
}

/* Pushes the two destructive actions to the foot of the rail, apart from the
   rest, where a stray tap aimed at Qty or Discount cannot land on them. */
html.tp-vertical .tpv-rail-spacer {
    flex: 1 1 auto;
    min-height: 8px;
}

html.tp-vertical .tpv-rail-danger .tpv-rail-icon,
html.tp-vertical .tpv-rail-danger .tpv-rail-label {
    color: var(--accent-color);
}

html.tp-vertical .tpv-rail-danger:hover {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-color) 45%, transparent);
    background: color-mix(in srgb, var(--accent-color) 8%, var(--secondary-color));
}

/* index.js toggles d-none on .returnAmount; honour it on the rail button. */
html.tp-vertical .tpv-rail-btn.d-none {
    display: none;
}

/* ── Collapsible rail ──────────────────────────────────────────────────────

   No animation: hiding or showing the rail is instant, and the order panel
   and the menu simply take up the new space. Only the buttons' own hover
   colours still ease. */
html.tp-vertical .tpv-rail-btn,
html.tp-vertical .tpv-rail-spacer {
    transition: background 0.12s ease, border-color 0.12s ease;
}

/* The < > toggle sits on a zero-width seam between the cart and the rail. It
   rides the rail's edge as it opens and closes, and rests against the panel's
   edge once the rail is gone — it never holds any width of its own. */
html.tp-vertical .tpv-rail-seam {
    flex: 0 0 0;
    width: 0;
    position: relative;
    z-index: 6;
}

/* A translucent pull-tab at mid-height, in the cart's right gutter. Faded at
   rest so it does not compete with the order; full strength on hover or focus. */
html.tp-vertical .tpv-rail-toggle {
    position: absolute;
    top: 50%;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 52px;
    margin: 0;
    padding: 0;
    border: 1px solid color-mix(in srgb, var(--text-color) 16%, transparent);
    border-right: 0;
    border-radius: 9px 0 0 9px;
    background: color-mix(in srgb, var(--text-color) 9%, transparent);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: var(--text-color);
    opacity: 0.6;
    cursor: pointer;
    transform: translateY(-50%);
    transition: opacity 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

/* Larger hit area than it looks, reaching into the cart gutter only — never
   over the resizer on the other side. */
html.tp-vertical .tpv-rail-toggle::before {
    content: "";
    position: absolute;
    inset: -10px 0 -10px -12px;
}

html.tp-vertical .tpv-rail-toggle:hover,
html.tp-vertical .tpv-rail-toggle:focus-visible {
    opacity: 1;
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--secondary-color) 80%, transparent);
    outline: none;
}

html.tp-vertical .tpv-rail-toggle svg {
    transition: transform 0.2s ease;
}

/* Collapsed: the rail takes NO width — no strip, no border, no gap. */
html.tp-vertical.tp-rail-collapsed:not(.tp-rail-pos-top) .tpv-rail {
    flex-basis: 0;
    width: 0;
    padding: 0;
    border-left-width: 0;
    border-right-width: 0;
    overflow: hidden;
}

/* visibility, not display: the buttons stay in the DOM for their F-keys, and
   visibility:hidden also drops them out of the Tab order. */
html.tp-vertical.tp-rail-collapsed:not(.tp-rail-pos-top) .tpv-rail-btn,
html.tp-vertical.tp-rail-collapsed:not(.tp-rail-pos-top) .tpv-rail-spacer {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

html.tp-vertical.tp-rail-collapsed .tpv-rail-toggle svg {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    html.tp-vertical .tpv-rail-btn,
    html.tp-vertical .tpv-rail-spacer,
    html.tp-vertical .tpv-rail-toggle {
        transition: none !important;
    }
}

/* The arrow flips at once too. */
html.tp-vertical .tpv-rail-toggle svg {
    transition: none;
}

/* ── Action buttons position ────────────────────────────────────────────────
   tp-rail-pos-right (default) / -left / -between on <html>, set before paint by
   home.php from localStorage.tpvRailPos. The rail and its show/hide seam are
   direct children of .tpv-body, so CSS order alone places them; nothing in the
   DOM moves at runtime. The order panel gives back the rail's width
   (--tpv-rail-reserve, 0 while collapsed) so the split stays as it was. */
html.tp-vertical {
    --tpv-rail-w: 100px;
    --tpv-rail-reserve: var(--tpv-rail-w);
}

html.tp-vertical.tp-rail-collapsed {
    --tpv-rail-reserve: 0px;
}

/* Explicit order for every arrangement, so the swap-sides setting and the
   rail position combine without row-reverse. */
html.tp-vertical .tpv-body {
    flex-direction: row !important;
}

html.tp-vertical:not(.tp-cart-right).tp-rail-pos-between #container1 { order: 1; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-between .tpv-rail-seam { order: 2; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-between .tpv-rail { order: 3; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-between .tpv-resizer { order: 4; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-between #container2 { order: 5; }

html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left .tpv-rail { order: 1; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left .tpv-rail-seam { order: 2; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left #container1 { order: 3; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left .tpv-resizer { order: 4; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left #container2 { order: 5; }

html.tp-vertical:not(.tp-cart-right).tp-rail-pos-right #container1 { order: 1; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-right .tpv-resizer { order: 2; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-right #container2 { order: 3; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-right .tpv-rail-seam { order: 4; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-right .tpv-rail { order: 5; }

html.tp-vertical.tp-cart-right.tp-rail-pos-between #container2 { order: 1; }
html.tp-vertical.tp-cart-right.tp-rail-pos-between .tpv-resizer { order: 2; }
html.tp-vertical.tp-cart-right.tp-rail-pos-between .tpv-rail { order: 3; }
html.tp-vertical.tp-cart-right.tp-rail-pos-between .tpv-rail-seam { order: 4; }
html.tp-vertical.tp-cart-right.tp-rail-pos-between #container1 { order: 5; }

html.tp-vertical.tp-cart-right.tp-rail-pos-left .tpv-rail { order: 1; }
html.tp-vertical.tp-cart-right.tp-rail-pos-left .tpv-rail-seam { order: 2; }
html.tp-vertical.tp-cart-right.tp-rail-pos-left #container2 { order: 3; }
html.tp-vertical.tp-cart-right.tp-rail-pos-left .tpv-resizer { order: 4; }
html.tp-vertical.tp-cart-right.tp-rail-pos-left #container1 { order: 5; }

html.tp-vertical.tp-cart-right.tp-rail-pos-right #container2 { order: 1; }
html.tp-vertical.tp-cart-right.tp-rail-pos-right .tpv-resizer { order: 2; }
html.tp-vertical.tp-cart-right.tp-rail-pos-right #container1 { order: 3; }
html.tp-vertical.tp-cart-right.tp-rail-pos-right .tpv-rail-seam { order: 4; }
html.tp-vertical.tp-cart-right.tp-rail-pos-right .tpv-rail { order: 5; }

/* Where the rail sits LEFT of its seam, the pull-tab mirrors: it hangs to the
   right of the seam, its arrow pointing the way the rail folds. */
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left .tpv-rail-toggle,
html.tp-vertical.tp-cart-right.tp-rail-pos-left .tpv-rail-toggle,
html.tp-vertical.tp-cart-right.tp-rail-pos-between .tpv-rail-toggle {
    left: 0;
    right: auto;
    border-left: 0;
    border-right: 1px solid color-mix(in srgb, var(--text-color) 16%, transparent);
    border-radius: 0 9px 9px 0;
}

html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left .tpv-rail-toggle::before,
html.tp-vertical.tp-cart-right.tp-rail-pos-left .tpv-rail-toggle::before,
html.tp-vertical.tp-cart-right.tp-rail-pos-between .tpv-rail-toggle::before {
    inset: -10px -12px -10px 0;
}

html.tp-vertical:not(.tp-cart-right).tp-rail-pos-left .tpv-rail-toggle svg,
html.tp-vertical.tp-cart-right.tp-rail-pos-left .tpv-rail-toggle svg,
html.tp-vertical.tp-cart-right.tp-rail-pos-between .tpv-rail-toggle svg {
    transform: rotate(180deg);
}

html.tp-vertical.tp-rail-collapsed:not(.tp-cart-right).tp-rail-pos-left .tpv-rail-toggle svg,
html.tp-vertical.tp-rail-collapsed.tp-cart-right.tp-rail-pos-left .tpv-rail-toggle svg,
html.tp-vertical.tp-rail-collapsed.tp-cart-right.tp-rail-pos-between .tpv-rail-toggle svg {
    transform: rotate(0deg);
}

/* The Options menu, opened from the last action button. Fixed, and placed
   beside that button by pos-vertical.php, so the scrolling row cannot clip it. */
html.tp-vertical .tpv-railpos-menu {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1200;
    max-height: calc(100vh - 16px);
    overflow-y: auto;
    min-width: 240px;
    padding: 6px;
    border: 1px solid var(--border-color);
    border-radius: 12px;
    background: var(--secondary-color);
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.35);
}

html.tp-vertical .tpv-railpos-menu[hidden] {
    display: none;
}

html.tp-vertical .tpv-railpos-title {
    padding: 6px 10px 8px;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

html.tp-vertical .tpv-railpos-opt + .tpv-railpos-title {
    margin-top: 6px;
    padding-top: 12px;
    border-top: 1px solid var(--border-color);
}

html.tp-vertical .tpv-railpos-opt {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 42px;
    padding: 8px 10px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--text-color);
    font-family: inherit;
    font-size: 13.5px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

html.tp-vertical .tpv-railpos-opt:hover,
html.tp-vertical .tpv-railpos-opt:focus-visible {
    background: color-mix(in srgb, var(--text-color) 7%, transparent);
    outline: none;
}

html.tp-vertical .tpv-railpos-check {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border: 2px solid var(--border-color);
    border-radius: 50%;
}

/* Swap sides is an on/off setting, so a square box, not a radio. */
html.tp-vertical .tpv-railpos-swap .tpv-railpos-check {
    border-radius: 5px;
}

html.tp-vertical .tpv-railpos-opt.is-on .tpv-railpos-check {
    border-color: var(--primary-color);
    background: radial-gradient(circle, var(--primary-color) 0 4px, transparent 5px);
}

html.tp-vertical .tpv-railpos-text {
    display: flex;
    flex-direction: column;
    line-height: 1.2;
}

html.tp-vertical .tpv-railpos-text small {
    font-size: 11px;
    font-weight: 500;
    color: color-mix(in srgb, var(--text-color) 60%, transparent) !important;
}

/* ── Action buttons in the top bar ──────────────────────────────────────────
   tp-rail-pos-top (the default): tpvPlaceRail() in pos-vertical.php moves the
   whole #tpvRail into #tpvTopbarActions, so the body is just order panel,
   resizer and menu. The row costs no width, so the panel keeps the full stored
   width (reserve 0, as when the side rail is collapsed), and there is no fold
   tab — nothing to fold sideways. */
html.tp-vertical.tp-rail-pos-top {
    --tpv-rail-reserve: 0px;
}

html.tp-vertical.tp-rail-pos-top .tpv-rail-seam {
    display: none;
}

html.tp-vertical:not(.tp-cart-right).tp-rail-pos-top #container1 { order: 1; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-top .tpv-resizer { order: 2; }
html.tp-vertical:not(.tp-cart-right).tp-rail-pos-top #container2 { order: 3; }

html.tp-vertical.tp-cart-right.tp-rail-pos-top #container2 { order: 1; }
html.tp-vertical.tp-cart-right.tp-rail-pos-top .tpv-resizer { order: 2; }
html.tp-vertical.tp-cart-right.tp-rail-pos-top #container1 { order: 3; }

/* As wide as its tiles; the search sizes itself to the drag handle. When the bar
   runs out it shrinks, the row overflows, and pos-vertical.php wraps it.
   Empty in the side positions, and then out of the way. */
html.tp-vertical .tpv-topbar-actions {
    flex: 0 1 auto;
    min-width: 0;
    align-self: stretch;
    display: flex;
}

/* Any room the search does not take sits on the tiles' far side, so they
   stay against the right end (or against the search, when it is on the right). */
html.tp-vertical .tpv-topbar:not(.tpv-topbar-wrap) .tpv-topbar-actions {
    margin-left: auto;
}

html.tp-vertical .tpv-topbar-actions:empty {
    display: none;
}

/* Square tiles, every one the same size, so they read and tap alike on every
   terminal: the side rail's flat look (no gaps, no rounding, neighbours
   sharing one hairline) with a larger icon and label. The bar is exactly one
   tile tall. --tpv-tile sets the size per screen tier; a label too long for
   the square wraps (and hyphenates) onto a second line. Every tile is laid
   out the same way so the row reads evenly: icon at one height, label starting
   on one line, F-key (or the diner) on one bottom line. When the row does not
   fit, pos-vertical.php gives it a row of its own (tpv-topbar-wrap). The
   tiles sit together against the right end of the bar, in their order, so
   Options and Menu are the last two on the right. */
html.tp-vertical {
    --tpv-tile: 92px;
    --tpv-tile-pad: 10px;
    --tpv-tile-icon: 24px;
    --tpv-tile-font: 12px;
}

@media (max-height: 820px) {
    html.tp-vertical {
        --tpv-tile: 80px;
        --tpv-tile-pad: 8px;
        --tpv-tile-icon: 22px;
        --tpv-tile-font: 11.5px;
    }
}

@media (max-width: 1280px) {
    html.tp-vertical {
        --tpv-tile: 72px;
        --tpv-tile-pad: 6px;
        --tpv-tile-icon: 20px;
        --tpv-tile-font: 10.5px;
    }
}

/* 1024 wide: one row of squares still fits beside the search, without the
   F-key line (each button's tooltip still names its key). */
@media (max-width: 1100px) {
    html.tp-vertical {
        --tpv-tile: 64px;
    }

    html.tp-vertical .tpv-topbar .tpv-rail-key,
    html.tp-vertical .tpv-topbar .tpv-rail-context .tpv-context-sub {
        display: none;
    }
}

html.tp-vertical.tp-rail-pos-top .tpv-topbar:not(.tpv-topbar-wrap) {
    height: calc(var(--tpv-tile) + 1px);
}

/* The tiles run to the screen edge on their side of the search. */
html.tp-vertical.tp-rail-pos-top:not(.tp-search-right) .tpv-topbar:not(.tpv-topbar-wrap) {
    padding-right: 0;
}

html.tp-vertical.tp-rail-pos-top.tp-search-right .tpv-topbar:not(.tpv-topbar-wrap) {
    padding-left: 0;
}

/* The top bar exists only for "In the top bar": with the actions on the
   side there is no top bar at all. */
html.tp-vertical:not(.tp-rail-pos-top) .tpv-topbar {
    display: none;
}

html.tp-vertical .tpv-topbar .tpv-rail {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    flex-direction: row;
    align-items: flex-start;
    gap: 0;
    padding: 0;
    border: 0;
    background: transparent;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -ms-overflow-style: none;
    overscroll-behavior-x: contain;
    touch-action: pan-x;
    -webkit-overflow-scrolling: touch;
}

html.tp-vertical .tpv-topbar .tpv-rail::-webkit-scrollbar {
    display: none;
    width: 0;
    height: 0;
}

/* Side borders only: the bar's own bottom border closes the row. */
html.tp-vertical .tpv-topbar .tpv-rail-btn {
    flex: 0 0 var(--tpv-tile);
    justify-content: flex-start;
    width: var(--tpv-tile);
    min-width: 0;
    height: var(--tpv-tile);
    min-height: 0;
    padding: var(--tpv-tile-pad) 3px 5px;
    gap: 3px;
    border-width: 0 1px;
    border-radius: 0;
    overflow: hidden;
}

html.tp-vertical .tpv-topbar .tpv-rail-btn + .tpv-rail-btn,
html.tp-vertical .tpv-topbar .tpv-rail-spacer + .tpv-rail-btn {
    margin-top: 0;
    margin-left: -1px;
}

html.tp-vertical .tpv-topbar .tpv-rail-icon {
    flex: 0 0 auto;
    width: var(--tpv-tile-icon);
    height: var(--tpv-tile-icon);
}

html.tp-vertical .tpv-topbar .tpv-rail-icon svg {
    width: 100%;
    height: 100%;
}

html.tp-vertical .tpv-topbar .tpv-rail-label {
    -webkit-line-clamp: 2;
    max-width: 100%;
    font-size: var(--tpv-tile-font);
    line-height: 1.15;
    -webkit-hyphens: auto;
    hyphens: auto;
}

html.tp-vertical .tpv-topbar .tpv-rail-key {
    margin-top: auto;
    font-size: 9.5px;
    line-height: 1;
}

/* Right-aligned: the free room sits before the first tile. A growing
   ::before rather than justify-content: flex-end, which spills an overflowing
   row off the left edge, where it cannot be scrolled to or measured. It
   shrinks to nothing when the row is full. No gap before Delete up here. */
html.tp-vertical .tpv-topbar .tpv-rail::before {
    content: "";
    flex: 1 1 0;
    min-width: 0;
}

html.tp-vertical .tpv-topbar .tpv-rail-spacer {
    display: none;
}

/* Table / Order Type: the same square. The table wraps like any label; the
   diner (or "Tap to choose") sits on the F-key line. */
html.tp-vertical .tpv-topbar .tpv-rail-context .tpv-context-text {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    min-height: 0;
    gap: 0;
}

html.tp-vertical .tpv-topbar .tpv-rail-context .tpv-context-main {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    white-space: normal;
    font-size: var(--tpv-tile-font);
    font-weight: 600;
    line-height: 1.15;
    -webkit-hyphens: auto;
    hyphens: auto;
}

html.tp-vertical .tpv-topbar .tpv-rail-context .tpv-context-sub {
    margin-top: auto;
    font-size: 9.5px;
    line-height: 1;
}

/* Search on the right (Options menu, tpvSearchPos): the tiles first, then the
   search. The two-row layouts keep their own order. */
html.tp-vertical.tp-search-right .tpv-topbar:not(.tpv-topbar-wrap) .tpv-topbar-actions { order: 1; }
html.tp-vertical.tp-search-right .tpv-topbar:not(.tpv-topbar-wrap) .tpv-search { order: 2; }

/* ── Top bar tiles fill the bar ──────────────────────────────────────────────
   "In the top bar" only. The search already follows the drag handle
   (syncSearch in pos-vertical.php), so the tiles start where the panels below
   split and share the rest of the bar evenly out to the screen edge, with no
   empty run before the first tile (when the tiles need more room the search
   still gives way first). Icon, label and F-key sit together. The top inset
   centres a one-line tile but never leaves less room than a two-line label
   needs, so every icon stays on one line whether a label wraps or a tile has
   no F-key. The squares stay the minimum size, so the
   fit/wrap check is unchanged. */
html.tp-vertical.tp-rail-pos-top .tpv-topbar:not(.tpv-topbar-wrap) .tpv-topbar-actions {
    flex: 1 1 auto;
    margin-left: 0;
}

/* Meet the drag handle's hairline instead of sitting 1px beside it. */
html.tp-vertical.tp-rail-pos-top:not(.tp-search-right):not(.tp-cart-right) .tpv-topbar:not(.tpv-topbar-wrap) .tpv-topbar-actions {
    margin-left: -1px;
}

html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-rail::before {
    display: none;
}

html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-rail-btn {
    flex: 1 0 var(--tpv-tile);
    width: auto;
    justify-content: flex-start;
    gap: 4px;
    padding: max(4px, min(calc((var(--tpv-tile) - var(--tpv-tile-icon) - var(--tpv-tile-font) * 1.15 - 16.5px) / 2),
                          calc(var(--tpv-tile) - 4px - var(--tpv-tile-icon) - var(--tpv-tile-font) * 2.3 - 16.5px))) 3px 4px;
}

html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-rail-key,
html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-rail-context .tpv-context-sub {
    margin-top: -1px;
}

html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-rail-context .tpv-context-text {
    flex: 0 0 auto;
    gap: 4px;
}

/* No F-key line on these tiers, so the inset centres icon + label only. */
@media (max-width: 1100px), (max-height: 500px) {
    html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-rail-btn {
        padding-top: max(4px, min(calc((var(--tpv-tile) - var(--tpv-tile-icon) - var(--tpv-tile-font) * 1.15 - 4px) / 2),
                                  calc(var(--tpv-tile) - 8px - var(--tpv-tile-icon) - var(--tpv-tile-font) * 2.3)));
    }
}

html.tp-vertical.tp-rail-pos-top .tpv-topbar .tpv-search .search_input {
    height: clamp(40px, calc(var(--tpv-tile) * 0.52), 48px) !important;
}

/* ── Arranging the action buttons ───────────────────────────────────────────
   Hold a button 3s (a fill rises while held), then drag it; pos-vertical.php
   does the moving and saves tpvRailOrder. No text selection or long-press
   callout on the buttons, so a held finger never selects a label. */
html.tp-vertical .tpv-rail-btn {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

html.tp-vertical .tpv-rail-btn.is-holding::after {
    content: "";
    position: absolute;
    inset: 0;
    background: color-mix(in srgb, var(--primary-color) 22%, transparent);
    transform-origin: bottom;
    animation: tpvHoldFill 3s linear forwards;
    pointer-events: none;
}

@keyframes tpvHoldFill {
    from { transform: scaleY(0); }
    to   { transform: scaleY(1); }
}

html.tp-vertical .tpv-rail-btn.is-dragging {
    z-index: 4;
    transform: scale(1.04);
    background: var(--secondary-color);
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35), inset 0 0 0 2px var(--primary-color);
    cursor: grabbing;
}

html.tp-vertical .tpv-rail.is-arranging .tpv-rail-btn:not(.is-dragging) {
    opacity: 0.8;
}

/* Just dropped: stays above its neighbours while it glides into its slot. */
html.tp-vertical .tpv-rail-btn.is-settling {
    z-index: 4;
}

html.tp-vertical .tpv-railpos-foot {
    margin-top: 6px;
    padding: 8px 10px 4px;
    border-top: 1px solid var(--border-color);
}

html.tp-vertical .tpv-railpos-hint {
    margin: 0 0 8px;
    font-size: 11.5px;
    line-height: 1.35;
    color: var(--information-color);
}

html.tp-vertical .tpv-railpos-reset {
    width: 100%;
    min-height: 36px;
    border: 1px solid var(--border-color);
    border-radius: 9px;
    background: transparent;
    color: var(--text-color);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
}

html.tp-vertical .tpv-railpos-reset:hover,
html.tp-vertical .tpv-railpos-reset:focus-visible {
    border-color: var(--primary-color);
    outline: none;
}

/* ── Product grid (#container2) ──────────────────────────────────────────── */

html.tp-vertical #container2 {
    flex: 1 1 auto !important;
    width: auto !important;
    min-width: 0 !important;
    height: auto !important;
    display: flex !important;
    flex-direction: column !important;
    flex-wrap: nowrap !important;
    flex-shrink: 1 !important;
    overflow-y: hidden !important;
    padding: 0 !important;
    border-top: 0 !important;
    background: var(--background-color) !important;
    color: var(--text-color) !important;
}

html.tp-vertical .tpv-grid-head {
    flex: 0 0 auto;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 0px 0px 10px;
}

html.tp-vertical .tpv-grid-path {
    display: flex;
    align-items: baseline;
    min-width: 0;
    overflow: hidden;
}

html.tp-vertical .tpv-grid-title {
    flex: 0 0 auto;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--info-val-color);
}

/* Where the cashier is: one crumb per category opened, the title being the root.
   Ancestors are buttons that jump back to that level; the last one is the
   current category and does nothing. */
html.tp-vertical .tpv-crumbs {
    display: flex;
    align-items: baseline;
    min-width: 0;
    overflow: hidden;
}

html.tp-vertical .tpv-crumb {
    flex: 0 1 auto;
    max-width: 180px;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: var(--information-color);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 500;
    line-height: inherit;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

html.tp-vertical .tpv-crumb::before {
    content: "›";
    margin: 0 6px;
    color: var(--information-color);
    opacity: 0.7;
}

html.tp-vertical .tpv-crumb:hover,
html.tp-vertical .tpv-crumb:focus-visible {
    outline: none;
    color: var(--primary-ink, var(--primary-color));
    text-decoration: underline;
}

html.tp-vertical .tpv-crumb.is-current {
    flex-shrink: 0;
    color: var(--info-val-color);
    font-weight: 600;
    cursor: default;
    text-decoration: none;
}

/* Explicit colour and opacity: a bare --information-color span was picking up an
   accent from the older global sheets and reading as a link. */
html.tp-vertical .tpv-grid-hint {
    font-size: 10.5px;
    font-weight: 400;
    color: var(--information-color) !important;
    opacity: 0.65;
    text-decoration: none;
}

/* Price-list chips (price-list.js): Regular + one per Management list. One row
   that scrolls sideways, so a long list never pushes the grid down. */
html.tp-vertical .tpv-pricelist {
    flex: 0 0 auto;
    display: flex;
    gap: 6px;
    padding: 0 20px 10px;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

html.tp-vertical .tpv-pricelist[hidden] {
    display: none;
}

html.tp-vertical .tpv-pricelist::-webkit-scrollbar {
    display: none;
}

html.tp-vertical .tpv-pl-chip {
    flex: 0 0 auto;
    max-width: 200px;
    margin: 0;
    padding: 6px 14px;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background: transparent;
    box-shadow: none;
    color: var(--text-color);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

html.tp-vertical .tpv-pl-chip:hover,
html.tp-vertical .tpv-pl-chip:focus-visible {
    outline: none;
    border-color: var(--primary-color);
    color: var(--primary-ink, var(--primary-color));
}

html.tp-vertical .tpv-pl-chip.is-active {
    border-color: var(--primary-color);
    background: var(--primary-color);
    color: var(--on-primary, #fff);
    font-weight: 600;
}

html.tp-vertical .tpv-pl-empty {
    grid-column: 1 / -1;
    padding: 40px 12px;
    text-align: center;
    color: var(--information-color);
    font-size: 13px;
}

html.tp-vertical .products-storage {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    height: auto !important;
    max-height: none !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
    padding: 0 20px 18px !important;
    width: 100% !important;
}

html.tp-vertical .products-storage::-webkit-scrollbar {
    width: 8px;
}

html.tp-vertical .products-storage::-webkit-scrollbar-thumb {
    background: var(--border-color);
    border-radius: 4px;
}

/* Overrides resto.css, which sizes these for the old visualDisplay.php shell.

   display:block matters: resto.css makes this a centred flex ROW. With the
   grid as its only child that was harmless, but the loading placeholder is a
   second child, so the two sat side by side competing for the width — the
   placeholder was squeezed to a single column and centred off the left edge.
   As a block, placeholder and grid stack, each at full width. */
html.tp-vertical .product-list-container {
    display: block !important;
    width: 100%;
    height: auto;
    padding: 0;
    margin: 0;
    background: transparent;
    border: 0;
}

html.tp-vertical .product-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 11px;
    width: 100%;
    height: auto;
    padding: 0;
    margin: 0;
    align-content: start;
}

/* One neutral surface for every tile. An operator-set colour still wins because
   visualDisplay.js writes it inline. */
/* Tiles sit on --secondary-color, one step off the --background-color ground, so
   they read as cards in BOTH themes. The earlier 4%-of-text tint computed to
   rgba(0,0,0,0.04) in light mode — invisible on #F1F1F4. An operator-set colour
   still wins, because visualDisplay.js writes it inline. */
html.tp-vertical .product-tile {
    height: 116px;
    min-height: 116px;
    width: auto;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: stretch;
    text-align: left;
    padding: 14px;
    margin: 0;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--secondary-color);
    color: var(--text-color);
    cursor: pointer;
    overflow: hidden;
}

/* Tells the cashier the catalogue is empty rather than showing a blank slab. */
html.tp-vertical .product-grid:empty::after {
    content: "No items to show. Add products and categories in Management.";
    grid-column: 1 / -1;
    display: block;
    padding: 64px 16px;
    text-align: center;
    font-size: 12.5px;
    color: var(--information-color);
    opacity: 0.8;
}

/* A level change (visualDisplay.js tpMenuOpen) keeps the current tiles until the
   next set lands; they stop taking taps meanwhile. */
html.tp-vertical .product-grid[aria-busy="true"] {
    pointer-events: none;
}

/* Infinite scroll: shown under the tiles while the next page is fetched. */
html.tp-vertical .tpv-grid-more {
    padding: 14px 16px 18px;
    text-align: center;
    font-size: 12.5px;
    color: var(--information-color);
    opacity: 0.8;
}

html.tp-vertical .tpv-grid-more[hidden] {
    display: none;
}

html.tp-vertical .product-tile:hover {
    border-color: var(--primary-color);
}

html.tp-vertical .product-tile:active {
    transform: scale(0.96);
}

/* ── Add-to-cart feedback ────────────────────────────────────────────────── */

/* The tapped tile flashes to the accent, so the tap is acknowledged even if the
   ghost is missed or motion is switched off. */
html.tp-vertical .product-tile.tpv-tile-added {
    border-color: var(--primary-color);
    box-shadow: inset 0 0 0 2px var(--primary-color);
    transition: box-shadow 0.12s ease, border-color 0.12s ease;
}

/* The ghost that travels to the order panel. Fixed-position and
   pointer-events:none so it can never intercept the next tap. */
.tpv-fly {
    position: fixed;
    z-index: 99999;
    pointer-events: none;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 10px;
    border: 1px solid var(--primary-color);
    border-radius: 10px;
    background: var(--secondary-color);
    color: var(--text-color);
    font-family: var(--font-family);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.25;
    text-align: center;
    overflow: hidden;
    opacity: 0.95;
    transform: translate(0, 0) scale(1);
    will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce) {
    .tpv-fly { display: none !important; }
    html.tp-vertical .product-tile:active { transform: none; }
}

/* The tile's text block is one .label-prod-name holding two spans: the product
   name, then the price. */
html.tp-vertical .product-tile .label-prod-name {
    display: flex !important;
    flex-direction: column;
    justify-content: space-between;
    flex: 1 1 auto;
    gap: 6px;
    width: 100%;
    margin: 0 !important;
    overflow: hidden;
}

html.tp-vertical .product-tile .label-prod-name > span:first-child {
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.3;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
}

/* The price is inline-styled to --primary-color in visualDisplay.js. With the
   Payment button already solid accent, an accent price on all 1,500 tiles left
   nothing for the accent to mean, so it is overridden to a plain value colour.
   Accent is reserved for the thing you are meant to press. */
html.tp-vertical .product-tile .label-prod-name > span:last-child {
    color: var(--info-val-color) !important;
    font-size: 14px;
    font-weight: 700;
    white-space: nowrap;
}

html.tp-vertical .product-tile .product_img_container {
    max-height: 46px;
    min-height: 0;
    max-width: 100%;
    min-width: 0;
    width: auto;
    margin: 0 auto;
    flex: 0 0 auto;
}

html.tp-vertical .product-tile .productImage {
    max-height: 46px;
    width: auto;
    max-width: 100%;
    object-fit: contain;
}

html.tp-vertical .product-tile .price-badge {
    position: static;
    background: none;
    padding: 0;
}

/* ── Menu tile ─────────────────────────────────────────────────────────────
   A framed photo on top (the item's initials when there is none), the full
   product name under it, then PRICE. Built by
   tpBuildProductTile() in visualDisplay.js. The name is never clamped, so a
   long one makes its row taller instead of being cut off; the folder, GROUPS
   and BACK tiles in the same row stretch to match. */
html.tp-vertical .product-grid > .product-tile {
    height: auto;
}

html.tp-vertical .product-grid > .product-tile.tp-menu-tile {
    min-height: 0;
    justify-content: flex-start;
    gap: 9px;
    padding: 8px;
    border-radius: 12px;
    text-align: left;
    container-type: inline-size;
    container-name: tpmenutile;
    position: relative;
    isolation: isolate;
}

/* The media box is only a spacer now: it keeps the tile at its original
   height, while the photo inside it is anchored to the whole tile. */
html.tp-vertical .tp-menu-tile .tp-tile-media {
    position: static;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 16 / 10;
    border: 1px solid transparent;
}

/* Full-bleed photo: covers the entire tile, cropped rather than letterboxed. */
html.tp-vertical .tp-menu-tile .tp-tile-media .product_img_container {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: block;
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    overflow: hidden;
    border-radius: inherit;
}

html.tp-vertical .tp-menu-tile .tp-tile-media .product_img_container.d-none {
    display: none !important;
}

html.tp-vertical .tp-menu-tile .tp-tile-media .productImage {
    display: block;
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    object-fit: cover;
    object-position: center;
    transition: transform 0.25s ease;
}

html.tp-vertical .tp-menu-tile:hover .tp-tile-media .productImage {
    transform: scale(1.05);
}

/* A fade in the tile's own colour (theme surface, or its picked colour) behind
   the name and price, so the existing text colours read on any photo. It
   tracks the text block, so a long name extends it. */
html.tp-vertical .tp-menu-tile:has(.product_img_container:not(.d-none)) .tp-tile-body::before {
    content: "";
    position: absolute;
    z-index: -1;
    left: -9px;
    right: -9px;
    bottom: -9px;
    top: -30px;
    background: linear-gradient(to top,
        color-mix(in srgb, var(--tile-surface, var(--secondary-color)) 92%, transparent) 0%,
        color-mix(in srgb, var(--tile-surface, var(--secondary-color)) 82%, transparent) calc(100% - 30px),
        transparent 100%);
    pointer-events: none;
}

html.tp-vertical .tp-menu-tile .tp-tile-initials {
    font-size: 22px;
    font-weight: 800;
    letter-spacing: 0.06em;
    color: color-mix(in srgb, var(--text-color) 36%, transparent);
    user-select: none;
}

html.tp-vertical .tp-menu-tile .tp-tile-media:has(.product_img_container:not(.d-none)) .tp-tile-initials {
    display: none;
}

html.tp-vertical .product-tile.tp-menu-tile .label-prod-name.tp-tile-body {
    display: flex !important;
    flex-direction: column;
    justify-content: space-between;
    flex: 1 1 auto;
    gap: 8px;
    padding: 0 3px 1px;
    overflow: visible;
    position: relative;
    z-index: 1;
}

/* The full name, always: wraps as far as it needs to and is never clamped. */
html.tp-vertical .product-tile.tp-menu-tile .label-prod-name > .tp-tile-name {
    display: block;
    -webkit-line-clamp: unset;
    overflow: visible;
    min-height: 2.6em;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
    text-align: left;
}

/* 34px is the row height the removed add button gave it; keeps the tile size. */
html.tp-vertical .tp-menu-tile .tp-tile-foot {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 8px;
    min-height: 34px;
    margin-top: auto;
}

html.tp-vertical .tp-menu-tile .tp-tile-price {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.15;
}

html.tp-vertical .tp-menu-tile .tp-tile-price-cap {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--information-color);
}

html.tp-vertical .tp-menu-tile .tp-tile-price-val {
    font-size: clamp(12px, 8.6cqi, 15.5px);
    font-weight: 800;
    color: var(--info-val-color);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Folder, GROUPS and BACK tiles: the same size, corners and padding as a menu
   tile, with the icon and label centred. Two invisible spacers carry a menu
   tile's proportions — ::before the photo frame, ::after the name and price
   rows — so a row of folders is exactly as tall as a row of items, and in a
   mixed row the folder simply stretches with it. */
html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile) {
    position: relative;
    min-height: 0;
    justify-content: flex-start;
    gap: 0;
    padding: 8px;
    border-radius: 12px;
}

html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile)::before {
    content: "";
    display: block;
    width: 100%;
    aspect-ratio: 16 / 10;
}

/* 9 gap + 33.8 name (2 lines of 13px/1.3) + 8 gap + 34 price row + 1 padding */
html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile)::after {
    content: "";
    display: block;
    height: 85.8px;
}

html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile) > .label-prod-name {
    position: absolute;
    inset: 0;
    display: flex !important;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: auto !important;
    margin: 0 !important;
    padding: 10px;
    text-align: center;
    overflow: hidden;
}

/* The label, not a price: wraps, and reads in the tile's own text colour. */
html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile) > .label-prod-name > span {
    display: block;
    width: 100%;
    color: inherit !important;
    font-size: 13.5px;
    font-weight: 700;
    line-height: 1.3;
    white-space: normal;
    overflow-wrap: anywhere;
    -webkit-line-clamp: unset;
}

html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile) svg,
html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile) .tp-cat-icon {
    width: 38px;
    height: 38px;
    font-size: 38px;
    flex: 0 0 auto;
}

html.tp-vertical .product-grid > .product-tile:not(.tp-menu-tile) .folder-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

/* A tile with its own colour carries --tile-ink (dark or light, picked by
   tpApplyTileInk() from the colour's brightness), so the name and price
   stay readable on any colour in either theme. */
html.tp-vertical .product-tile.tp-tile-inked,
html.tp-vertical .product-tile.tp-tile-inked .label-prod-name,
html.tp-vertical .product-tile.tp-tile-inked .label-prod-name > span {
    color: var(--tile-ink) !important;
}

html.tp-vertical .tp-menu-tile.tp-tile-inked .tp-tile-price-val {
    color: var(--tile-ink) !important;
}

html.tp-vertical .tp-menu-tile.tp-tile-inked .tp-tile-price-cap,
html.tp-vertical .tp-menu-tile.tp-tile-inked .tp-tile-initials {
    color: color-mix(in srgb, var(--tile-ink) 62%, transparent);
}


/* ── Loading skeletons ──────────────────────────────────────────────────── */

/* Shown only while html.tpv-grid-loading / html.tpv-cart-loading are set.
   home.php sets both before the body is parsed; the request watcher in
   layout/pos-vertical.php clears each when its request has been answered. */
html.tp-vertical .tpv-grid-skeleton,
html.tp-vertical .tpv-cart-skeleton {
    display: none;
}

/* The panels are visibility:hidden in their markup until a DOMContentLoaded
   handler reveals them — a blank screen for the whole first moment. The
   vertical layout needs no width measuring before it can show, so it shows at
   once, and the skeletons are what fill that moment instead. */
html.tp-vertical #container1,
html.tp-vertical #container2 {
    visibility: visible !important;
}

/* One shimmer for every placeholder, derived from the text colour so it reads
   correctly in both themes without a hardcoded grey. */
html.tp-vertical .tpv-skel,
html.tp-vertical.tpv-cart-loading .tpv-total-row > span:last-child,
html.tp-vertical.tpv-cart-loading .tpv-grand-value,
html.tp-vertical .tpv-clock .pos-clock-date:empty,
html.tp-vertical .tpv-clock .pos-clock-time:empty {
    border-radius: 6px;
    background-image: linear-gradient(90deg,
            color-mix(in srgb, var(--text-color) 7%, transparent) 0%,
            color-mix(in srgb, var(--text-color) 15%, transparent) 50%,
            color-mix(in srgb, var(--text-color) 7%, transparent) 100%);
    background-size: 220% 100%;
    animation: tpvShimmer 1.35s ease-in-out infinite;
}

@keyframes tpvShimmer {
    from { background-position: 120% 0; }
    to   { background-position: -120% 0; }
}

html.tp-vertical .tpv-skel {
    display: block;
}

/* ── Grid placeholder ── keep in step with .product-grid / .product-tile ── */

html.tp-vertical.tpv-grid-loading .tpv-grid-skeleton {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 11px;
    width: 100%;
}

/* The skeleton only stands in for an empty grid (the first load), so the
   "No items to show" message must not appear under it. */
html.tp-vertical.tpv-grid-loading .product-grid:empty::after {
    content: none;
}

html.tp-vertical .tpv-skel-tile {
    height: 116px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--secondary-color);
}

html.tp-vertical .tpv-skel-tile .tpv-skel-line {
    height: 11px;
}

html.tp-vertical .tpv-skel-price {
    width: 42%;
    height: 14px;
    margin-top: auto;
}

/* Same shape as a menu tile: photo frame, name, price row. */
html.tp-vertical.tpv-grid-loading .tpv-grid-skeleton .tpv-skel-tile {
    height: auto;
    min-height: 0;
    padding: 8px 8px 12px;
    border-radius: 12px;
}

html.tp-vertical .tpv-skel-tile .tpv-skel-media {
    width: 100%;
    aspect-ratio: 16 / 10;
    margin-bottom: 2px;
    border-radius: 9px;
}

html.tp-vertical .tpv-skel-tile .tpv-skel-price {
    height: 22px;
    margin-top: 10px;
}

/* ── Cart placeholder ─────────────────────────────────────────────────────── */

html.tp-vertical.tpv-cart-loading .tpv-cart-skeleton {
    display: block;
}

/* !important: the show rule carries an id inside :has(), so it out-ranks this. */
html.tp-vertical.tpv-cart-loading .tpv-cart-empty {
    display: none !important;
}

html.tp-vertical .tpv-skel-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 13px 4px;
    border-bottom: 1px solid var(--hairline);
}

html.tp-vertical .tpv-skel-col {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 7px;
}

html.tp-vertical .tpv-skel-row .tpv-skel-line {
    height: 12px;
}

html.tp-vertical .tpv-skel-sub {
    width: 38%;
    height: 9px;
}

html.tp-vertical .tpv-skel-num {
    flex: 0 0 auto;
    width: 30px;
    height: 12px;
}

html.tp-vertical .tpv-skel-num-wide {
    width: 54px;
}

/* ── Totals ────────────────────────────────────────────────────────────────
   Before the cart arrives the totals read "₱ 0.00" — a real-looking figure
   that is not true yet, and that then jumps to the actual total. Hidden under
   the same shimmer until the cart has loaded. */
html.tp-vertical.tpv-cart-loading .tpv-total-row > span:last-child,
html.tp-vertical.tpv-cart-loading .tpv-grand-value {
    color: transparent !important;
    min-width: 64px;
}

/* The grand total is flex:1 so a long figure can right-align; left alone, its
   placeholder stretched across the entire row. Shaped like a number instead,
   still pinned to the right where the real total will land. */
html.tp-vertical.tpv-cart-loading .tpv-grand-value {
    flex: 0 0 auto;
    width: 150px;
    margin-left: auto;
}

html.tp-vertical.tpv-cart-loading .tpv-total-row > span:last-child *,
html.tp-vertical.tpv-cart-loading .tpv-grand-value * {
    color: transparent !important;
}

html.tp-vertical.tpv-cart-loading .tpv-pay-total {
    visibility: hidden;
}

/* ── Top bar clock ──────────────────────────────────────────────────────────
   footer.php fills the date and time a moment after load; until then the
   empty spans hold their place. */
html.tp-vertical .tpv-clock .pos-clock-date:empty,
html.tp-vertical .tpv-clock .pos-clock-time:empty {
    display: inline-block;
    height: 11px;
}

html.tp-vertical .tpv-clock .pos-clock-date:empty { width: 66px; }
html.tp-vertical .tpv-clock .pos-clock-time:empty { width: 54px; }

@media (prefers-reduced-motion: reduce) {
    html.tp-vertical .tpv-skel,
    html.tp-vertical.tpv-cart-loading .tpv-total-row > span:last-child,
    html.tp-vertical.tpv-cart-loading .tpv-grand-value,
    html.tp-vertical .tpv-clock .pos-clock-date:empty,
    html.tp-vertical .tpv-clock .pos-clock-time:empty {
        animation: none;
        background-position: 50% 0;
    }
}

/* ── Whole-page skeleton ─────────────────────────────────────────────────────
   While html.tpv-page-loading is set, every piece of chrome renders as a
   placeholder in its own exact shape: the element keeps its box, its content
   is hidden, and the shared shimmer fills it. Nothing leaves the layout, so the
   page does not shift when it reveals.

   Pointer input is off while loading, so nothing half-loaded can be tapped —
   no Payment before the cart exists. Keyboard shortcuts are unaffected: the
   F-key handler in footer.php clicks these buttons by id, as before.

   Cleared once, by the request watcher in layout/pos-vertical.php. */
html.tp-vertical.tpv-page-loading .tpv-context,
html.tp-vertical.tpv-page-loading .tpv-search,
html.tp-vertical.tpv-page-loading .tpv-status,
html.tp-vertical.tpv-page-loading .tpv-user,
html.tp-vertical.tpv-page-loading .tpv-clock,
html.tp-vertical.tpv-page-loading .tpv-icon-btn,
html.tp-vertical.tpv-page-loading .tpv-customer-chip,
html.tp-vertical.tpv-page-loading .tpv-order-title,
html.tp-vertical.tpv-page-loading .tpv-total-row > span:first-child,
html.tp-vertical.tpv-page-loading .tpv-total-grand > span:first-child,
html.tp-vertical.tpv-page-loading .tpv-btn-send,
html.tp-vertical.tpv-page-loading .tpv-btn-pay,
html.tp-vertical.tpv-page-loading .tpv-rail-btn,
html.tp-vertical.tpv-page-loading .tpv-rail-toggle,
html.tp-vertical.tpv-page-loading .tpv-grid-title,
html.tp-vertical.tpv-page-loading .tpv-grid-hint {
    color: transparent !important;
    border-color: transparent !important;
    background-color: transparent !important;
    background-image: linear-gradient(90deg,
            color-mix(in srgb, var(--text-color) 7%, transparent) 0%,
            color-mix(in srgb, var(--text-color) 15%, transparent) 50%,
            color-mix(in srgb, var(--text-color) 7%, transparent) 100%) !important;
    background-size: 220% 100% !important;
    box-shadow: none !important;
    text-shadow: none !important;
    animation: tpvShimmer 1.35s ease-in-out infinite;
    pointer-events: none;
}

html.tp-vertical.tpv-page-loading .tpv-order-title,
html.tp-vertical.tpv-page-loading .tpv-total-row > span:first-child,
html.tp-vertical.tpv-page-loading .tpv-total-grand > span:first-child,
html.tp-vertical.tpv-page-loading .tpv-grid-title,
html.tp-vertical.tpv-page-loading .tpv-grid-hint,
html.tp-vertical.tpv-page-loading .tpv-status,
html.tp-vertical.tpv-page-loading .tpv-user,
html.tp-vertical.tpv-page-loading .tpv-clock {
    border-radius: 6px;
}

/* Square, like the side-rail tiles themselves; rounded in the top bar. */
html.tp-vertical.tpv-page-loading .tpv-body .tpv-rail-btn {
    border-radius: 0;
}

/* Icons, badges and inner text disappear; the box that holds them stays. */
html.tp-vertical.tpv-page-loading .tpv-context *,
html.tp-vertical.tpv-page-loading .tpv-status *,
html.tp-vertical.tpv-page-loading .tpv-clock *,
html.tp-vertical.tpv-page-loading .tpv-icon-btn *,
html.tp-vertical.tpv-page-loading .tpv-customer-chip *,
html.tp-vertical.tpv-page-loading .tpv-btn-send *,
html.tp-vertical.tpv-page-loading .tpv-btn-pay *,
html.tp-vertical.tpv-page-loading .tpv-rail-btn *,
html.tp-vertical.tpv-page-loading .tpv-rail-toggle * {
    visibility: hidden !important;
}

/* The search field LOOKS loading but stays alive. index.js focuses it on load
   and a barcode scanner types straight into it, so hiding it would drop a scan
   made in the first moment. Its icons go; the field keeps taking input, which
   simply appears when the page reveals. */
html.tp-vertical.tpv-page-loading .tpv-search {
    border-radius: 10px;
}

html.tp-vertical.tpv-page-loading .tpv-search > :not(.search_input) {
    visibility: hidden !important;
}

html.tp-vertical.tpv-page-loading .tpv-search .search_input {
    background: transparent !important;
    border-color: transparent !important;
    box-shadow: none !important;
    color: transparent !important;
    caret-color: transparent;
}

html.tp-vertical.tpv-page-loading .tpv-search .search_input::placeholder {
    color: transparent !important;
}

@media (prefers-reduced-motion: reduce) {
    html.tp-vertical.tpv-page-loading .tpv-context,
    html.tp-vertical.tpv-page-loading .tpv-search,
    html.tp-vertical.tpv-page-loading .tpv-status,
    html.tp-vertical.tpv-page-loading .tpv-user,
    html.tp-vertical.tpv-page-loading .tpv-clock,
    html.tp-vertical.tpv-page-loading .tpv-icon-btn,
    html.tp-vertical.tpv-page-loading .tpv-customer-chip,
    html.tp-vertical.tpv-page-loading .tpv-order-title,
    html.tp-vertical.tpv-page-loading .tpv-total-row > span:first-child,
    html.tp-vertical.tpv-page-loading .tpv-total-grand > span:first-child,
    html.tp-vertical.tpv-page-loading .tpv-btn-send,
    html.tp-vertical.tpv-page-loading .tpv-btn-pay,
    html.tp-vertical.tpv-page-loading .tpv-rail-btn,
    html.tp-vertical.tpv-page-loading .tpv-rail-toggle,
    html.tp-vertical.tpv-page-loading .tpv-grid-title,
    html.tp-vertical.tpv-page-loading .tpv-grid-hint {
        animation: none !important;
        background-position: 50% 0 !important;
    }
}

/* ── Tile customiser (#colorPopup) ──────────────────────────────────────── */

/* Right-click a tile to open it. Fixed rather than absolute: the layout shell
   clips its overflow, and viewport coordinates hold wherever the popup sits in
   the DOM. visualDisplay.js places it and picks the arrow corner. */
html.tp-vertical #colorPopup {
    display: none;
    position: fixed;
    z-index: 1000;
    width: 264px;
    padding: 0;
    border: 0;
    background: transparent;
    filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.24));
    transform-origin: 22px 0;
    animation: tpvPopIn 0.14s ease-out;
}

html.tp-vertical #colorPopup.arrow-tr { transform-origin: calc(100% - 22px) 0; }
html.tp-vertical #colorPopup.arrow-bl { transform-origin: 22px 100%; }
html.tp-vertical #colorPopup.arrow-br { transform-origin: calc(100% - 22px) 100%; }

/* The arrow points at the tile that was clicked. */
html.tp-vertical #colorPopup::before {
    content: "";
    position: absolute;
    width: 0;
    height: 0;
    border: 10px solid transparent;
}

html.tp-vertical #colorPopup.arrow-tl::before,
html.tp-vertical #colorPopup.arrow-tr::before {
    top: -10px;
    border-top-width: 0;
    border-bottom-color: var(--primary-color);
}

html.tp-vertical #colorPopup.arrow-bl::before,
html.tp-vertical #colorPopup.arrow-br::before {
    bottom: -10px;
    border-bottom-width: 0;
    border-top-color: var(--primary-color);
}

html.tp-vertical #colorPopup.arrow-tl::before,
html.tp-vertical #colorPopup.arrow-bl::before { left: 12px; }

html.tp-vertical #colorPopup.arrow-tr::before,
html.tp-vertical #colorPopup.arrow-br::before { right: 12px; }

html.tp-vertical #colorPopup .color-popup-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 14px 16px 16px;
    border: 1.5px solid var(--primary-color);
    border-radius: 12px;
    background: var(--secondary-color);
    color: var(--text-color);
}

html.tp-vertical #colorPopup .cp-title {
    margin: 0;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border-color);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--primary-ink, var(--primary-color));
}

html.tp-vertical #colorPopup .cp-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

html.tp-vertical #colorPopup .cp-label,
html.tp-vertical #colorPopup .cp-file-label {
    margin: 0;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--information-color);
}

html.tp-vertical #colorPopup input.backolor {
    flex: 0 0 auto;
    width: 58px;
    height: 34px;
    margin: 0;
    padding: 3px;
    border: 1px solid color-mix(in srgb, var(--text-color) 26%, transparent);
    border-radius: 8px;
    background: var(--background-color);
    cursor: pointer;
}

html.tp-vertical #colorPopup input.backolor::-webkit-color-swatch-wrapper {
    padding: 0;
}

html.tp-vertical #colorPopup input.backolor::-webkit-color-swatch {
    border: 0;
    border-radius: 5px;
}

html.tp-vertical #colorPopup input.backolor:focus-visible {
    outline: none;
    border-color: var(--primary-color);
}

html.tp-vertical #colorPopup .cp-file-row {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

html.tp-vertical #colorPopup .cp-file-btn {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 9px 12px;
    border: 1px dashed color-mix(in srgb, var(--text-color) 30%, transparent);
    border-radius: 8px;
    background: color-mix(in srgb, var(--text-color) 6%, transparent);
    color: var(--text-color);
    font-size: 12.5px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

html.tp-vertical #colorPopup .cp-file-btn:hover {
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--text-color) 10%, transparent);
}

html.tp-vertical #colorPopup #imgPreview {
    width: 100%;
    max-height: 90px;
    object-fit: contain;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: var(--background-color);
}

/* Category icon picker: Iconify search plus a grid of built-in food icons. */
html.tp-vertical #colorPopup .cp-icon-row {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

html.tp-vertical #colorPopup .cp-icon-search {
    display: flex;
    gap: 8px;
}

html.tp-vertical #colorPopup .cp-icon-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 36px;
    margin: 0;
    padding: 0 10px;
    border: 1px solid color-mix(in srgb, var(--text-color) 22%, transparent);
    border-radius: 8px;
    background: color-mix(in srgb, var(--text-color) 6%, var(--secondary-color));
    color: var(--text-color);
    font-family: inherit;
    font-size: 12.5px;
    outline: none;
}

html.tp-vertical #colorPopup .cp-icon-input::placeholder {
    color: var(--information-color);
    opacity: 0.8;
}

html.tp-vertical #colorPopup .cp-icon-input:focus {
    border-color: var(--primary-color);
}

html.tp-vertical #colorPopup .cp-icon-find {
    flex: 0 0 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    margin: 0;
    padding: 0;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    background: color-mix(in srgb, var(--text-color) 6%, var(--secondary-color));
    color: var(--text-color);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
    cursor: pointer;
}

html.tp-vertical #colorPopup .cp-icon-find:hover {
    border-color: color-mix(in srgb, var(--text-color) 24%, transparent);
    background: color-mix(in srgb, var(--text-color) 11%, var(--secondary-color));
}

html.tp-vertical #colorPopup .cp-icon-find:focus-visible {
    outline: none;
    border-color: var(--primary-color);
}

html.tp-vertical #colorPopup .cp-icon-hint {
    margin: -2px 0 0;
    font-size: 11px;
    color: var(--information-color);
}

html.tp-vertical #colorPopup .cp-icon-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    grid-auto-rows: 30px;
    gap: 7px;
    height: 164px;
    padding: 8px;
    border: 1px solid color-mix(in srgb, var(--text-color) 22%, transparent);
    border-radius: 8px;
    background: color-mix(in srgb, var(--text-color) 4%, var(--secondary-color));
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
}

html.tp-vertical #colorPopup .cp-icon-cell {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 30px;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    box-shadow: none;
    color: var(--text-color);
    font-size: 22px;
    cursor: pointer;
}

html.tp-vertical #colorPopup .cp-icon-cell:hover {
    background: color-mix(in srgb, var(--text-color) 10%, transparent);
}

html.tp-vertical #colorPopup .cp-icon-cell:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: -2px;
}

html.tp-vertical #colorPopup .cp-icon-cell.is-selected {
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

html.tp-vertical #colorPopup .cp-icon-msg {
    grid-column: 1 / -1;
    align-self: center;
    margin: 0;
    font-size: 11.5px;
    text-align: center;
    color: var(--information-color);
}

/* A chosen icon replaces the folder glyph on the category tile. */
html.tp-vertical .categoryClass .tp-cat-icon {
    display: inline-block;
    font-size: 32px;
    line-height: 1;
}

html.tp-vertical #colorPopup .cp-actions {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    padding-top: 12px;
    border-top: 1px solid var(--border-color);
}

html.tp-vertical #colorPopup .cp-actions button {
    height: 38px;
    margin: 0;
    padding: 0 6px;
    border: 1px solid var(--border-color);
    border-radius: 9px;
    background: color-mix(in srgb, var(--text-color) 6%, var(--secondary-color));
    color: var(--text-color);
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}

html.tp-vertical #colorPopup .cp-actions button:hover {
    border-color: color-mix(in srgb, var(--text-color) 24%, transparent);
    background: color-mix(in srgb, var(--text-color) 11%, var(--secondary-color));
}

html.tp-vertical #colorPopup .cp-actions button:focus-visible {
    outline: none;
    border-color: var(--primary-color);
}

html.tp-vertical #colorPopup .cp-actions button:active {
    transform: scale(0.97);
}

@keyframes tpvPopIn {
    from { opacity: 0; transform: scale(0.96); }
}

@media (prefers-reduced-motion: reduce) {
    html.tp-vertical #colorPopup {
        animation: none;
    }
}

/* ── Responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 1280px) {
    html.tp-vertical #container1 {
        flex-basis: 420px;
        width: 420px;
    }

    html.tp-vertical .product-grid {
        grid-template-columns: repeat(auto-fill, minmax(152px, 1fr));
    }
    html.tp-vertical.tpv-grid-loading .tpv-grid-skeleton {
        grid-template-columns: repeat(auto-fill, minmax(152px, 1fr));
    }

}

/* 1024x768 is the smallest supported terminal. Nothing is hidden — the grid
   reflows and the chrome tightens. */
@media (max-width: 1100px), (max-height: 820px) {

    html.tp-vertical .tpv-topbar {
        height: 54px;
        gap: 11px;
        padding: 0 13px;
    }

    html.tp-vertical .tpv-status {
        display: none;
    }

    /* The name is NOT dropped here. This query catches (max-height: 820px),
       which every 1366x768 till matches — hiding it would mean the one screen
       this ships to most is the one screen that never shows who is on it. It
       gives up width instead. */
    html.tp-vertical .tpv-user {
        max-width: 20ch;
    }

    html.tp-vertical #container1 {
        flex-basis: 380px;
        width: 380px;
    }

    html.tp-vertical .tpv-order-head {
        padding: 11px 15px 7px;
    }

    html.tp-vertical .tpv-totals {
        padding: 9px 15px 0;
    }

    html.tp-vertical .tpv-grand-value {
        font-size: 23px;
    }

    html.tp-vertical .tpv-actions {
        padding: 10px 11px 11px;
        gap: 6px;
    }

    html.tp-vertical .tpv-btn {
        height: 52px;
    }

    html.tp-vertical .tpv-btn-send {
        font-size: 12.5px;
    }

    html.tp-vertical {
        --tpv-rail-w: 94px;
    }

    html.tp-vertical .tpv-rail {
        padding: 9px 0;
        gap: 0;
    }

    html.tp-vertical .tpv-rail-btn {
        min-height: 62px;
        padding: 7px 4px 6px;
    }

    html.tp-vertical .tpv-rail-label {
        font-size: 11px;
    }

    html.tp-vertical .tpv-grid-hint {
        display: none;
    }

    html.tp-vertical .tpv-pricelist {
        padding: 0 14px 8px;
    }

    html.tp-vertical .products-storage {
        padding: 0 14px 14px;
    }

    html.tp-vertical .product-grid {
        grid-template-columns: repeat(auto-fill, minmax(138px, 1fr));
        gap: 9px;
    }
    html.tp-vertical.tpv-grid-loading .tpv-grid-skeleton {
        grid-template-columns: repeat(auto-fill, minmax(138px, 1fr));
        gap: 9px;
    }

    html.tp-vertical .tpv-skel-tile {
        height: 100px;
        padding: 11px;
    }


    html.tp-vertical .product-tile {
        height: 100px;
        min-height: 100px;
        padding: 11px;
    }

    html.tp-vertical .product-tile .label-prod-name {
        font-size: 12.5px;
    }

    html.tp-vertical .product-tile .label-prod-price,
    html.tp-vertical .product-tile .price-badge {
        font-size: 14px;
    }
}

/* ── Item editor ────────────────────────────────────────────────────────
   Editing one line already in the cart: quantity, sugar level, add-ons and
   the kitchen note.

   Scoped to the modal's own id so it cannot reach the shared .tp-modal
   language, and every colour is a token — the older resto stepper in
   resto.css hardcodes its greys and oranges and is unreadable in light mode,
   which is why this does not reuse it. */

#itemEditModal .tp-modal-container {
    max-height: 88vh;
}

#itemEditModal .ie-headings {
    min-width: 0;
}

/* The item, and what it currently comes to. */
#itemEditModal .ie-summary {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    padding: 12px 14px;
    margin-bottom: 14px;
    border: 1px solid var(--border-color);
    border-radius: 12px;
    background: var(--background-color);
}

#itemEditModal .ie-summary-main {
    min-width: 0;
}

#itemEditModal .ie-name {
    font-size: 15px;
    font-weight: 700;
    line-height: 1.25;
    color: var(--text-color);
    word-break: break-word;
}

#itemEditModal .ie-meta {
    margin-top: 3px;
    font-size: 11.5px;
    color: var(--information-color);
}

#itemEditModal .ie-summary-money {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex-shrink: 0;
}

#itemEditModal .ie-total-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

#itemEditModal .ie-total {
    font-size: 19px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--primary-ink, var(--primary-color));
}

#itemEditModal .ie-locked-note {
    margin-bottom: 14px;
    padding: 9px 12px;
    border-radius: 10px;
    border: 1px solid color-mix(in srgb, var(--tm-billout, #f39c12) 45%, transparent);
    background: color-mix(in srgb, var(--tm-billout, #f39c12) 12%, transparent);
    font-size: 12px;
    color: var(--text-color);
}

#itemEditModal .ie-field {
    margin-bottom: 16px;
}

#itemEditModal .ie-field[hidden] {
    display: none;
}

#itemEditModal .ie-label,
#itemEditModal .ie-label-row {
    display: block;
    margin-bottom: 7px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

#itemEditModal .ie-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
}

#itemEditModal .ie-label-row .ie-label {
    margin-bottom: 0;
}

#itemEditModal .ie-addon-total {
    font-size: 12px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    text-transform: none;
    letter-spacing: 0;
    color: var(--primary-ink, var(--primary-color));
}

/* The running total and the ADD ONS button share the right of the heading. */
#itemEditModal .ie-label-end {
    display: flex;
    align-items: center;
    gap: 10px;
}

#itemEditModal .ie-addon-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: 1px solid var(--primary-color);
    border-radius: 999px;
    background: color-mix(in srgb, var(--primary-color) 12%, transparent);
    color: var(--primary-ink, var(--primary-color));
    font-family: inherit;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.5px;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

#itemEditModal .ie-addon-btn:hover:not(:disabled) {
    background: color-mix(in srgb, var(--primary-color) 24%, transparent);
}

/* Open: filled, so the button reads as the thing holding the panel down. */
#itemEditModal .ie-addon-btn.is-on {
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

#itemEditModal .ie-addon-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

#itemEditModal .ie-hint {
    margin-top: 6px;
    font-size: 11px;
    color: var(--information-color);
}

/* Quantity stepper. Touch-sized, and themed rather than hardcoded. */
#itemEditModal .ie-stepper {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--border-color);
    border-radius: 11px;
    overflow: hidden;
    background: var(--background-color);
}

#itemEditModal .ie-step {
    width: 54px;
    border: 0;
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
    color: var(--primary-ink, var(--primary-color));
    font-size: 22px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease;
}

#itemEditModal .ie-step:hover:not(:disabled) {
    background: color-mix(in srgb, var(--primary-color) 26%, transparent);
}

#itemEditModal .ie-step:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

#itemEditModal .ie-step-value {
    width: 92px;
    height: 48px;
    border: 0;
    border-left: 1px solid var(--border-color);
    border-right: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-color);
    font-size: 18px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
    outline: none;
}

#itemEditModal .ie-step-value:focus {
    box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--primary-color) 40%, transparent);
}

/* Chips — sugar levels and quick notes. */
#itemEditModal .ie-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

#itemEditModal .ie-chip {
    padding: 6px 12px;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background: var(--background-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

#itemEditModal .ie-chip:hover {
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--primary-color) 10%, transparent);
}

#itemEditModal .ie-chip.is-on {
    border-color: var(--primary-color);
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

#itemEditModal .ie-quick {
    margin-bottom: 8px;
}

#itemEditModal .ie-quick .ie-chip {
    font-weight: 500;
}

/* Sizes — a bigger tile than a chip: the name and what it costs. */
#itemEditModal .ie-size-chips {
    gap: 8px;
}

#itemEditModal .ie-size-chip {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    min-width: 96px;
    padding: 8px 16px;
    border-radius: 10px;
}

#itemEditModal .ie-size-name {
    font-weight: 700;
    text-transform: uppercase;
}

#itemEditModal .ie-size-price {
    font-size: 0.92em;
    font-weight: 600;
    color: var(--primary-ink, var(--primary-color));
}

#itemEditModal .ie-size-chip.is-on .ie-size-price {
    color: var(--on-primary, #fff);
}

#itemEditModal .ie-size-chip:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

#itemEditModal .ie-hidden-select {
    display: none;
}

/* Add-ons — a priced list, each row a toggle. */
#itemEditModal .ie-addons {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 7px;
    max-height: 190px;
    overflow-y: auto;
}

/* Name over price, not name beside price. Side by side, the price took enough
   of a 190px column that a real product name still could not fit in two
   lines — stacking gives the name the whole width. */
#itemEditModal .ie-addon {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 3px;
    padding: 9px 12px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--background-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}

#itemEditModal .ie-addon:hover {
    border-color: var(--primary-color);
}

#itemEditModal .ie-addon.is-on {
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
}

/* The row is a plain button again. It used to be a div wrapping one, because
   the chosen add-on carried its own stepper and a button cannot nest — the
   stepper lives on the tag now, so the row has nothing to hold but itself. */
#itemEditModal .ie-addon:disabled {
    cursor: not-allowed;
}

/* ── The add-ons this item HAS ───────────────────────────────────────────
   A strip of tags, the way quick notes read. Deliberately not the 190px
   grid the picker uses: the whole point is that the name is not cut off. */
#itemEditModal .ie-addon-tags {
    margin-bottom: 8px;
}

#itemEditModal .ie-addon-none {
    font-size: 12px;
    color: var(--information-color);
}

#itemEditModal .ie-addon-tag {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    max-width: 100%;
    padding: 5px 6px 5px 12px;
    border: 1px solid var(--primary-color);
    border-radius: 999px;
    /* A tint, not the solid accent fill a plain chip uses: the stepper and the
       remove button sit inside this pill and have to stay readable. */
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
    color: var(--text-color);
    font-size: 12px;
    font-weight: 600;
}

#itemEditModal .ie-addon-tag-name {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

#itemEditModal .ie-addon-tag-qty {
    display: inline-flex;
    align-items: stretch;
    flex: 0 0 auto;
    border: 1px solid color-mix(in srgb, var(--primary-color) 45%, transparent);
    border-radius: 999px;
    overflow: hidden;
    background: var(--background-color);
}

#itemEditModal .ie-addon-tag-qty.is-static {
    padding: 2px 9px;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

#itemEditModal .ie-addon-tag-price {
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    font-weight: 800;
    color: var(--primary-ink, var(--primary-color));
}

#itemEditModal .ie-addon-tag-remove {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--information-color);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

#itemEditModal .ie-addon-tag-remove:hover {
    background: color-mix(in srgb, var(--tm-occupied, #e74c3c) 18%, transparent);
    color: var(--tm-occupied, #e74c3c);
}

/* ── The add-ons this item COULD have ────────────────────────────────────
   A card in the right column: what the line HAS at the top, what it could
   have below, with a search over the list. */
#itemEditModal .ie-addon-search {
    width: 100%;
    margin-bottom: 8px;
    padding: 8px 12px;
    border: 1px solid var(--border-color);
    border-radius: 9px;
    background: var(--secondary-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 12.5px;
}

#itemEditModal .ie-addon-search[hidden] {
    display: none;
}

#itemEditModal .ie-addon-search:focus {
    outline: none;
    border-color: var(--primary-color);
}

#itemEditModal .ie-addon-qty {
    display: inline-flex;
    align-items: stretch;
    flex: 0 0 auto;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    overflow: hidden;
    background: var(--background-color);
}

#itemEditModal .ie-addon-step {
    width: 28px;
    border: 0;
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
    color: var(--primary-ink, var(--primary-color));
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease;
}

#itemEditModal .ie-addon-step:hover {
    background: color-mix(in srgb, var(--primary-color) 26%, transparent);
}

#itemEditModal .ie-addon-count {
    min-width: 26px;
    padding: 4px 2px;
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    font-size: 12.5px;
}
/* Wraps to two lines rather than ending in an ellipsis. A cashier cannot
   choose "0.9% SODIUM …" from a list — and in a 190px column that is what
   every real product name came to. */
#itemEditModal .ie-addon-name {
    min-width: 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
    font-weight: 600;
    line-height: 1.3;
}

#itemEditModal .ie-addon-price {
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--primary-ink, var(--primary-color));
}

#itemEditModal .ie-empty {
    grid-column: 1 / -1;
    padding: 14px 12px;
    border: 1px dashed var(--border-color);
    border-radius: 10px;
    font-size: 12px;
    color: var(--information-color);
    text-align: center;
}

#itemEditModal .ie-note {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--background-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.4;
    resize: vertical;
    outline: none;
}

#itemEditModal .ie-note:focus {
    border-color: var(--primary-color);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
}

#itemEditModal .ie-note:disabled,
#itemEditModal .ie-step-value:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

#itemEditModal .ie-footer-end {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

/* Read-only: the controls stop responding rather than disappearing, so the
   cashier can still read the item they tapped. */
#itemEditModal.is-readonly .ie-chip,
#itemEditModal.is-readonly .ie-addon,
#itemEditModal.is-readonly .ie-addon-tag {
    opacity: 0.55;
    cursor: not-allowed;
}

/* An add-on sits under the item it belongs to. It is still a charged line and
   still counts, it just stops reading as a separate order. */
html.tp-vertical #tbody-cashiering tr.is-addon-line td.prod_desc {
    padding-left: 18px !important;
    font-size: 12px !important;
    color: color-mix(in srgb, var(--text-color) 72%, transparent);
}

html.tp-vertical #tbody-cashiering tr.is-addon-line td.prod_desc::before {
    content: "+";
    margin-right: 5px;
    font-weight: 800;
    color: var(--primary-ink, var(--primary-color));
}

/* A 768px-tall terminal: the modal has to stay inside the screen, and the
   add-on grid is the part that can afford to shrink. */
@media (max-height: 820px) {
    #itemEditModal .tp-modal-container {
        max-height: 94vh;
    }

    #itemEditModal .ie-addons {
        max-height: 128px;
    }

    /* The tag strip can grow without bound when an item carries many add-ons,
       so it scrolls rather than pushing the note off a 768px screen. */
    #itemEditModal .ie-addon-tags {
        max-height: 92px;
        overflow-y: auto;
    }

    /* The add-on list gives up height first, so the price breakdown under it
       stays above the fold — the total is the one thing in this dialog that
       should never need scrolling to. */
    #itemEditModal .ie-card-addons .ie-addons {
        max-height: 112px;
    }


    #itemEditModal .ie-step,
    #itemEditModal .ie-step-value {
        height: 42px;
    }

    #itemEditModal .ie-field {
        margin-bottom: 12px;
    }
}

/* ── Bill Out ───────────────────────────────────────────────────────────
   The rail button, and the dialog that reviews the bill before it prints.

   The button borrows the BILL OUT amber the floor plan already uses for a
   billed table, so the two read as the same idea. Everything else is tokens,
   so it follows the terminal's palette in both themes. */

html.tp-vertical .tpv-rail-btn.tpv-rail-billout .tpv-rail-icon,
html.tp-vertical .tpv-rail-btn.tpv-rail-billout .tpv-rail-label {
    color: var(--tm-billout, #f39c12);
}

html.tp-vertical .tpv-rail-btn.tpv-rail-billout:hover {
    background: color-mix(in srgb, var(--tm-billout, #f39c12) 12%, transparent);
}

#billOutModal .tp-modal-container {
    max-height: 90vh;
}

#billOutModal .bo-headings {
    min-width: 0;
}

/* Table, diner, when they ordered. */
#billOutModal .bo-context {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    margin-bottom: 12px;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: 11px;
    background: var(--background-color);
    font-size: 12px;
    color: var(--information-color);
}

#billOutModal .bo-ctx-strong {
    font-size: 14px;
    font-weight: 800;
    color: var(--text-color);
}

#billOutModal .bo-ctx-dot {
    margin: 0 8px;
    opacity: 0.5;
}

/* The lines, laid out the way the paper reads: name, qty, amount. */
#billOutModal .bo-lines {
    max-height: 230px;
    overflow-y: auto;
    margin-bottom: 14px;
    border: 1px solid var(--border-color);
    border-radius: 11px;
}

#billOutModal .bo-line {
    display: grid;
    grid-template-columns: 1fr auto auto;
    gap: 12px;
    align-items: baseline;
    padding: 8px 12px;
    font-size: 12.5px;
    border-bottom: 1px solid var(--hairline);
}

#billOutModal .bo-line:last-child {
    border-bottom: 0;
}

#billOutModal .bo-line-name {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--text-color);
}

#billOutModal .bo-line-qty {
    min-width: 42px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--information-color);
}

#billOutModal .bo-line-amt {
    min-width: 78px;
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--text-color);
}

/* A line's own discount, indented under the dish it came off. */
#billOutModal .bo-line-sub {
    padding-top: 0;
    padding-left: 26px;
    border-bottom: 0;
    font-size: 11.5px;
}

#billOutModal .bo-line-sub .bo-line-name,
#billOutModal .bo-line-sub .bo-line-amt {
    font-weight: 500;
    color: var(--cart-mark-reduced, #f0a020);
}

#billOutModal .bo-field {
    margin-bottom: 14px;
}

#billOutModal .bo-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 7px;
}

#billOutModal .bo-label {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

#billOutModal .bo-quote {
    font-size: 13px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--cart-mark-reduced, #f0a020);
}

#billOutModal .bo-types {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

#billOutModal .bo-chip {
    padding: 6px 12px;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background: var(--background-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

#billOutModal .bo-chip:hover {
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--primary-color) 10%, transparent);
}

#billOutModal .bo-chip.is-on {
    border-color: var(--primary-color);
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

/* Diners / eligible / name — only for the programmes that split per head. */
#billOutModal .bo-split {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 10px;
}

#billOutModal .bo-split[hidden] {
    display: none;
}

#billOutModal .bo-split-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

#billOutModal .bo-split-name {
    flex: 1 1 200px;
    min-width: 0;
}

#billOutModal .bo-sub {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    color: var(--information-color);
}

#billOutModal .bo-num,
#billOutModal .bo-text {
    height: 38px;
    padding: 0 10px;
    border: 1px solid var(--border-color);
    border-radius: 9px;
    background: var(--background-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 13px;
    outline: none;
}

#billOutModal .bo-num {
    width: 84px;
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

#billOutModal .bo-text {
    width: 100%;
}

#billOutModal .bo-num:focus,
#billOutModal .bo-text:focus {
    border-color: var(--primary-color);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
}

/* Said out loud when a programme has nothing on this order to discount —
   a setup question, not an error. */
#billOutModal .bo-note {
    margin-top: 9px;
    padding: 9px 11px;
    border: 1px dashed var(--border-color);
    border-radius: 9px;
    font-size: 11.5px;
    color: var(--information-color);
}

#billOutModal .bo-totals {
    padding: 11px 12px;
    border: 1px solid var(--border-color);
    border-radius: 11px;
    background: var(--background-color);
}

#billOutModal .bo-total-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 3px 0;
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    color: var(--information-color);
}

#billOutModal .bo-total-row.is-less {
    color: var(--cart-mark-reduced, #f0a020);
}

#billOutModal .bo-total-row.is-grand {
    margin-top: 7px;
    padding-top: 9px;
    border-top: 1px solid var(--border-color);
    font-size: 17px;
    font-weight: 800;
    color: var(--text-color);
}

#billOutModal .bo-total-row.is-grand span:last-child {
    color: var(--primary-ink, var(--primary-color));
}

/* The footer says what the paper says. */
#billOutModal .bo-footer-note {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.4px;
    text-transform: uppercase;
    color: var(--cart-mark-removed, #c62828);
}

#billOutModal .bo-footer-end {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

/* A 768px-tall terminal: the line list is what gives way. */
@media (max-height: 820px) {
    #billOutModal .tp-modal-container {
        max-height: 95vh;
    }

    #billOutModal .bo-lines {
        max-height: 150px;
    }

    #billOutModal .bo-field {
        margin-bottom: 10px;
    }
}

/* ── Partial Pay ────────────────────────────────────────────────────────
   Rail button, the cart's Amount Paid / Remaining Balance rows, and the
   dialog. Green = money already in; the balance takes the primary ink. */

html.tp-vertical .tpv-rail-btn.tpv-rail-partial .tpv-rail-icon,
html.tp-vertical .tpv-rail-btn.tpv-rail-partial .tpv-rail-label {
    color: var(--tm-partial, #16a34a);
}

html.tp-vertical .tpv-rail-btn.tpv-rail-partial:hover {
    background: color-mix(in srgb, var(--tm-partial, #16a34a) 12%, transparent);
}

html.tp-vertical .tpv-total-row.tpv-partial-paid-row > span:last-child {
    color: var(--tm-partial, #16a34a);
}

html.tp-vertical .tpv-total-row.tpv-balance-row > span {
    font-weight: 800;
    color: var(--text-color);
}

/* Payment screen: "Fully paid — keep table occupied" (Table Management only). */
#payCashModal .payCash-keep-table {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 10px 0 0;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--background-color);
    color: var(--text-color);
    cursor: pointer;
    user-select: none;
}

#payCashModal .payCash-keep-table:has(input:checked) {
    border-color: var(--primary-color);
    background: color-mix(in srgb, var(--primary-color) 10%, var(--background-color));
}

#payCashModal .payCash-keep-table input {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    margin: 1px 0 0;
    accent-color: var(--primary-color);
    cursor: pointer;
}

#payCashModal .payCash-keep-table span {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

#payCashModal .payCash-keep-table strong {
    font-size: 14px;
}

#payCashModal .payCash-keep-table small {
    font-size: 12.5px;
    color: color-mix(in srgb, var(--text-color) 60%, transparent) !important;
}

/* process.js resets CHANGE with an inline white; keep it readable in light mode. */
:root.light-mode #payCashModal .phpChange[style*="white"]:not(.text-success):not(.text-danger) {
    color: var(--text-color) !important;
}

/* Partial Pay dialog: the tp-modal system (modal-redesign.css) does the look;
   these only fit it to the content and keep it legible on 1024x768 / 1366x768
   tills — no text under 12.5px, money at 15px+, touch targets 40px+. */

/* media-sizes.css zooms every .modal to 72% at exactly 1366px. */
#partialPayModal {
    zoom: 1 !important;
    -moz-transform: none !important;
}

#partialPayModal [hidden] {
    display: none !important;
}

#partialPayModal .tp-modal-container {
    width: calc(100vw - 32px);
    max-width: 520px;
    max-height: calc(100vh - 32px);
}

#partialPayModal .pp-headings {
    min-width: 0;
}

#partialPayModal .tp-modal-close {
    width: 40px;
    height: 40px;
}

#partialPayModal .tp-modal-subtitle {
    font-size: 13px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#partialPayModal .tp-modal-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

#partialPayModal .tp-modal-section-title {
    margin-top: 0;
    padding-top: 12px;
    font-size: 12.5px;
}

#partialPayModal .tp-modal-label {
    font-size: 12.5px;
    margin-bottom: 0;
}

#partialPayModal .tp-modal-sub-text {
    font-size: 14px;
    color: var(--text-color);
}

/* Bill / paid / balance. */
#partialPayModal .tp-modal-info-card {
    margin-bottom: 0;
    padding: 6px 16px;
}

#partialPayModal .tp-modal-info-row {
    padding: 8px 0;
    font-size: 15px;
    font-variant-numeric: tabular-nums;
}

#partialPayModal .tp-modal-amount-total {
    font-size: 20px;
    font-variant-numeric: tabular-nums;
}

/* Payment history: a short list, so the form below stays on screen. */
#partialPayModal .pp-history {
    min-height: 0;
    max-height: 150px;
}

#partialPayModal .tp-modal-table tbody tr {
    cursor: default;
}

#partialPayModal .tp-modal-table tbody td {
    padding: 9px 8px;
    font-size: 14px;
    vertical-align: middle;
}

#partialPayModal .pp-hist-main b {
    display: block;
    font-weight: 700;
}

#partialPayModal .pp-hist-main small {
    display: block;
    font-size: 12.5px;
    color: var(--information-color);
}

#partialPayModal .pp-hist-amt {
    text-align: right;
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

#partialPayModal tr.is-cancelled td {
    color: var(--information-color);
}

#partialPayModal tr.is-cancelled .pp-hist-amt {
    text-decoration: line-through;
}

#partialPayModal .pp-hist-act {
    width: 1%;
    white-space: nowrap;
    text-align: right;
}

#partialPayModal .pp-row-btn {
    width: 40px;
    height: 40px;
    margin-left: 4px;
    border-radius: 10px;
    border: 1px solid var(--border-color);
    background: var(--background-color);
    color: var(--information-color);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    vertical-align: middle;
}

#partialPayModal .pp-row-btn:hover {
    border-color: var(--primary-color);
    color: var(--text-color);
}

#partialPayModal .pp-row-btn.pp-cancel:hover {
    border-color: var(--accent-color);
    color: var(--accent-color);
}

#partialPayModal .pp-tag {
    font-size: 12.5px;
    font-weight: 700;
    color: var(--accent-color);
}

/* Methods: the system's segmented toggle, wrapping when a store has many. */
#partialPayModal .pp-methods {
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 0;
}

#partialPayModal .pp-methods .btn {
    flex: 1 1 90px;
    min-height: 40px;
    font-size: 14px;
}

#partialPayModal .pp-amount-wrap {
    display: flex;
    gap: 8px;
}

#partialPayModal .pp-amount-wrap .tp-modal-display-input {
    flex: 1 1 auto;
    min-width: 0;
    padding-right: 16px;
    font-variant-numeric: tabular-nums;
}

#partialPayModal .pp-quick-btn {
    flex: 0 0 auto;
    justify-content: center;
    min-width: 84px;
    font-weight: 600;
}

#partialPayModal .tp-modal-input {
    min-height: 44px;
    font-size: 15px;
}

#partialPayModal .pp-note {
    margin: 0;
    padding: 10px 12px;
    border-radius: 10px;
    background: color-mix(in srgb, #f39c12 14%, transparent);
    text-align: left;
}

#partialPayModal .pp-link {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 700;
    text-decoration: underline;
    color: var(--primary-ink, var(--primary-color));
    cursor: pointer;
}

#partialPayModal .pp-cancel-box,
#partialPayModal #ppConfirmBox {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

#partialPayModal #ppConfirmBox .tp-modal-info-value {
    text-align: right;
    overflow-wrap: anywhere;
}

#partialPayModal .pp-cancel-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

#partialPayModal .pp-footer-end {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

#partialPayModal .tp-modal-footer button {
    min-height: 44px;
    white-space: nowrap;
}

/* 768px-tall tills (1024x768, 1366x768): tighter, never smaller text. */
@media (max-height: 820px) {
    #partialPayModal .tp-modal-header {
        padding: 16px 20px 10px;
    }

    #partialPayModal .tp-modal-body {
        padding: 0 20px 12px;
        gap: 10px;
    }

    #partialPayModal .tp-modal-footer {
        padding: 12px 20px 14px;
    }

    #partialPayModal .tp-modal-info-row {
        padding: 6px 0;
    }

    #partialPayModal .tp-modal-display-input {
        padding-top: 10px;
        padding-bottom: 10px;
        font-size: 1.45rem;
    }

    #partialPayModal .pp-history {
        max-height: 104px;
    }
}

/* Narrow screens: stack the footer and the cash pair. */
@media (max-width: 560px) {
    #partialPayModal .tp-modal-container {
        width: calc(100vw - 16px);
        max-height: calc(100vh - 16px);
    }

    #partialPayModal .tp-modal-header {
        padding: 16px 16px 10px;
    }

    #partialPayModal .tp-modal-body {
        padding: 0 16px 12px;
    }

    #partialPayModal .tp-modal-footer {
        padding: 12px 16px 14px;
    }

    #partialPayModal .tp-modal-footer {
        flex-direction: column;
        align-items: stretch;
    }

    #partialPayModal .pp-footer-end {
        margin-left: 0;
    }

    #partialPayModal .pp-footer-end button {
        flex: 1 1 0;
        justify-content: center;
    }
}

/* ── Item editor: the discount on one line ──────────────────────────────
   A type toggle, an amount, and what it actually comes to — the last of
   those is the one that matters, so it is the one that is emphasised. */

#itemEditModal .ie-discount {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

/* A segmented control: one track, with the chosen half filled.
   Pill-shaped to match the chips, tags and status pill already in this
   dialog, rather than a second, squarer button vocabulary. The track holds
   the border; the halves sit inside it on a 3px inset, so the fill is a pill
   within a pill instead of a square corner clipped by an overflow. */
#itemEditModal .ie-disc-mode {
    display: inline-flex;
    flex: 0 0 auto;
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background: var(--background-color);
}

#itemEditModal .ie-disc-tab {
    /* Both halves the same width, so the control does not shift as the label
       changes length and the two read as equal choices. */
    flex: 1 1 0;
    min-width: 86px;
    height: 32px;
    padding: 0 14px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--information-color);
    font-family: inherit;
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

#itemEditModal .ie-disc-tab:hover:not(:disabled):not(.is-on) {
    background: color-mix(in srgb, var(--primary-color) 12%, transparent);
    color: var(--primary-ink, var(--primary-color));
}

#itemEditModal .ie-disc-tab.is-on {
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

#itemEditModal .ie-disc-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 42px;
    padding: 0 12px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--background-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 15px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: right;
    outline: none;
}

#itemEditModal .ie-disc-input:focus {
    border-color: var(--primary-color);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
}

/* What comes off the line. */
#itemEditModal .ie-disc-result {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    min-width: 104px;
    justify-content: flex-end;
    padding: 0 12px;
    border: 1px solid color-mix(in srgb, var(--cart-mark-reduced, #f0a020) 45%, transparent);
    border-radius: 10px;
    background: color-mix(in srgb, var(--cart-mark-reduced, #f0a020) 12%, transparent);
    font-size: 14px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--cart-mark-reduced, #f0a020);
}

/* Why the field is inert — a control that is simply dead reads as broken. */
#itemEditModal .ie-discount-off {
    font-size: 11px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    color: var(--cart-mark-reduced, #f0a020);
}

#itemEditModal .ie-disc-tab:disabled,
#itemEditModal .ie-disc-input:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

@media (max-height: 820px) {
    #itemEditModal .ie-disc-input,
    #itemEditModal .ie-disc-result {
        height: 38px;
    }

    /* The track keeps its 3px inset, so the halves come down with it rather
       than being set to the track's own height and spilling out of it. */
    #itemEditModal .ie-disc-tab {
        height: 30px;
    }
}

/* ── Item editor: quantity and discount on one row ──────────────────────
   They are the two numbers on this item and belong next to each other;
   stacked, they pushed the note and the add-ons below the fold. */

#itemEditModal .ie-row {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    margin-bottom: 4px;
}

#itemEditModal .ie-row > .ie-field {
    margin-bottom: 12px;
}

/* The stepper is a fixed size, so its column is too. */
#itemEditModal .ie-field-qty {
    flex: 0 0 auto;
}

/* The discount takes whatever is left; min-width:0 is what lets its input
   actually shrink inside a flex row instead of forcing an overflow. */
#itemEditModal .ie-field-disc {
    flex: 1 1 auto;
    min-width: 0;
}

#itemEditModal .ie-field-disc[hidden] {
    display: none;
}

/* Line the two controls up on their baselines: the label rows differ in
   height because only one of them carries a reason chip. */
#itemEditModal .ie-field-disc .ie-label-row {
    min-height: 19px;
}


/* Both hints sit under the whole row, full width. In a 200px column the
   kitchen notice wrapped to four lines.

   It reads as a notice rather than as stray text under the discount box: an
   11px line with no room of its own ran straight into the ADD-ONS heading
   below it, which is what the report showed. */
#itemEditModal .ie-hint-row {
    margin-top: 0;
    margin-bottom: 14px;
    padding: 8px 11px;
    border-radius: 9px;
    border: 1px solid color-mix(in srgb, var(--information-color) 30%, transparent);
    background: color-mix(in srgb, var(--information-color) 9%, transparent);
    font-size: 11.5px;
    line-height: 1.45;
}

#itemEditModal .ie-hint-row[hidden] {
    display: none;
}

/* No fixed height here any more. The row wraps its presets on a narrow card,
   and pinning it to 42px both stopped that wrap and left a 2px band above and
   below every control inside it. */

/* Too narrow to sit side by side — stack rather than crush the input. */
@media (max-width: 560px) {
    #itemEditModal .ie-row {
        flex-direction: column;
        gap: 0;
    }

    #itemEditModal .ie-field-qty,
    #itemEditModal .ie-field-disc {
        width: 100%;
    }
}

/* ── Add-ons, on the item they belong to ───────────────────────────────
   An add-on is a priced row of its own — that is how it is stocked and
   billed — but in the cart a row of its own reads as a separate order,
   and it sorts ABOVE its item because the punch path inserts children
   before the parent. So the row is hidden and the add-on is drawn on the
   item instead.

   Hidden, never removed: auto-zread.js, index.js, visualDisplay.js and
   process.js all count #tableTransactions tbody tr, and a cart that has
   been emptied but still counts rows blocks an automatic Z-reading.

   Written at id specificity for the reason the note above gives: the
   retail rules on #tbody-cashiering outrank anything scoped by class. */

html.tp-vertical #tbody-cashiering tr.is-addon-folded {
    display: none;
}

html.tp-vertical #tbody-cashiering tr td.prod_desc .tpv-line-addon {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-top: 3px;
    padding-left: 7px;
    white-space: normal;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.3;
    color: color-mix(in srgb, var(--text-color) 78%, transparent);
}

html.tp-vertical #tbody-cashiering tr td.prod_desc .tpv-line-addon-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The money right-aligned, so several add-ons under one item line up. */
html.tp-vertical #tbody-cashiering tr td.prod_desc .tpv-line-addon-price {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: color-mix(in srgb, var(--text-color) 62%, transparent);
}

/* On an item the kitchen already has, its add-ons are history too. */
html.tp-vertical #tbody-cashiering tr.is-line-sent td.prod_desc .tpv-line-addon,
html.tp-vertical #tbody-cashiering tr.is-line-locked td.prod_desc .tpv-line-addon {
    color: color-mix(in srgb, var(--text-color) 58%, transparent);
}

/* ── The kitchen note, on the cart line ─────────────────────────────────
   What was actually asked for. It lived only on the printed ticket and in
   the send confirmation, so a cashier looking at the order could not see
   the instruction they had taken.

   Its own line, not appended to the barcode line: that one is nowrap and
   ellipsised at id specificity, and half an instruction is worse than
   none. Written at id specificity for the same reason — the retail rules
   on #tbody-cashiering outrank anything scoped only by class. */

html.tp-vertical #tbody-cashiering tr td.prod_desc .tpv-line-note {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 3px;
    padding-left: 7px;
    border-left: 2px solid color-mix(in srgb, var(--primary-color) 55%, transparent);
    white-space: normal;
    font-size: 11px;
    font-weight: 600;
    font-style: italic;
    line-height: 1.3;
    color: color-mix(in srgb, var(--text-color) 82%, transparent);
}

/* On a line the kitchen already has, the note is history rather than an
   instruction still to carry out — quieter, but still readable. */
html.tp-vertical #tbody-cashiering tr.is-line-sent td.prod_desc .tpv-line-note,
html.tp-vertical #tbody-cashiering tr.is-line-locked td.prod_desc .tpv-line-note {
    border-left-color: color-mix(in srgb, var(--text-color) 28%, transparent);
    color: color-mix(in srgb, var(--text-color) 62%, transparent);
}

/* An add-on's own note sits under an already indented line. */
html.tp-vertical #tbody-cashiering tr.is-addon-line td.prod_desc .tpv-line-note {
    font-size: 10.5px;
}

@media (max-height: 820px) {
    html.tp-vertical #tbody-cashiering tr td.prod_desc .tpv-line-note {
        -webkit-line-clamp: 1;
    }
}


/* ══ Item editor: the two-column layout ═══════════════════════════════════
   Left is what you change about the line, right is what it comes to. The
   dialog is wider than the .tp-lg default because two columns need the room;
   everything inside is the same tokens the rest of the editor already uses. */

#itemEditModal .tp-modal-container {
    max-width: 920px;
}

#itemEditModal .ie-title-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* Sent to the kitchen is a state, so it reads as one. */
#itemEditModal .ie-status-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border: 1px solid color-mix(in srgb, var(--tm-billout, #f39c12) 50%, transparent);
    border-radius: 999px;
    background: color-mix(in srgb, var(--tm-billout, #f39c12) 14%, transparent);
    color: var(--text-color);
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.3px;
    text-transform: none;
}

#itemEditModal .ie-status-pill[hidden] {
    display: none;
}

#itemEditModal .ie-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: 14px;
    align-items: start;
}

#itemEditModal .ie-col {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* ── Cards ───────────────────────────────────────────────────────────── */

/* Divs, not <section>s, deliberately: home-redesign.css carries a global
   `section { height: 100vh !important; padding: 0 !important }` for the page
   shell, which made every card exactly a screen tall with no padding. */
#itemEditModal .ie-card {
    padding: 13px 14px;
    border: 1px solid var(--border-color);
    border-radius: 14px;
    background: var(--background-color);
}

#itemEditModal .ie-card[hidden] {
    display: none;
}

#itemEditModal .ie-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 10px;
}

#itemEditModal .ie-card-title {
    margin: 0;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

#itemEditModal .ie-card-note {
    font-size: 11px;
    color: var(--information-color);
}

#itemEditModal .ie-card-note[hidden] {
    display: none;
}

#itemEditModal .ie-card-sub {
    margin: -6px 0 10px;
    font-size: 11px;
    color: var(--information-color);
}

/* ── Quantity ────────────────────────────────────────────────────────── */

#itemEditModal .ie-qty-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

#itemEditModal .ie-preset-row {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

#itemEditModal .ie-preset {
    min-width: 44px;
    padding: 9px 10px;
    border: 1px solid var(--border-color);
    border-radius: 10px;
    background: var(--secondary-color);
    color: var(--text-color);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

#itemEditModal .ie-preset:hover:not(:disabled) {
    border-color: var(--primary-color);
}

#itemEditModal .ie-preset.is-on {
    border-color: var(--primary-color);
    background: var(--primary-color);
    color: var(--on-primary, #fff);
}

#itemEditModal .ie-preset:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ── Discount ────────────────────────────────────────────────────────── */

#itemEditModal .ie-field-disc .ie-discount {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* One line: toggle, amount, presets. Nothing wraps — the amount is the only
   part that gives way, because the other two have a shortest sensible size
   and it does not. */
#itemEditModal .ie-field-disc .ie-discount {
    flex-wrap: nowrap;
}

#itemEditModal .ie-field-disc .ie-disc-input {
    flex: 1 1 60px;
    min-width: 52px;
}

#itemEditModal .ie-field-disc .ie-preset-row {
    flex: 0 0 auto;
    gap: 5px;
}

/* Tighter than the quantity presets: those have a row to themselves, these
   share one with two other controls. */
#itemEditModal .ie-field-disc .ie-preset {
    min-width: 38px;
    padding: 8px 7px;
}

/* ── The note ────────────────────────────────────────────────────────── */

#itemEditModal .ie-note-wrap {
    position: relative;
}

#itemEditModal .ie-note-count {
    position: absolute;
    right: 10px;
    bottom: 8px;
    font-size: 10.5px;
    font-variant-numeric: tabular-nums;
    color: var(--information-color);
    pointer-events: none;
}

/* ── Add-ons card ────────────────────────────────────────────────────── */

#itemEditModal .ie-card-addons .ie-addon-tags {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 0;
}

/* A full-width row in this column, not a pill: the name gets the whole width
   and the steppers line up down the card. */
#itemEditModal .ie-card-addons .ie-addon-tag {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 8px 8px 11px;
    border-radius: 11px;
}

#itemEditModal .ie-card-addons .ie-addon-tag-name {
    flex: 1 1 auto;
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.3;
}

#itemEditModal .ie-addon-avail {
    margin-top: 12px;
    padding-top: 11px;
    border-top: 1px solid var(--border-color);
}

#itemEditModal .ie-avail-head {
    margin-bottom: 8px;
}

#itemEditModal .ie-avail-title {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

/* One column in this width, and a row rather than a stacked card. */
#itemEditModal .ie-card-addons .ie-addons {
    grid-template-columns: 1fr;
    max-height: 176px;
}

#itemEditModal .ie-card-addons .ie-addon {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 11px;
}

#itemEditModal .ie-addon-end {
    display: flex;
    align-items: center;
    gap: 9px;
    flex-shrink: 0;
}

#itemEditModal .ie-addon-action {
    padding: 3px 9px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--primary-color) 14%, transparent);
    color: var(--primary-ink, var(--primary-color));
    font-size: 10.5px;
    font-weight: 800;
    white-space: nowrap;
}

#itemEditModal .ie-addon.is-on .ie-addon-action {
    background: color-mix(in srgb, var(--information-color) 16%, transparent);
    color: var(--information-color);
}

/* ── Price breakdown ─────────────────────────────────────────────────── */

#itemEditModal .ie-breakdown {
    /* Sticks to the bottom of the scrollport. The left column is the taller
       of the two, so without this the total — the number being agreed to —
       sat below the fold on a 768px screen. */
    position: sticky;
    bottom: 0;
    padding: 14px;
    border: 1px solid var(--border-color);
    border-radius: 14px;
    /* The one emphasised surface in the dialog: it is the number the cashier
       is actually agreeing to. Built from the accent, not a hardcoded navy, so
       it follows the store's palette and both themes. */
    background: color-mix(in srgb, var(--primary-color) 10%, var(--background-color));
}

#itemEditModal .ie-bd-title {
    margin: 0 0 10px;
    font-size: 10.5px;
    font-weight: 800;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: var(--information-color);
}

#itemEditModal .ie-bd-list {
    margin: 0 0 10px;
}

#itemEditModal .ie-bd-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 6px;
    font-size: 12px;
}

#itemEditModal .ie-bd-row[hidden] {
    display: none;
}

#itemEditModal .ie-bd-row dt {
    min-width: 0;
    color: var(--information-color);
    font-weight: 600;
}

#itemEditModal .ie-bd-row dd {
    margin: 0;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--text-color);
}

#itemEditModal .ie-bd-row dd.is-plus {
    color: var(--primary-ink, var(--primary-color));
}

#itemEditModal .ie-bd-row dd.is-minus {
    color: var(--tm-occupied, #e74c3c);
}

#itemEditModal .ie-bd-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--border-color);
}

#itemEditModal .ie-bd-vat {
    margin: 6px 0 0;
    font-size: 10.5px;
    color: var(--information-color);
}

#itemEditModal .ie-bd-vat[hidden] {
    display: none;
}

/* ── The footer's running total ──────────────────────────────────────── */

#itemEditModal .ie-save-total {
    padding-left: 9px;
    margin-left: 9px;
    border-left: 1px solid color-mix(in srgb, var(--on-primary, #fff) 35%, transparent);
    font-variant-numeric: tabular-nums;
}

/* ── One column when there is not room for two ───────────────────────── */

@media (max-width: 900px) {
    #itemEditModal .ie-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ══ Item editor: readable at every size ═══════════════════════════════
   The body scrolls as one; nothing inside is pinned or squeezed. */

/* In the flow, not sticky: pinned to the bottom of the scrollport it slid over
   the add-on list as soon as a line carried a few add-ons. The total it was
   guarding is also on the UPDATE ITEM button, which never scrolls away. */
#itemEditModal .ie-breakdown {
    position: static;
}

/* media-sizes.css shrinks every .modal to 72-78% at exactly 1366, 1440 and
   1600px wide. That made this editor's text ~9px; the payment modal is
   exempted the same way. */
#itemEditModal {
    zoom: 1 !important;
    -moz-transform: none !important;
}

/* The add-ons card lays itself out by its own width. */
#itemEditModal .ie-card-addons {
    container-type: inline-size;
    container-name: ieaddons;
}

#itemEditModal .ie-card-addons .ie-addon-tag {
    flex-wrap: wrap;
    row-gap: 7px;
    font-size: 13px;
}

/* A wide card: the name takes the room left beside the controls and wraps
   inside it, so the controls stay together on the right. */
#itemEditModal .ie-card-addons .ie-addon-tag-name {
    flex: 1 1 0;
    min-width: 120px;
}

/* The chosen add-ons never get a scroll area of their own (a half-cut row in a
   nested scroller is hard to read and to drag); the dialog body scrolls. */
#itemEditModal .ie-card-addons .ie-addon-tags {
    max-height: none;
    overflow: visible;
}

/* Bigger targets for the per-add-on controls. */
#itemEditModal .ie-addon-step {
    width: 32px;
    min-height: 30px;
    font-size: 16px;
}

#itemEditModal .ie-addon-count {
    min-width: 32px;
    font-size: 13px;
}

#itemEditModal .ie-addon-tag-remove {
    width: 30px;
    height: 30px;
    font-size: 19px;
}

#itemEditModal .ie-card-addons .ie-addon-name {
    font-size: 13px;
}

/* A narrow card gives the name its own line, then the controls under it:
   stepper on the left, price and remove on the right. */
@container ieaddons (max-width: 460px) {
    #itemEditModal .ie-card-addons .ie-addon-tag-name {
        flex: 1 1 100%;
    }

    #itemEditModal .ie-card-addons .ie-addon-tag-price {
        margin-left: auto;
    }
}

/* A short screen: the cards tighten a little. */
@media (max-height: 820px) {
    #itemEditModal .ie-card-addons .ie-addon-tags {
        max-height: none;
    }
}

@media (max-height: 700px) {
    #itemEditModal .ie-card {
        padding: 10px 12px;
    }

    #itemEditModal .ie-grid,
    #itemEditModal .ie-col {
        gap: 10px;
    }
}

/* Full screen at every resolution: the editor takes the whole screen, the two
   columns run its full height, and the available add-ons list grows into
   whatever height is left instead of sitting in a small fixed box. The body
   scrolls only when a line genuinely has more than fits. */
#itemEditModal .tp-modal-container,
#itemEditModal .tp-modal-container.tp-lg {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    height: 100dvh;
    max-height: 100vh;
    max-height: 100dvh;
    border: 0;
    border-radius: 0;
}

#itemEditModal .tp-modal-body {
    display: flex;
    flex-direction: column;
}

#itemEditModal .ie-grid {
    flex: 1 0 auto;
    align-items: stretch;
}

#itemEditModal .ie-card-addons {
    /* Hugs its rows rather than filling the column. flex: 1 1 auto here is
       what left one "PEARL" row sitting in half a screen of empty box: the
       card, its .ie-addon-avail wrapper and the list below it were all
       stretching to the tallest thing in the row, so the list grew whether or
       not it had anything to put in it. */
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
}

#itemEditModal .ie-addon-avail {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
}

#itemEditModal .ie-card-addons .ie-addons {
    /* A list, not a panel. min-height: 168px / max-height: none made the box
       reserve a fixed block of empty space whatever it held; the list now
       grows with its rows up to the cap the base rule sets, and scrolls past
       it. max-height is restated because the base rule's 190px is the one
       that should still apply. */
    flex: 0 0 auto;
    min-height: 0;
    max-height: 190px;
}

/* Only the note grows; the other cards keep their natural height (the
   discount card carries a flex: 1 from an older row layout). */
#itemEditModal .ie-col-main > .ie-card {
    flex: 0 0 auto;
}

/* The note gets the spare height in its column the same way. */
#itemEditModal .ie-col-main > .ie-card:last-child {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}

#itemEditModal .ie-col-main > .ie-card:last-child .ie-note-wrap {
    flex: 1 1 auto;
    display: flex;
}

#itemEditModal .ie-col-main > .ie-card:last-child .ie-note {
    flex: 1 1 auto;
    min-height: 84px;
}

/* Full screen leaves room to spare on a tall screen (1080p and up), so the
   text and the controls a cashier reads and taps grow with it. A 768px screen
   keeps the compact sizes: its height is needed for the two add-on lists. */
@media (min-width: 1100px) and (min-height: 860px) {
    #itemEditModal .tp-modal-title {
        font-size: 22px;
    }

    #itemEditModal .tp-modal-subtitle {
        font-size: 14px;
    }

    #itemEditModal .ie-card {
        padding: 16px 18px;
    }

    #itemEditModal .ie-card-title,
    #itemEditModal .ie-avail-title {
        font-size: 12px;
    }

    #itemEditModal .ie-card-note,
    #itemEditModal .ie-card-sub {
        font-size: 13px;
    }

    #itemEditModal .ie-step {
        width: 64px;
        height: 54px;
        font-size: 24px;
    }

    #itemEditModal .ie-step-value {
        width: 110px;
        height: 54px;
        font-size: 22px;
    }

    #itemEditModal .ie-preset {
        min-width: 54px;
        padding: 12px 14px;
        font-size: 15px;
    }

    #itemEditModal .ie-disc-tab,
    #itemEditModal .ie-disc-input {
        font-size: 15px;
    }

    #itemEditModal .ie-chip {
        padding: 9px 15px;
        font-size: 14px;
    }

    #itemEditModal .ie-note {
        font-size: 15px;
    }

    #itemEditModal .ie-card-addons .ie-addon-tag,
    #itemEditModal .ie-card-addons .ie-addon-tag-name,
    #itemEditModal .ie-card-addons .ie-addon-name {
        font-size: 14.5px;
    }

    #itemEditModal .ie-addon-tag-price,
    #itemEditModal .ie-addon-price {
        font-size: 14.5px;
    }

    #itemEditModal .ie-addon-action {
        padding: 5px 12px;
        font-size: 12.5px;
    }

    #itemEditModal .ie-addon-search {
        height: 46px;
        font-size: 15px;
    }

    #itemEditModal .ie-card-addons .ie-addon {
        padding: 12px 14px;
        height: 5vh;
    }

    #itemEditModal .ie-bd-row {
        font-size: 15px;
    }

    #itemEditModal .tp-modal-footer button {
        min-height: 52px;
        font-size: 16px;
    }
}

/* Phone-sized or a narrow window: the dialog takes the whole screen and every
   row is allowed to wrap. */
@media (max-width: 640px) {
    #itemEditModal .ie-field-disc .ie-discount {
        flex-wrap: wrap;
    }

    #itemEditModal .ie-field-disc .ie-disc-mode {
        flex: 1 1 100%;
    }

    #itemEditModal .tp-modal-footer {
        flex-wrap: wrap;
        gap: 8px;
    }

    #itemEditModal .ie-footer-end {
        flex: 1 1 100%;
        margin-left: 0;
    }

    #itemEditModal .ie-footer-end {
        flex-direction: column-reverse;
        gap: 8px;
    }

    #itemEditModal .ie-footer-end > button,
    #itemEditModal #ieVoidBtn {
        width: 100%;
        min-height: 46px;
        justify-content: center;
    }

    #itemEditModal #ieVoidBtn {
        flex: 1 1 100%;
        order: 2;
    }
}

/* Two columns: the dialog itself never scrolls. It is a fixed full-screen
   layout, and each list scrolls inside its own box — the chosen add-ons, the
   available add-ons — while the price breakdown stays in view at the bottom.
   Only when the screen is too short even for that does the left column scroll
   on its own. One column (phones, narrow windows) keeps a scrolling body:
   there is no room for a fixed layout there. */
@media (min-width: 901px) and (min-height: 560px) {
    #itemEditModal .tp-modal-body {
        overflow: hidden;
    }

    #itemEditModal .ie-grid {
        flex: 1 1 0;
        min-height: 0;
    }

    #itemEditModal .ie-col {
        min-height: 0;
    }

    #itemEditModal .ie-col-main {
        overflow-y: auto;
    }

    #itemEditModal .ie-col-side {
        overflow: hidden;
    }

    #itemEditModal .ie-card-addons {
        flex: 1 1 0;
        min-height: 0;
    }

    /* The chosen add-ons: as tall as they need, until the card runs out of
       room — then they scroll in place. */
    #itemEditModal .ie-card-addons .ie-addon-tags {
        flex: 0 1 auto;
        min-height: 0;
        max-height: none;
        overflow-y: auto;
        padding-right: 2px;
    }

    /* The available list keeps room for a few rows and scrolls itself. */
    #itemEditModal .ie-addon-avail {
        flex: 1 1 0;
        min-height: 176px;
    }

    #itemEditModal .ie-card-addons .ie-addons {
        flex: 1 1 0;
        min-height: 0;
        max-height: none;
        overflow-y: auto;
    }

    #itemEditModal .ie-breakdown {
        flex: 0 0 auto;
    }
}

/* A short screen: the breakdown and the add-ons card tighten, so the two
   lists keep a usable height. */
@media (min-width: 901px) and (max-height: 820px) {
    #itemEditModal .ie-breakdown {
        padding: 10px 14px;
    }

    #itemEditModal .ie-bd-title {
        margin-bottom: 4px;
    }

    #itemEditModal .ie-bd-row {
        padding: 2px 0;
    }

    #itemEditModal .ie-card-addons .ie-card-sub {
        margin-bottom: 6px;
    }

    #itemEditModal .ie-addon-avail {
        margin-top: 8px;
        padding-top: 8px;
    }

    #itemEditModal .ie-avail-head {
        margin-bottom: 6px;
    }

    #itemEditModal .ie-locked-note {
        padding: 6px 10px;
        font-size: 11.5px;
    }
}

/* ── Top bar on tablets and phones ──────────────────────────────────────────
   Only the bar adapts here; the order / menu body below keeps its layout.
   pos-vertical.php adds tpv-topbar-wrap when the tiles do not fit beside the
   search, and always on a narrow screen: the search on the first row, then the
   tiles across the full width, wrapping onto as many rows as they need —
   every one still the same square. */
html.tp-vertical .tpv-topbar.tpv-topbar-wrap {
    flex-wrap: wrap;
    height: auto;
    row-gap: 6px;
    padding-top: 6px;
    padding-bottom: 0;
}

html.tp-vertical .tpv-topbar.tpv-topbar-wrap .tpv-search {
    order: 1;
    flex: 1 1 200px;
}

html.tp-vertical .tpv-topbar.tpv-topbar-wrap .tpv-topbar-actions {
    order: 3;
    flex: 1 1 100%;
}

html.tp-vertical .tpv-topbar.tpv-topbar-wrap .tpv-rail {
    flex-wrap: wrap;
    justify-content: flex-end;
    overflow: visible;
}

html.tp-vertical .tpv-topbar.tpv-topbar-wrap .tpv-rail::before {
    display: none;
}

/* Each tile carries a top hairline, so the rows are ruled off from the search
   row and from each other. */
html.tp-vertical .tpv-topbar.tpv-topbar-wrap .tpv-rail-btn,
html.tp-vertical .tpv-topbar.tpv-topbar-wrap .tpv-rail-btn + .tpv-rail-btn {
    border-width: 1px 1px 0;
    margin: 0 0 0 -1px;
}

/* Phones: smaller squares and no F-key line (no keyboard to press them on). */
@media (max-width: 560px) {
    html.tp-vertical {
        --tpv-tile: 64px;
        --tpv-tile-pad: 6px;
    }

    html.tp-vertical .tpv-topbar .tpv-rail-key,
    html.tp-vertical .tpv-topbar .tpv-rail-context .tpv-context-sub {
        display: none;
    }

    html.tp-vertical .tpv-topbar .tpv-search {
        min-width: 0;
    }
}

/* Phone landscape: one slim row of smaller squares, no F-keys. */
@media (max-height: 500px) {
    html.tp-vertical {
        --tpv-tile: 54px;
        --tpv-tile-pad: 4px;
        --tpv-tile-icon: 18px;
        --tpv-tile-font: 9.5px;
    }

    html.tp-vertical .tpv-topbar {
        gap: 8px;
        padding: 0 8px;
    }

    html.tp-vertical .tpv-topbar .tpv-search {
        min-width: 120px;
    }

    html.tp-vertical .tpv-topbar .tpv-rail-key,
    html.tp-vertical .tpv-topbar .tpv-rail-context .tpv-context-sub {
        display: none;
    }
}
