/*
 * Mobile layer — phone and small-tablet presentation for the admin app.
 *
 * Everything here lives inside a max-width media query, so the desktop UI is
 * byte-for-byte what it was. The breakpoint is 991.98px to line up with the two
 * that already exist: the sidebar is `lg:` (Tailwind) and the drawer javascript
 * in layouts/partials/javascripts.blade.php checks `>= 992`. A third,
 * different, breakpoint would show as a band of screen widths where the
 * sidebar, the drawer and these styles disagree.
 *
 * The approach is deliberately global. 389 views use Bootstrap's col-md-*
 * grid, which already stacks below 992px, so layout is mostly fine; what
 * actually breaks on a phone are the shared components — tables, buttons,
 * modals, DataTables chrome. Styling those by class fixes every page at once
 * instead of editing hundreds of templates.
 */

/* Bottom navigation height + the phone's own home-indicator inset. Used to
   keep the last row of a page clear of the fixed bar. */
:root {
    --mobile-nav-height: 56px;
    --mobile-safe-bottom: env(safe-area-inset-bottom, 0px);
    --mobile-accent: #3c8dbc; /* AdminLTE blue-light, the default skin here */
}

@media (max-width: 991.98px) {

    /* ---------------------------------------------------------------- base */

    /* The app shell is a fixed-height flex column with its own scroller
       (#scrollable-container). The bar sits over the bottom of that scroller,
       so the scroller needs to end above it or the last row is unreachable. */
    #scrollable-container {
        padding-bottom: calc(var(--mobile-nav-height) + var(--mobile-safe-bottom) + 8px);
        -webkit-overflow-scrolling: touch;

        /* Hide the scroller's own scrollbar on phones. Android draws a classic
           one for a non-body scroller like this, which takes real width off the
           content and reads as a desktop artefact beside a touch UI.

           Hidden, not disabled: the element still scrolls exactly as before,
           and a finger needs no track to drag. Scoped to this one element
           rather than applied globally -- a table that scrolls sideways still
           shows its bar, which is the only hint that there is more to the
           right. (app.css carries a commented-out global attempt at this;
           leave it commented.) */
        scrollbar-width: none;      /* Firefox */
        -ms-overflow-style: none;   /* legacy Edge */
    }

    #scrollable-container::-webkit-scrollbar {
        width: 0;
        height: 0;
    }

    /* Nothing should push the page sideways. A single wide element otherwise
       makes the whole app scroll horizontally, which reads as broken. */
    body,
    .content-wrapper,
    .content {
        overflow-x: hidden;
    }

    .content,
    section.content {
        padding: 10px 8px;
    }

    .content-header {
        padding: 10px 8px 0;
    }

    .content-header > h1 {
        font-size: 20px;
        line-height: 1.3;
    }

    .content-header > h1 > small {
        display: block;
        margin: 4px 0 0;
        font-size: 13px;
    }

    /* Breadcrumbs are desktop furniture; on a phone they cost a row of height
       and tell the user what the heading already says. */
    .content-header > .breadcrumb {
        display: none;
    }

    /* ----------------------------------------------------------- containers */

    .box {
        margin-bottom: 12px;
        border-radius: 8px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, .08);
    }

    .box-header {
        padding: 12px 12px 8px;
    }

    .box-header > .box-title {
        font-size: 16px;
        line-height: 1.3;
    }

    .box-body {
        padding: 12px 10px;
    }

    /* Header tool buttons wrap under the title rather than overlapping it. */
    .box-header > .box-tools {
        position: static;
        display: block;
        margin-top: 8px;
    }

    /* -------------------------------------------------------------- tables */

    /*
     * Two treatments, chosen per table by js/mobile.js.
     *
     * A table of five columns or more is rebuilt as one card per row, because
     * no amount of scrolling makes a nineteen-column grid readable on a phone.
     * The products table is the case that proves it: it is declared
     * `width: 100%; table-layout: fixed`, so nineteen columns get about
     * eighteen pixels each and the text overlaps its neighbours -- and being
     * pinned to 100% it cannot scroll out of that either.
     *
     * Narrower tables stay as tables, with a scrolling parent for the rare one
     * that still does not fit.
     */

    .table-responsive {
        width: 100%;
        margin-bottom: 0;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        border: 0;
    }

    /*
     * Holding cards there is nothing to scroll, and the wrapper must stop being
     * a scroll container at all: Bootstrap sets overflow-y: hidden here, and a
     * clipping ancestor cuts off the Actions dropdown when it opens. Both axes
     * have to be released -- CSS promotes `visible` back to `auto` if the other
     * axis is not visible too.
     */
    .table-responsive.holds-cards {
        overflow-x: visible;
        overflow-y: visible;
    }

    .table-responsive > .table {
        margin-bottom: 0;
    }

    /* ---------------------------------------------------------- card layout */

    /* Beats the inline `width: 100%; table-layout: fixed` on the markup. */
    .table.mobile-cards {
        display: block;
        width: 100% !important;
        table-layout: auto !important;
        border: 0;
        background: none;
    }

    /* The headings now live on each cell, so the header row is redundant. */
    .table.mobile-cards > thead {
        display: none;
    }

    .table.mobile-cards > tbody {
        display: block;
    }

    /*
     * The card. Laid out as a wrapping flex row so the pieces can be ordered
     * independently of the column order in the table: select box, thumbnail and
     * name share the top line, the labelled values stack under it, and the
     * actions sit at the foot however early the Action column appears.
     */
    .table.mobile-cards > tbody > tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        margin: 0 0 10px;
        padding: 10px 12px;
        background: #fff !important;
        border: 1px solid #e5e7eb;
        border-radius: 10px;
        box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
    }

    .table.mobile-cards > tbody > tr:last-child {
        margin-bottom: 0;
    }

    /*
     * No !important on display: DataTables hides columns with an inline
     * display:none, and an inline style loses to !important. Leaving it off is
     * what keeps hidden columns hidden.
     */
    .table.mobile-cards > tbody > tr > td {
        display: flex;
        align-items: flex-start;
        gap: 10px;
        flex: 0 0 100%;
        width: auto;
        padding: 9px 0;
        border: 0;
        border-top: 1px solid #f3f4f6;
        font-size: 14px;
        line-height: 1.45;
        /* The opposite of the grid treatment: values wrap instead of colliding. */
        white-space: normal;
        word-break: break-word;
    }

    /* ---- the top line: select box, thumbnail, name ---- */

    /* Media cells shrink to their contents and lead the card. */
    .table.mobile-cards > tbody > tr > td.is-unlabelled:not(.is-action) {
        flex: 0 0 auto;
        order: -3;
        padding: 0 10px 0 0;
        border-top: 0;
    }

    .table.mobile-cards > tbody > tr > td.is-title {
        flex: 1 1 40%;
        order: -2;
        padding: 0;
        border-top: 0;
        font-size: 15px;
        font-weight: 600;
        color: #111827;
    }

    /* The name is the headline; repeating "Product:" in front of it is noise. */
    .table.mobile-cards > tbody > tr > td.is-title::before {
        display: none;
    }

    /* The first labelled value starts the list, so it draws the divider. */
    .table.mobile-cards > tbody > tr > td:not(.is-title):not(.is-unlabelled):not(.is-action) {
        margin-top: 0;
    }

    /* ---- the foot: actions ---- */

    .table.mobile-cards > tbody > tr > td.is-action {
        order: 99;
        flex: 0 0 100%;
        padding-bottom: 0;
    }

    .table.mobile-cards > tbody > tr > td.is-action::before {
        display: none;
    }

    .table.mobile-cards > tbody > tr > td::before {
        content: attr(data-label);
        flex: 0 0 40%;
        min-width: 0;
        color: #6b7280;
        font-size: 12px;
        font-weight: 600;
        line-height: 1.5;
        text-transform: uppercase;
        letter-spacing: .02em;
        word-break: break-word;
    }

    /* The select checkbox, the product thumbnail, the actions button: these
       carry no heading worth printing, so they run the full width. */
    .table.mobile-cards > tbody > tr > td.is-unlabelled::before {
        display: none;
    }

    .table.mobile-cards > tbody > tr > td.is-unlabelled {
        justify-content: flex-start;
    }

    /* An empty value would otherwise render as a heading against blank space. */
    .table.mobile-cards > tbody > tr > td.is-blank {
        display: none;
    }

    /*
     * A column the page has switched off stays off.
     *
     * `td { display: flex }` above is three classes and an element, so it beat
     * Bootstrap's single-class .hide and every column the sell screen hides --
     * inline tax, discount, price-inc-tax -- came back as soon as the row
     * became a card. Inline styles still win over both, which is what keeps
     * DataTables' own column hiding working.
     */
    .table.mobile-cards > tbody > tr > td.hide,
    .table.mobile-cards > tbody > tr > td.hidden {
        display: none;
    }

    /* "No matching records found" and friends. */
    .table.mobile-cards > tbody > tr > td[colspan] {
        display: block;
        text-align: center;
        color: #6b7280;
    }

    .table.mobile-cards img {
        max-width: 56px;
        height: auto;
        border-radius: 6px;
    }

    /* Actions should be reachable without hunting. */
    .table.mobile-cards .btn-group,
    .table.mobile-cards .dropdown {
        width: 100%;
    }

    .table.mobile-cards .btn-group > .btn {
        width: 100%;
        min-height: 40px;
    }

    /*
     * Cards inside DataTables' own scroll box.
     *
     * A table declared `scrollY: "75vh", scrollX: true` is moved by DataTables
     * into a fixed-height, sideways-scrolling box with its header cloned into
     * a second table above it. js/mobile.js now builds cards in there too --
     * it used to skip such tables entirely, which is why Products, All sales
     * and Contacts, the three widest lists in the application, never got the
     * card layout that was written for them.
     *
     * What the box does is wrong for cards in every respect: the cards carry
     * their own labels so the cloned header is a duplicate, and a list that
     * scrolls inside a 75vh window nested in the page's own scroller is two
     * scrollbars answering one gesture. The heights and widths DataTables
     * writes are inline, hence !important.
     */
    .dataTables_scroll.holds-cards {
        overflow: visible;
    }

    .dataTables_scroll.holds-cards .dataTables_scrollHead,
    .dataTables_scroll.holds-cards .dataTables_scrollFoot {
        display: none !important;
    }

    .dataTables_scroll.holds-cards .dataTables_scrollBody {
        max-height: none !important;
        width: auto !important;
        overflow: visible !important;
        border: 0 !important;
    }

    /* --------------------------------------------------- tables kept as grids */

    /* Bootstrap pins .table to width:100%; min-width lets a narrow table size
       to its content so its wrapper can scroll, rather than squeezing columns
       until the text overlaps. */
    .table.mobile-scrolls {
        width: auto !important;
        min-width: 100%;
        table-layout: auto !important;
    }

    .table.mobile-scrolls > thead > tr > th,
    .table.mobile-scrolls > tbody > tr > td {
        padding: 10px 8px;
        font-size: 13px;
        white-space: nowrap;
    }

    /* Action buttons in a row stay tappable rather than shrinking to icons. */
    .table .btn-group > .btn,
    .table .btn {
        min-height: 34px;
    }

    /* --------------------------------------------------------- datatables */

    /* Its chrome is laid out as a desktop toolbar; stacked it stops colliding. */
    .dataTables_wrapper .row > div[class^="col-"] {
        width: 100%;
        float: none;
        text-align: left !important;
    }

    .dataTables_length,
    .dataTables_filter,
    .dataTables_info,
    .dataTables_paginate {
        float: none !important;
        width: 100%;
        margin-bottom: 8px;
        text-align: left !important;
    }

    .dataTables_filter input {
        width: 100% !important;
        margin-left: 0 !important;
        min-height: 44px;
    }

    .dataTables_filter label {
        display: block;
        width: 100%;
    }

    .dataTables_paginate .pagination {
        margin: 4px 0;
        flex-wrap: wrap;
    }

    .dataTables_paginate .paginate_button > a,
    .pagination > li > a,
    .pagination > li > span {
        min-width: 40px;
        min-height: 40px;
        line-height: 28px;
        text-align: center;
    }

    /*
     * Sales Order loses the toolbar entirely on a phone.
     *
     * Export CSV, Export Excel, Print, Column visibility and Export PDF are a
     * desktop toolbar: five buttons nobody presses standing at a counter, and
     * on a 390px screen they take two full rows directly under the heading and
     * push the orders themselves below the fold. "Show 25 entries" goes with
     * them -- the default is sensible and there is paging underneath, so the
     * control only exists to be tapped by accident.
     *
     * Scoped to this page by the body class the layout writes from the route,
     * so every other list keeps its exports until someone asks otherwise, and
     * nothing is taken away on a desktop.
     */
    .page-sales-order .dt-buttons,
    .page-sales-order .dataTables_length {
        display: none !important;
    }

    /*
     * The filter panel, opened.
     *
     * Five stacked label-and-select pairs at full size came to 424px -- more
     * than a phone screen, so choosing the second filter meant scrolling and
     * the orders themselves were two screens down. Two to a row, with the
     * caption in the same small grey style the cards use, brings it to about
     * half that. The date range keeps the full width: it is the one control
     * whose value is a sentence rather than a word.
     */
    .page-sales-order #collapseFilter .box-body {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 10px 10px;
    }

    .page-sales-order #collapseFilter .box-body > div {
        width: 100%;
        float: none;
        padding: 0;
    }

    /* The date range is last in the markup whether or not the shipping-status
       filter is rendered, so this follows it rather than a column count. */
    .page-sales-order #collapseFilter .box-body > div:last-child {
        grid-column: 1 / -1;
    }

    .page-sales-order #collapseFilter .form-group {
        margin-bottom: 0;
    }

    .page-sales-order #collapseFilter .form-group > label {
        margin-bottom: 4px;
        font-size: 11px;
        font-weight: 600;
        line-height: 1.4;
        color: #6b7280;
        text-transform: uppercase;
        letter-spacing: .02em;
    }

    /* A filter header that only says "Filters" should not look like a heading
       you cannot press. */
    .page-sales-order .box-header > .box-title > a {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        min-height: 36px;
        text-decoration: none;
    }

    /*
     * Adding an order floats over the list, bottom right.
     *
     * It was a full-width button above the search box, which is a fine place
     * for it until the list is longer than a screen -- from then on the one
     * thing you came to the page to do is wherever you last scrolled away
     * from. Fixed, it is in the same place whatever the list is doing.
     *
     * `position: fixed` survives here because nothing between it and the body
     * carries a transform, filter or perspective; `#scrollable-container` is
     * only `position: relative`, which makes a containing block for
     * `absolute` and not for `fixed`.
     *
     * A transform on **the scroller** -- or on anything inside it that wraps
     * this button -- would silently pin it to the scrolling box instead, and
     * it would scroll away with the list. `position` still computes as
     * `fixed` in that state, so only scrolling catches it: the test scrolls
     * the list and asserts the button has not moved. (Measured: a transform
     * on the shell *around* the scroller is harmless, because that element
     * does not scroll.)
     */
    .page-sales-order .box-header > .box-tools {
        position: fixed;
        right: 14px;
        /* Clear of the bottom bar, and of the phone's own home indicator. */
        bottom: calc(var(--mobile-nav-height) + var(--mobile-safe-bottom) + 14px);
        z-index: 1030;
        width: auto;
        margin: 0;
    }

    .page-sales-order .box-header > .box-tools .tw-dw-btn,
    .page-sales-order .box-header > .box-tools > a {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        width: auto;
        min-height: 48px;
        padding: 0 18px;
        border-radius: 999px;
        box-shadow: 0 6px 18px rgba(20, 30, 60, .24);
        /* Stated rather than inherited: the button carries its own horizontal
           padding, and Tailwind's preflight sets this globally in the app --
           but a rule that silently depends on another stylesheet's reset
           breaks the day that stylesheet is not there. */
        box-sizing: border-box;
        text-align: center;
    }

    /*
     * An order card.
     *
     * The generic treatment prints ten equal label-and-value rows, so one
     * order filled the screen and the number you are looking for -- the order
     * no -- was the second row, in the same grey as everything else. Here the
     * order no and its status lead, the customer follows, and the rest of the
     * identifying detail collapses into one quiet line.
     *
     * The cells are picked by position, which is safe to do here and nowhere
     * else: sales_order/index.blade.php declares the ten columns in a fixed
     * order in its own DataTable config, so they cannot be reordered by a
     * setting the way the sell screen's can.
     */
    .page-sales-order #sell_table.mobile-cards > tbody > tr {
        padding: 12px;
        gap: 0 8px;
        align-items: center;
    }

    /*
     * The top line: the order number, where it is from, and where it is up to.
     *
     * The number and the status take equal shares of the line and pull to
     * their own ends, so whatever is left in the middle is centred on the
     * card -- and the branch centred in it is centred on the screen. Sizing
     * the number to its content instead would centre the branch between two
     * unequal ends, which is a different place and looks like a mistake.
     */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(3) {
        order: -6;
        flex: 1 1 0;
        min-width: 0;
        padding: 0;
        border-top: 0;
        font-size: 16px;
        font-weight: 700;
    }

    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(6) {
        order: -5;
        flex: 0 1 auto;
        min-width: 0;
        padding: 0 6px;
        border-top: 0;
        justify-content: center;
        font-size: 13px;
        font-weight: 600;
        color: #4b5563;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(7) {
        order: -4;
        flex: 1 1 0;
        min-width: 0;
        padding: 0;
        border-top: 0;
        justify-content: flex-end;
    }

    /* Who it is for. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(4) {
        order: -3;
        flex: 0 0 100%;
        padding: 6px 0 0;
        border-top: 0;
        font-size: 15px;
        font-weight: 600;
        color: #111827;
    }

    /*
     * Date, a way to ring them, and who raised it: three facts that identify
     * the order without being what you scan for, so they share one quiet line.
     */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(2),
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(5),
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(10) {
        order: -2;
        flex: 0 0 auto;
        align-items: center;
        padding: 3px 0 0;
        border-top: 0;
        font-size: 12px;
        line-height: 1.6;
        color: #6b7280;
    }

    /* A separator that disappears with the value it follows. Only between the
       two pieces of text: the call button ends the line on its own. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(2)::after {
        content: "\00b7";
        margin-left: 8px;
        color: #c8ccd4;
    }

    /* Ring them, and act on the order: the two things you do *to* a card, at
       the end of its one quiet line rather than each on a row of its own. The
       call button stays last, hard against the edge, because that is where the
       thumb already goes for it. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(5) {
        order: -1;
        margin-left: auto;
        padding-right: 0;
    }

    /*
     * ...and the two pieces of text beside it give way rather than pushing it
     * off the line. This is what actually went wrong live: the fixture's
     * "added by" was `admin` and the real data's is a customer's full name, so
     * the line wrapped, Actions took its end and the call button dropped to
     * the next row -- at the left, where nothing was holding it right. The
     * call button's position must not depend on how long somebody's name is.
     */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(2),
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(10) {
        /*
         * Bounded, not merely shrinkable. In a wrapping flex row the items are
         * packed by their content size and wrap when the line is full --
         * shrinking only happens afterwards, within a line -- so `flex-shrink`
         * alone does not stop a long name pushing the call button onto the
         * next row. A max-width caps the base size, so the line never
         * overflows in the first place.
         *
         * And `display: block` rather than the card's flex, because
         * `text-overflow` has nothing to act on in a flex container: the text
         * becomes an anonymous item and the ellipsis never appears.
         */
        display: block;
        max-width: 42%;
        min-width: 0;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* The number itself is not what anyone does with it on a phone; this is a
       button that rings the customer. js/mobile.js builds the link. */
    .page-sales-order #sell_table.mobile-cards .mobile-call {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 34px;
        height: 34px;
        margin: -6px 0;
        border: 1px solid #cfe0f8;
        border-radius: 999px;
        background: #f2f7ff;
        color: #1a56db;
        font-size: 13px;
    }

    /* Seven cells carry their meaning in the value, so the caption is noise --
       and on the meta line it is worse than noise: "CONTACT NUMBER" against a
       40% column broke "LOCATION" across two lines. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(2)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(3)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(4)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(5)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(6)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(7)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(10)::before {
        display: none;
    }

    /* What is left keeps a caption, in a narrower column: "Quantity
       remaining" against 40% wrapped onto two lines on its own. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(8),
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(9) {
        padding: 8px 0;
        font-size: 13px;
    }

    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(8)::before,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(9)::before {
        flex-basis: 46%;
        font-size: 11px;
    }

    /* The first of them draws the line under the heading block. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td:nth-child(8) {
        margin-top: 8px;
        border-top: 1px solid #eef0f4;
    }

    /*
     * Actions sits under the call button, on a line of its own, right-aligned
     * so the two line up. Sharing the meta line was tried first and is what
     * pushed the call button off it -- and a menu has no business competing
     * for room with the facts that identify the order. It is still not the
     * full-width blue bar it was at the foot of the card, which was the
     * loudest thing in the list and the least urgent.
     */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td.is-action {
        order: 0;
        flex: 0 0 100%;
        justify-content: flex-end;
        padding: 6px 0 0;
        border-top: 0;
    }

    /* The generic card makes .btn-group 100% wide so an action is easy to hit;
       here that left the group filling the row with the button at its left,
       so `justify-content: flex-end` on the cell had nothing to push. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td.is-action .btn-group,
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td.is-action .dropdown {
        width: auto;
    }

    /*
     * The menu opens leftwards from the button, not rightwards off the screen.
     *
     * Bootstrap anchors a dropdown at `left: 0`, which was fine while the
     * button was full width and is not now it sits at the card's right edge:
     * every label past "Delete" was cut off at the screen edge. Widening it
     * would not have helped -- mobile.css sets `overflow-x: hidden` on body
     * and .content precisely so nothing pushes the page sideways, so the part
     * past the edge is clipped rather than reachable.
     *
     * Above the floating Add button too (z-index 1030): a menu that opens
     * under the thing you have to move to read it is worse than no menu.
     */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td.is-action .dropdown-menu {
        right: 0;
        left: auto;
        z-index: 1040;
        min-width: 180px;
        /* Never wider than the screen, whatever a translation makes of
           "Edit Shipping Address". */
        max-width: calc(100vw - 28px);
    }

    /* Which means a long label wraps rather than being cut. */
    .page-sales-order #sell_table.mobile-cards > tbody > tr > td.is-action .dropdown-menu > li > a {
        white-space: normal;
    }

    .page-sales-order #sell_table.mobile-cards > tbody > tr > td.is-action .btn {
        width: auto;
        min-height: 34px;
        padding: 4px 12px;
        font-size: 12px;
    }

    /* The count is the only thing left that says how many orders there are,
       and it sat in grey small print under the paging. */
    .page-sales-order .dataTables_info {
        font-size: 13px;
        color: #444;
        padding-top: 4px;
    }

    /* ------------------------------------------ customers and suppliers */

    /*
     * The same two pieces of desktop toolbar that came off the Sales Order
     * list, for the same reasons: five export buttons take two rows at 390px
     * and push the list below the fold, and "Show 25 entries" exists only to
     * be tapped by accident. The search box, the count and the paging stay.
     *
     * Scoped to the page, so suppliers get it as well as customers -- they are
     * the same screen with a different `type` -- and nothing changes on a
     * desktop.
     */
    .page-contacts .dt-buttons,
    .page-contacts .dataTables_length {
        display: none !important;
    }

    /*
     * A customer reads as a customer, not as twelve rows of grey labels.
     *
     * The generic card printed every one of the sixteen columns as an equal
     * label-and-value row, so one customer was about 430px -- a screen and a
     * half of one person, and the two things you actually came to look up,
     * their name and what they owe, were somewhere in the middle of it in the
     * same grey as their tax number.
     *
     * So: the name and the balance due lead, at the size of a heading; the id,
     * when they were added and a button that rings them share one quiet line;
     * and everything else keeps its caption but sits two to a row, which is
     * where most of the height goes. Nothing is hidden -- the figures a
     * counter needs are all still on the card, in about two thirds of the
     * space.
     *
     * The cells are picked by the cc-* class js/app.js puts on each column,
     * never by position: this table's reward-points column is conditional on
     * a business setting, so every position after it shifts, and the headings
     * the card labels itself from are translated. The class is the one hook
     * that is neither.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr {
        padding: 12px;
        gap: 0 8px;
        align-items: flex-start;
    }

    /*
     * Two line breaks, because `order` alone does not make one.
     *
     * The card is a wrapping flex row, so ordering the cells says what comes
     * first and nothing about what shares a line: the first attempt ordered
     * the name, the due, the id, the date and the call button correctly and
     * then fitted all five onto one line, which left the name 41px wide. A
     * flex item with a 100% basis fills a line on its own, so a zero-height
     * one is a break -- and a `tr` laid out as flex has exactly two boxes
     * going spare, which is exactly how many breaks this card needs.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr::after {
        content: "";
        flex: 0 0 100%;
        height: 0;
    }

    /* After the name and the due. */
    .page-contacts #contact_table.mobile-cards > tbody > tr::before {
        order: -45;
    }

    /* After the quiet line. */
    .page-contacts #contact_table.mobile-cards > tbody > tr::after {
        order: -15;
    }

    /* ---- the top line: who they are, and what they owe ---- */

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-name {
        order: -60;
        flex: 1 1 0;
        min-width: 0;
        align-self: center;
        padding: 0;
        border-top: 0;
        font-size: 16px;
        font-weight: 700;
        color: #111827;
    }

    /*
     * The one number a counter is looking up, so it keeps its caption -- a
     * bare figure at the right of a name would be ambiguous between the due,
     * the advance and the credit limit. Stacked and right-aligned rather than
     * captioned on the left, because "Total Sale Due" against a 40% column is
     * wider than the number it introduces.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-due {
        order: -50;
        display: block;
        flex: 0 0 auto;
        /* Held to the right by its own margin rather than by the name beside
           it: `name` is not required on a contact, and where it is empty
           mobile.js drops that cell -- which left the due leading the card
           from the left, reading as the customer's title. */
        margin-left: auto;
        max-width: 46%;
        padding: 0;
        border-top: 0;
        text-align: right;
        font-size: 16px;
        font-weight: 700;
        line-height: 1.3;
        color: #1f2937;
        white-space: nowrap;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-due::before {
        display: block;
        flex: none;
        font-size: 10px;
        line-height: 1.4;
        color: #9ca3af;
    }

    /* ---- the quiet line: id, when, and a way to ring them ---- */

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-id,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-added {
        flex: 0 0 auto;
        /*
         * Bounded, not merely shrinkable: in a wrapping flex row the items are
         * packed at their content size and wrap when the line is full, so
         * `flex-shrink` alone would let a long contact id push the call button
         * onto a row of its own -- at the left, where nothing holds it right.
         * That is the fault this card inherits from the Sales Order one, where
         * it shipped live.
         *
         * `display: block` because `text-overflow` has nothing to act on in a
         * flex container; the text becomes an anonymous item and the ellipsis
         * never appears.
         */
        display: block;
        max-width: 42%;
        min-width: 0;
        padding: 4px 0 0;
        border-top: 0;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        font-size: 12px;
        line-height: 1.6;
        color: #6b7280;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-id {
        order: -40;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-added {
        order: -30;
    }

    /* A separator that disappears with the value it follows. */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-id::after {
        content: "\00b7";
        margin-left: 8px;
        color: #c8ccd4;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-id::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-added::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-mobile::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-name::before {
        display: none;
    }

    /* Ringing them is what the number is for on a phone; js/mobile.js builds
       the link. Last on the line, hard against the card's edge, because that
       is where the thumb already goes for it. */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-mobile {
        order: -20;
        flex: 0 0 auto;
        align-self: center;
        align-items: center;
        margin-left: auto;
        padding: 4px 0 0;
        border-top: 0;
        font-size: 12px;
        color: #6b7280;
    }

    .page-contacts #contact_table.mobile-cards .mobile-call,
    .page-contacts #contact_table.mobile-cards .lu-call {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 34px;
        height: 34px;
        margin: -4px 0;
        border: 1px solid #cfe0f8;
        border-radius: 999px;
        background: #f2f7ff;
        color: #1a56db;
        font-size: 13px;
    }

    /* ---- the rest: captioned, two to a row ---- */

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-biz,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-tax,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-credit,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-opening,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-advance {
        /* Stated rather than inherited: two of the app's stylesheets set it
           globally, and a rule that silently depends on another file's reset
           breaks the day that file is not there. A content-box half-width
           plus padding overflows the card. */
        box-sizing: border-box;
        display: block;
        /* Two to a row. The card is the flex container, so this is a basis
           rather than a grid -- and a cell with nothing in it is dropped by
           js/mobile.js, so a customer with no tax number simply has one fewer
           cell rather than a captioned blank.
           Less half the gap: at a flat 50% the pair plus the 8px column gap
           comes to 100% + 8px, so every value took a line of its own and the
           card was barely shorter than the rows it replaced -- while still
           measuring half the card wide, which is what a width-only assertion
           called a pass. */
        flex: 0 0 calc(50% - 4px);
        min-width: 0;
        padding: 8px 8px 0 0;
        border-top: 0;
        font-size: 13px;
        line-height: 1.35;
        color: #1f2937;
    }

    /*
     * Five columns are not wanted on a phone.
     *
     * Email, pay term, reward points, the address and the sell-return due are
     * things you look up on a customer's own screen, not while scanning a
     * list, and they were half the height of the card. **On a phone only** --
     * the columns are untouched at desktop width and the exports still carry
     * them, so this hides them rather than removing them.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-email,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-payterm,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-rp,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-address,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-retdue {
        display: none;
    }

    /*
     * ...and a cell with nothing in it is still dropped.
     *
     * Every page rule above sets `display` at one id and three classes, which
     * is exactly what the generic `.is-blank { display: none }` re-stated here
     * would come to -- so whichever is written last wins, and a customer with
     * no pay term got a captioned blank the first time, a customer with no
     * sign-in account the second. `!important` settles it once rather than
     * making every new rule remember to come before this one. It can only
     * hide, so it cannot fight DataTables' inline column hiding, which hides
     * too.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-blank {
        display: none !important;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-biz::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-tax::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-credit::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-opening::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-advance::before,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-users::before {
        display: block;
        flex: none;
        font-size: 10px;
        line-height: 1.4;
        color: #9ca3af;
    }

    /* ---- who signs in as this customer ---- */

    /*
     * The accounts linked to the contact -- `users` rows whose
     * `crm_contact_id` points at it -- with the person's name, what they do,
     * and a button that rings them. Built server-side by
     * ContactUtil::crmUsersHtml, because there can be several of them and
     * because the number they carry is the user's, not the contact's.
     *
     * At the foot of the card, under the Actions menu -- asked for there
     * rather than up with the contact's own number, where it read as a second
     * customer with a second call button directly beneath the first. A 100%
     * basis puts it on a line of its own without needing one of the card's
     * two breaks, and an order past the menu's 99 is what puts it last.
     *
     * A contact with no account renders nothing at all, and mobile.js drops
     * the empty cell, so this costs nothing on the customers who have none --
     * including the rule below it, which would otherwise draw a divider under
     * the menu on every card in the list.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.cc-users {
        order: 100;
        box-sizing: border-box;
        display: block;
        flex: 0 0 100%;
        min-width: 0;
        margin-top: 8px;
        padding: 8px 0 0;
        border-top: 1px solid #eef0f4;
    }

    .page-contacts #contact_table.mobile-cards .lu {
        display: flex;
        align-items: center;
        gap: 8px;
        padding-top: 4px;
    }

    /* The name and the role stack, and both give way rather than pushing the
       call button off the line -- the same rule the quiet line above needs,
       for the same reason. */
    .page-contacts #contact_table.mobile-cards .lu-who {
        flex: 1 1 auto;
        min-width: 0;
    }

    .page-contacts #contact_table.mobile-cards .lu-name,
    .page-contacts #contact_table.mobile-cards .lu-role {
        display: block;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    .page-contacts #contact_table.mobile-cards .lu-name {
        font-size: 13px;
        font-weight: 600;
        line-height: 1.35;
        color: #1f2937;
    }

    .page-contacts #contact_table.mobile-cards .lu-role {
        font-size: 11px;
        line-height: 1.35;
        color: #6b7280;
    }

    /* ---- the foot: one menu, right-aligned ---- */

    /*
     * Not the full-width blue bar the generic card puts here: on a list this
     * is a menu, not the point of the row. Right-aligned so it lines up under
     * the call button, exactly as it does on the Sales Order card.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action {
        flex: 0 0 100%;
        justify-content: flex-end;
        padding: 8px 0 0;
        border-top: 0;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action .btn-group,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action .dropdown {
        width: auto;
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action .btn,
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action .tw-dw-btn {
        width: auto;
        min-height: 34px;
        padding: 4px 12px;
        font-size: 12px;
    }

    /*
     * The menu opens leftwards from the button, not rightwards off the screen.
     *
     * Bootstrap anchors a dropdown at `left: 0`, and the markup's own
     * .dropdown-menu-left says the same thing, which is fine for a full-width
     * button and not for one at the card's right edge. Widening it would not
     * help: mobile.css sets `overflow-x: hidden` on body and .content, so the
     * part past the edge is clipped rather than reachable. "Merchant Payment"
     * and "Pay sell return due" are the labels that showed it.
     */
    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action .dropdown-menu {
        right: 0;
        left: auto;
        z-index: 1040;
        min-width: 180px;
        max-width: calc(100vw - 28px);
    }

    .page-contacts #contact_table.mobile-cards > tbody > tr > td.is-action .dropdown-menu > li > a {
        white-space: normal;
    }

    /* The count is the only thing left that says how many customers there
       are, and it sat in grey small print under the paging. */
    .page-contacts .dataTables_info {
        font-size: 13px;
        color: #444;
        padding-top: 4px;
    }

    /* ------------------------------------------------ the contact screen */

    /*
     * View Contact.
     *
     * The page prints `<strong>label</strong><p>value</p>` over and over, in
     * two columns that stack on a phone -- so every fact took two lines, and
     * one customer came to about 900px before the tabs began. The pairs are
     * one line each here: the label in a narrow grey column, the value beside
     * it, which is the same shape the card list uses two screens away.
     *
     * The pairs are not wrapped in anything, so this is a grid on the column
     * itself and the cells fill in document order -- label, value, label,
     * value. That holds only while every `<strong>` is followed by exactly one
     * `<p>`: one extra element in the middle shifts every pair after it into
     * the wrong column. Which is not theoretical, see the `<br>` below. The
     * test asserts every label starts at the same edge and every value at
     * another, so a partial that grows an odd element fails rather than
     * quietly interleaving.
     */
    .contact-view-info .col-sm-3 {
        display: grid;
        grid-template-columns: 40% 1fr;
        gap: 6px 10px;
        align-items: baseline;
        padding: 0;
    }

    /*
     * contact_basic_info ends its heading with a `<br>`, which is a real
     * element and therefore a real grid cell: it took the first label's place
     * and pushed every label and value one column along, so the page read as
     * a column of values with their labels beside them, one pair out.
     */
    .contact-view-info .col-sm-3 > br {
        display: none;
    }

    /*
     * The address flows as a sentence rather than one line per part.
     *
     * `Contact::getContactAddressAttribute` joins the parts with `, ` and
     * prefixes several of them with `<br>`, so the separators are already
     * there and dropping the breaks costs nothing but the line count: six
     * lines become two or three. A `<br>` inside a value is only ever this.
     */
    .contact-view-info .col-sm-3 > p br {
        display: none;
    }

    /* Who they are, across the full width and at the top. */
    .contact-view-info .profile-username {
        grid-column: 1 / -1;
        margin: 0 0 6px;
        font-size: 18px;
        font-weight: 700;
        line-height: 1.3;
        color: #111827;
    }

    .contact-view-info .profile-username small {
        display: block;
        margin-top: 2px;
        font-size: 11px;
        font-weight: 600;
        color: #6b7280;
        text-transform: uppercase;
        letter-spacing: .03em;
    }

    .contact-view-info .col-sm-3 > strong {
        font-size: 11px;
        font-weight: 600;
        line-height: 1.55;
        color: #6b7280;
        text-transform: uppercase;
        letter-spacing: .02em;
        word-break: break-word;
    }

    /*
     * ...and the value is the thing being read, so it is the darker of the
     * two. `text-muted` greys it in the markup, which on a grey label made the
     * whole page one weight of grey.
     */
    .contact-view-info .col-sm-3 > p {
        margin: 0;
        font-size: 14px;
        line-height: 1.45;
        color: #1f2937;
        word-break: break-word;
    }

    /*
     * The second column's 56px spacer lines the two up beside each other on a
     * desktop. Stacked, it is a hole in the middle of the list -- which is
     * what the gap under "Added On" was. app.css declares it !important, so
     * this must too.
     */
    .contact-view-info .col-sm-3.mt-56 {
        margin-top: 10px !important;
    }

    /* The actions under the panel: two halves of a row rather than two small
       floated buttons, and tall enough to hit. */
    .contact-view-info .col-sm-12 {
        display: flex;
        gap: 8px;
        margin-top: 14px;
        padding: 0;
    }

    .contact-view-info .col-sm-12 > .tw-dw-btn {
        flex: 1 1 0;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
        min-height: 44px;
        /* Beats .tw-m-2 and the .pull-right they carry for the desktop
           layout, neither of which means anything inside a flex row. */
        margin: 0 !important;
        float: none;
        text-align: center;
    }

    /* The page's own heading, which is a full-size h3 above a select. */
    .contact-view > .row.no-print h3 {
        margin: 4px 0 10px;
        font-size: 20px;
    }

    /* ---- what is under the tabs ---- */

    /*
     * The Ledger tab's filters: four Bootstrap columns that stack on a phone.
     * Two of them needed more than stacking. The format chooser is a
     * .btn-group of four radio buttons which wrapped three-and-one at 390px,
     * and the PDF and email buttons are .btn-xs -- a 12px icon in a 22px box,
     * right-aligned under a select, which is not a target anyone hits first
     * time.
     */
    .contact-view #ledger_tab .form-group {
        margin-bottom: 12px;
    }

    .contact-view #ledger_tab .form-group > label {
        margin-bottom: 4px;
        font-size: 11px;
        font-weight: 600;
        line-height: 1.4;
        color: #6b7280;
        text-transform: uppercase;
        letter-spacing: .02em;
    }

    /* Four equal chips on one line. */
    .contact-view #ledger_tab .btn-group[data-toggle="buttons"] {
        display: flex;
        width: 100%;
    }

    .contact-view #ledger_tab .btn-group[data-toggle="buttons"] > .btn {
        flex: 1 1 0;
        float: none;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 40px;
        padding: 8px 4px;
        font-size: 13px;
        white-space: nowrap;
    }

    /* PDF and email: two buttons a thumb can find, at the right where they
       were, on a line of their own. vendor.css gives .btn-xs a 14px margin
       above and below, which is why they floated in mid-air. */
    .contact-view #ledger_tab .col-md-2.text-right {
        display: flex;
        justify-content: flex-end;
        gap: 8px;
        margin-bottom: 12px;
    }

    .contact-view #ledger_tab .col-md-2.text-right > .btn {
        flex: 0 0 auto;
        min-width: 64px;
        min-height: 40px;
        margin: 0;
        font-size: 16px;
    }

    /*
     * The statement itself, loaded by ajax into #contact_ledger_div.
     *
     * It is laid out as a printed page: "To" on the left and the account
     * summary on the right, each a col-xs-6 -- so on a phone the summary's
     * two-column table was squeezed into 170px. Stacked, full width.
     */
    .contact-view #contact_ledger_div .col-xs-6 {
        width: 100%;
        float: none;
    }

    /* The summary tables are captioned by their own first column, so they
       stay tables; five columns is what turns the statement rows into cards,
       and that happens on its own. */
    .contact-view #contact_ledger_div .table-condensed td {
        padding: 4px 6px;
        font-size: 13px;
    }

    /* Heading, then the sort toolbar under it, rather than the two sharing a
       line and the toolbar wrapping mid-way. */
    .contact-view .ledger-table-header-row > p {
        flex: 1 1 100% !important;
        min-width: 0 !important;
    }

    .contact-view .ledger-sort-toolbar {
        width: 100%;
        justify-content: space-between;
    }

    /*
     * Two nested scrollers, undone.
     *
     * The statement table sits in a 630px window and the payments list in a
     * 500px one, each with its own scrollbar inside the page's own scroller:
     * two bars answering one gesture, and a card list that scrolls with the
     * page needs neither. Both are inline styles, hence !important.
     */
    .contact-view .ledger-table-scroll-wrapper {
        max-height: none !important;
        overflow: visible !important;
    }

    .contact-view #contact_payments_div {
        height: auto !important;
        overflow: visible !important;
    }

    /*
     * The Sales tab's filters, two to a row like the Sales Order list's.
     *
     * The cells are picked by position, which is safe here and nowhere
     * else: show.blade.php names exactly which three filters this tab gets
     * (`only` => payment status, date range, subscriptions), in that order.
     * The date range is the one whose value is a sentence, so it takes the
     * full width and the checkbox moves up beside the status.
     */
    .contact-view #sales_tab .tw-py-2 {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 6px 10px;
    }

    .contact-view #sales_tab .tw-py-2 > .col-md-3 {
        width: 100%;
        float: none;
        padding: 0;
    }

    .contact-view #sales_tab .tw-py-2 > .col-md-3:nth-child(2) {
        grid-column: 1 / -1;
        order: 3;
    }

    .contact-view #sales_tab .tw-py-2 .form-group > label {
        margin-bottom: 4px;
        font-size: 11px;
        font-weight: 600;
        color: #6b7280;
        text-transform: uppercase;
        letter-spacing: .02em;
    }

    /*
     * A floated button in a column of its own must not collapse the column.
     *
     * This is the Assigned Users "+ Add" button, which did nothing on a phone
     * and worked on a desktop, and the difference is Bootstrap's grid. At
     * 992px and up a column floats, which makes it a block formatting context
     * that contains its floats: the column is as tall as the button. Below
     * that a column is a plain block, so a column holding nothing but a
     * `pull-right` button is 1px tall -- and the *next* column, which is
     * `position: relative` like every Bootstrap column, starts on the same
     * line and paints above the float. The button was visible and could not
     * be pressed: every tap landed on the transparent column over it.
     *
     * `flow-root` is the containment without the float, applied to every
     * column under the tabs so the next lone floated button gets it too.
     */
    .contact-view .tab-content [class*="col-"] {
        display: flow-root;
    }

    /* ...and that one is a real target, on a row of its own at the right. */
    .contact-view #assigned_user_tab .row > .col-md-12:first-child {
        display: flex;
        justify-content: flex-end;
        margin-bottom: 4px;
    }

    .contact-view #assigned_user_tab #add_assigned_user_btn {
        float: none;
        display: inline-flex;
        align-items: center;
        gap: 6px;
        min-height: 44px;
        padding: 8px 18px;
    }

    /* Delivery Lines: the save button is a floated desktop button under a
       table; here it is the one thing to press, full width. */
    .contact-view #delivery_lines_tab .btn.pull-right {
        float: none;
        width: 100%;
        min-height: 44px;
        margin-top: 8px;
    }

    /* ------------------------------------------- the sell screen's lines */

    /*
     * Search first, then what you have added.
     *
     * The template puts the search box under the table because that is where a
     * desktop has room for it. On a phone it means scrolling past everything
     * already on the order to add the next thing, and past a growing amount of
     * it -- so the two are ordered the other way round here. Desktop keeps the
     * template's order: this is a flex container only inside the breakpoint.
     */
    .page-sells .pos-entry {
        display: flex;
        flex-direction: column;
    }

    .page-sells .pos-entry > .pos-entry-search {
        order: -1;
        width: 100%;
        margin: 0 0 4px;
        padding: 0;
    }


    /*
     * Adding products to a sale or a sales order, on a phone.
     *
     * The generic card treats every value as a label beside a value, which is
     * right for reading a list and wrong for a row you fill in: the quantity
     * stepper had 60% of the card to sit in and its + fell off the edge. Here
     * the label goes above its control and the control gets the full width,
     * then unit price, subtotal and the remove cross share the last line.
     *
     * Selected with :has() rather than a class on the cells, so this stays in
     * the mobile layer and does not touch a row template shared with the POS
     * screen. Where :has() is missing the cards render exactly as they do
     * everywhere else -- readable, just taller.
     */
    .page-sells #pos_table.mobile-cards > tbody > tr {
        padding: 12px;
        gap: 0 10px;
        /* The cells on the money line are different heights; centred, their
           captions sit at different levels and the line reads as crooked. */
        align-items: stretch;
    }

    .page-sells #pos_table.mobile-cards > tbody > tr > td {
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
        padding: 10px 0 0;
    }

    /* No longer a 40% column beside the value; it is a caption above it. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td::before {
        flex: 0 0 auto;
    }

    /* The name line stays a row: stacking it put the thumbnail on a line of
       its own, which is a 44px band saying nothing. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td.is-title {
        flex-direction: row;
        align-items: center;
        gap: 10px;
        padding: 0;
    }

    /* Thumbnail first, whatever order the row template writes it in. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td.is-title img {
        order: -1;
        flex: 0 0 auto;
        width: 44px !important;
        height: 44px !important;
        border-radius: 8px;
        margin: 0 !important;
    }

    /* The quantity row: stepper and its unit side by side under the caption. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td:has(.pos_quantity) {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        gap: 8px;
    }

    .page-sells #pos_table.mobile-cards > tbody > tr > td:has(.pos_quantity)::before {
        flex: 0 0 100%;
    }

    .page-sells #pos_table.mobile-cards > tbody > tr > td:has(.pos_quantity) br {
        display: none;
    }

    .page-sells #pos_table.mobile-cards > tbody > tr > td .sub_unit {
        flex: 0 0 84px;
        width: 84px !important;
        min-height: 42px;
    }

    /* The stepper: full width, and big enough to hit. */
    .page-sells #pos_table.mobile-cards .input-group.input-number {
        display: flex;
        flex: 1 1 150px;
        width: auto;
        /* A flex item will not shrink below its content by default, and a text
           input's content width is about twenty characters -- so the stepper
           refused to go under ~270px and pushed the unit select onto a line of
           its own. */
        min-width: 0;
    }

    .page-sells #pos_table.mobile-cards .input-group.input-number .form-control {
        flex: 1 1 auto;
        min-width: 0;
        width: auto !important;
        min-height: 42px;
        text-align: center;
        font-weight: 600;
    }

    .page-sells #pos_table.mobile-cards .input-group.input-number .input-group-btn {
        flex: 0 0 auto;
        width: auto;
        display: flex;
    }

    .page-sells #pos_table.mobile-cards .input-group.input-number .btn {
        min-width: 44px;
        min-height: 42px;
    }

    /* Money and the remove cross share the last line. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td:has(.pos_unit_price) {
        flex: 1 1 45%;
    }

    .page-sells #pos_table.mobile-cards > tbody > tr > td:has(.pos_line_total_text),
    .page-sells #pos_table.mobile-cards > tbody > tr > td:has(.pos_line_total) {
        flex: 1 1 30%;
    }

    /* The subtotal is the number being checked, so it reads as one rather than
       as another field, and it lines up with the price beside it. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td .pos_line_total_text {
        display: flex;
        align-items: center;
        min-height: 42px;
        font-size: 16px;
        font-weight: 700;
        color: #111827;
    }

    .page-sells #pos_table.mobile-cards > tbody > tr > td.is-action {
        flex: 0 0 auto;
        margin-left: auto;
        align-items: flex-end;
        justify-content: flex-end;
    }

    /* The cross sits on the money line, so it starts where those controls do
       rather than at the top of the cell. */
    .page-sells #pos_table.mobile-cards > tbody > tr > td.is-action::before {
        content: "";
        display: block;
        height: 18px;
    }

    /* A bare cross is a 12px target; this is one a thumb can find. */
    .page-sells #pos_table.mobile-cards .pos_remove_row {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        height: 40px;
        border: 1px solid #f0c8c8;
        border-radius: 8px;
        background: #fff6f6;
        font-size: 16px;
    }

    /* ------------------------------------- generating an invoice from an order */

    /*
     * /sells/{id}/generate-invoice, on a phone.
     *
     * The same problem the sell screen's lines had above, arriving a different
     * way. Here the generic card did not merely read wrongly, it broke: the
     * quantity cell holds four element children -- the ordered-qty figure, a
     * <br>, the number field and the available-qty figure -- and a `td` laid
     * out as `display: flex` with no `flex-wrap` puts every one of them on one
     * line inside the 60% the caption leaves.
     *
     * What lets them shrink to nothing is the declaration beside it.
     * `word-break: break-word` means `overflow-wrap: anywhere`, and `anywhere`
     * -- unlike `break-word` -- shrinks an element's min-content to a single
     * character. A flex item's automatic minimum size is its min-content, so
     * the floor that normally stops an item shrinking past its longest word
     * was gone: "Ordered Qty: 1.00" rendered one letter per line in a 28px
     * column. Measured, by swapping that one declaration for `overflow-wrap`,
     * which gives back "Ordered / Qty: / 1.00".
     *
     * (A <br> in a flex container is blockified into an empty item. It makes
     * no line break at all -- it only pays for a gap, which is why all four
     * children shared one line rather than stacking as they do on a desktop.)
     *
     * Every other card in the application has one element per cell, which is
     * the only reason the generic rule has survived since it was written. The
     * day a second cell holds a small form, this is where it will show.
     *
     * So the cells stack their own contents: caption above control, the field
     * full width, the two quantity figures on a quiet line under it, and unit
     * price, line total and the remove cross sharing the last line.
     *
     * Picked by position, and that is the deliberate opposite of the #pos_table
     * block above. That table's columns come and go with business settings, so
     * a position there means nothing and :has() is the only safe hook. This one
     * declares its six columns once, in one view, in a fixed order, with no
     * DataTable to reorder them -- and :has() degrades to "readable, just
     * taller" only where the un-styled card is readable. Here it is the
     * one-character-per-line break, so a selector every browser understands is
     * worth more than a tidier one.
     */
    .page-sells #products_table.mobile-cards > tbody > tr {
        padding: 12px;
        gap: 0 10px;
        /* The money-line cells are different heights; centred, their contents
           sit at different levels and the line reads as crooked. */
        align-items: stretch;
    }

    /*
     * `display` is deliberately not set here. This rule carries an id, so it
     * would outrank the generic `.is-blank { display: none }` -- and the rows
     * the search box appends write `<td></td>` for the image, so every one of
     * them would then print an empty Image cell.
     */
    .page-sells #products_table.mobile-cards > tbody > tr > td {
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
        padding: 10px 0 0;
    }

    /* No longer a 40% column beside the value; it is a caption above it. */
    .page-sells #products_table.mobile-cards > tbody > tr > td::before {
        flex: 0 0 auto;
    }

    /* The <br>s are the desktop line breaks. A column has them already, and as
       flex items they are a blank line each. */
    .page-sells #products_table.mobile-cards > tbody > tr > td br {
        display: none;
    }

    /* The thumbnail keeps the gutter the generic card gives it. */
    .page-sells #products_table.mobile-cards > tbody > tr > td.is-unlabelled:not(.is-action) {
        padding: 0 10px 0 0;
    }

    /* Name, brand and SKU stack beside the thumbnail rather than fighting for
       one line -- the same break as the quantity cell, one column over. */
    .page-sells #products_table.mobile-cards > tbody > tr > td.is-title {
        flex: 1 1 60%;
        gap: 2px;
        padding: 0;
    }

    .page-sells #products_table.mobile-cards > tbody > tr > td.is-title small {
        font-size: 12px;
        font-weight: 400;
        color: #6b7280;
    }

    /* ---- the quantity being invoiced ---- */

    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(3) {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        gap: 6px 10px;
        padding-bottom: 10px;
        border-bottom: 1px solid #f3f4f6;
    }

    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(3)::before {
        flex: 0 0 100%;
        order: -2;
    }

    /*
     * The field leads, whichever row this is.
     *
     * The page appends a second, different row from its own autocomplete: same
     * six cells in the same order, but with no "Ordered Qty" figure at all, so
     * the one <small> it does carry sits *after* the field rather than before
     * it. Nothing in the markup tells the two figures apart, so ordering them
     * individually is not available -- putting the field first and leaving the
     * figures in document order reads correctly for both shapes.
     */
    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(3) .invoice_qty {
        flex: 0 0 100%;
        order: -1;
        width: 100%;
        min-height: 44px;
        text-align: center;
        font-weight: 600;
    }

    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(3) small {
        flex: 0 1 auto;
        min-width: 0;
        font-size: 12px;
    }

    /*
     * Grey, unless the figure is the warning.
     *
     * The available-qty <small> carries .text-danger instead of .text-muted
     * when there is no stock, and that red is the one thing on the card that
     * has to be noticed. A colour written on `small` here is an id, three
     * class-level selectors and four elements, which outranks .text-danger's
     * single class by three orders.
     *
     * Honestly: today it would lose anyway, because this theme declares
     * `.text-danger { color: #f5365c !important }` in vendor.css beside stock
     * Bootstrap's version, which has no !important. So the :not() is not what
     * holds the red now -- it is what holds it the day vendor.css is rebuilt
     * from stock. Belt and braces, said so rather than dressed up as
     * load-bearing, and deliberately not an !important of our own, which every
     * rule added to this block afterwards would then have to remember.
     */
    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(3) small:not(.text-danger) {
        color: #6b7280;
    }

    /* ---- unit price, line total and the remove cross share the last line ---- */

    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(4) {
        flex: 0 1 auto;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: baseline;
        justify-content: flex-start;
        gap: 4px;
        border-top: 0;
        /* Small enough that price, total and the cross still share one line on
           a 320px screen, where at 13px/16px the cross was pushed onto a row
           of its own -- and inconsistently, depending on how long the figure
           beside it happened to be. */
        font-size: 12px;
        color: #6b7280;
    }

    /*
     * The number being agreed, so it reads as one rather than as another
     * labelled value, and it ends where the card does.
     *
     * It takes what the price beside it leaves, rather than a share of its
     * own: a currency string is as long as the figure makes it, and at a fixed
     * 30% "Rs 1,280.00" broke after the symbol -- the generic card's
     * `word-break: break-word` acting on a number, which is where it is least
     * wanted. Hence the nowrap as well as the auto basis.
     */
    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(5) {
        flex: 1 1 auto;
        align-items: flex-end;
        justify-content: flex-end;
        min-width: 0;
        border-top: 0;
        font-size: 15px;
        font-weight: 700;
        white-space: nowrap;
        color: #111827;
    }

    /*
     * Both captions go.
     *
     * "Unit Price Incl. Tax / Unit" is longer than the value it heads and took
     * two lines to say what "/ Kg" beside the figure already says; grey on the
     * left against bold on the right is a receipt line, and the cell above it
     * is captioned. Overriding `content` in the stylesheet was the alternative
     * and would put a second copy of two labels somewhere they can drift from
     * the headings.
     */
    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(4)::before,
    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(5)::before {
        display: none;
    }

    .page-sells #products_table.mobile-cards > tbody > tr > td.is-action {
        flex: 0 0 auto;
        margin-left: auto;
        align-items: flex-end;
        justify-content: flex-end;
        border-top: 0;
    }

    /*
     * A btn-sm holding a 12px cross is not a target; this one is -- and it is
     * quiet. Filled danger red made the least urgent thing on the card the
     * loudest, which is the note already recorded against the Sales Order
     * list's actions. The same outline the sell screen's cross carries.
     */
    .page-sells #products_table.mobile-cards .remove_invoice_line {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 40px;
        min-width: 40px;
        min-height: 40px;
        padding: 0;
        border: 1px solid #f0c8c8;
        border-radius: 8px;
        background: #fff6f6;
        font-size: 15px;
        color: #d9534f;
    }

    /* ---- the rest of the screen ---- */

    /*
     * The order's own facts. Ten of them, written as `<b>Label:</b> value<br>`
     * in three columns that stack into one long grey run on a phone; the labels
     * step back so the values carry the weight, and each stacked column is
     * divided from the one above rather than running into it.
     */
    .page-sells .order-info b {
        color: #6b7280;
        font-weight: 600;
    }

    .page-sells .order-info > .row > div {
        line-height: 1.8;
    }

    .page-sells .order-info > .row > div + div {
        margin-top: 8px;
        padding-top: 8px;
        border-top: 1px solid #f3f4f6;
    }

    /*
     * A payment against the order leads with what was paid.
     *
     * Seven columns, so the generic card treatment applies -- and its headline
     * became the row number, a card that opens by announcing "1". The number is
     * what the card replaces, so it goes; the amount and the date take the top
     * line and the rest stay labelled values underneath.
     */
    .page-sells #order_payment_lines_table.mobile-cards > tbody > tr > td:nth-child(1) {
        display: none;
    }

    .page-sells #order_payment_lines_table.mobile-cards > tbody > tr > td:nth-child(4) {
        flex: 1 1 auto;
        order: -2;
        border-top: 0;
        font-size: 16px;
        font-weight: 700;
        color: #111827;
    }

    .page-sells #order_payment_lines_table.mobile-cards > tbody > tr > td:nth-child(2) {
        flex: 0 0 auto;
        order: -1;
        justify-content: flex-end;
        border-top: 0;
        font-size: 12px;
        color: #6b7280;
    }

    .page-sells #order_payment_lines_table.mobile-cards > tbody > tr > td:nth-child(2)::before,
    .page-sells #order_payment_lines_table.mobile-cards > tbody > tr > td:nth-child(4)::before {
        display: none;
    }

    /*
     * Paying from the advance is a tick box gating a money field, and a bare
     * 13px box is the smallest target on the screen.
     */
    .page-sells #generate_invoice_form .checkbox > label {
        display: flex;
        align-items: center;
        gap: 8px;
        min-height: 44px;
        padding-left: 0;
    }

    .page-sells #generate_invoice_form .checkbox > label > input[type="checkbox"] {
        position: static;
        width: 20px;
        height: 20px;
        margin: 0;
    }

    /* The total is what the two parties agree; it is not another table row. */
    .page-sells #generate_invoice_form .table-condensed > tbody > tr > th,
    .page-sells #generate_invoice_form .table-condensed > tbody > tr > td {
        padding: 10px 8px;
        font-size: 14px;
    }

    .page-sells #generate_invoice_form .table-condensed > tbody > tr.bg-gray > th,
    .page-sells #generate_invoice_form .table-condensed > tbody > tr.bg-gray > td {
        padding: 12px 8px;
        font-size: 17px;
    }

    /*
     * The two actions are full width and stacked, and deliberately not a fixed
     * bar: a staff phone already carries the bottom nav, and a second fixed bar
     * would take about 115px off every screen of a form people scroll through.
     * Generating an invoice is the end of a sequence, not something done from
     * the middle of one.
     *
     * The width states its own box-sizing: these carry horizontal padding, so a
     * content-box 100% overhangs the screen by it. Tailwind's preflight sets it
     * globally in the app, and a rule that silently depends on another
     * stylesheet's reset breaks the day that stylesheet is not there.
     */
    .page-sells #generate_invoice_form .text-center > .btn-lg {
        display: block;
        box-sizing: border-box;
        width: 100%;
        margin: 0 0 8px;
    }

    /* Export/print/column buttons: a wrapping row of equal chips. */
    .dt-buttons {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
        width: 100%;
        margin-bottom: 8px;
    }

    .dt-buttons > .btn,
    .dt-buttons > a {
        flex: 1 1 auto;
        min-height: 40px;
        margin: 0 !important;
    }

    /* --------------------------------------------------------------- forms */

    /* 16px is the threshold below which iOS zooms the page on focus, which
       then leaves the layout scaled and the user hunting for the way back. */
    .form-control,
    .form-group input[type="text"],
    .form-group input[type="number"],
    .form-group input[type="email"],
    .form-group input[type="password"],
    .form-group input[type="date"],
    select.form-control,
    textarea.form-control {
        font-size: 16px;
        min-height: 44px;
    }

    .form-group {
        margin-bottom: 14px;
    }

    .form-group > label {
        font-size: 14px;
        margin-bottom: 6px;
    }

    /* select2 is used throughout and renders its own control. */
    .select2-container .select2-selection--single {
        height: 44px !important;
    }

    .select2-container--default .select2-selection--single .select2-selection__rendered {
        line-height: 42px !important;
        font-size: 16px;
    }

    .select2-container--default .select2-selection--single .select2-selection__arrow {
        height: 42px !important;
    }

    .select2-container .select2-selection--multiple {
        min-height: 44px !important;
    }

    .select2-results__option {
        padding: 12px 10px;
        font-size: 15px;
    }

    .input-group-addon {
        min-width: 44px;
    }

    /* -------------------------------------------------------------- buttons */

    /* 44px is the smallest reliably tappable target. */
    .btn {
        min-height: 44px;
        padding: 10px 14px;
        font-size: 15px;
    }

    .btn-sm,
    .btn-xs {
        min-height: 36px;
        padding: 6px 10px;
        font-size: 13px;
    }

    /* A lone primary action spans the width rather than sitting orphaned. */
    .box-footer > .btn-primary:only-child,
    .modal-footer > .btn-primary:only-child {
        display: block;
        width: 100%;
    }

    /* Groups of form actions stack into full-width rows, in tap order. */
    .form-actions .btn,
    .box-footer .btn {
        margin-bottom: 8px;
    }

    /* --------------------------------------------------------------- modals */

    /* Full-screen sheets. A centred dialog on a phone wastes the edges and
       puts the footer buttons somewhere unpredictable. */
    .modal-dialog {
        margin: 0;
        width: 100%;
        max-width: 100%;
    }

    .modal-content {
        min-height: 100vh;
        border: 0;
        border-radius: 0;
    }

    .modal-header {
        position: sticky;
        top: 0;
        z-index: 2;
        background: #fff;
        border-bottom: 1px solid #e5e7eb;
    }

    .modal-footer {
        position: sticky;
        bottom: 0;
        z-index: 2;
        background: #fff;
        border-top: 1px solid #e5e7eb;
        padding-bottom: calc(10px + var(--mobile-safe-bottom));
    }

    .modal-footer .btn {
        width: 100%;
        margin: 0 0 8px;
    }

    /* ----------------------------------------------------------------- tabs */

    /*
     * A scrolling strip of chips, the same shape as the storefront's category
     * strip (13.1).
     *
     * They were tabs in the desktop sense: `app.css` sets them at 18px
     * semibold, so three of them filled a 390px screen and the other five --
     * Payments, Documents, Delivery Lines and the rest -- were off the edge
     * with only a scrollbar to say so. A chip is smaller, says plainly that
     * it is a control rather than a heading, and fits five.
     */
    .nav-tabs {
        display: flex;
        flex-wrap: nowrap;
        gap: 6px;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        padding: 8px 10px;
        border-bottom: 1px solid #eef0f4;
        /*
         * The scrollbar is hidden, not the scrolling: Android draws a classic
         * bar for a non-body scroller like this, which took real height under
         * the strip and read as desktop chrome beside a touch UI. The cue is
         * the fade below plus a chip cut off at the edge -- which is what
         * `js/mobile.js` keeps in step. This is the deliberate exception to
         * "a sideways-scrolling thing keeps its bar" (8.3): a table has
         * nothing else to hint with, a chip strip does.
         */
        scrollbar-width: none;
    }

    .nav-tabs::-webkit-scrollbar {
        /* Both: `display: none` alone leaves the computed width and height
           reading `auto`, which is what the test reads -- it cannot measure a
           hidden bar any other way, since this Chromium draws overlay
           scrollbars that take no width either way (8.3). */
        display: none;
        width: 0;
        height: 0;
    }

    /*
     * The right-edge fade, pinned inside the scroller rather than positioned
     * over it: `::after` on a flex container is a flex item, and `sticky`
     * holds it at the right edge while the strip scrolls under it. A negative
     * margin so it adds no scroll width of its own.
     *
     * It is only shown while there is more to the right -- js/mobile.js sets
     * .has-more -- because a fade still promising more at the end of the strip
     * is worse than none.
     */
    .nav-tabs::after {
        content: "";
        position: sticky;
        right: 0;
        flex: 0 0 28px;
        margin-right: -28px;
        align-self: stretch;
        opacity: 0;
        transition: opacity .15s;
        background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff);
        pointer-events: none;
    }

    .nav-tabs.has-more::after {
        opacity: 1;
    }

    .nav-tabs > li {
        float: none;
        flex: 0 0 auto;
        /* AdminLTE marks the open tab with a 3px bar above it and spaces the
           tabs with a right margin; the chip carries both jobs itself. */
        margin: 0 !important;
        border-top: 0 !important;
    }

    .nav-tabs > li > a {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        min-height: 38px;
        padding: 8px 14px;
        border: 1px solid #e5e7eb !important;
        border-radius: 999px !important;
        background: #f7f8fa !important;
        color: #4b5563 !important;
        font-size: 13px;
        font-weight: 600;
        white-space: nowrap;
    }

    /* Which one you are on. AdminLTE says so with a white tab against a grey
       bar, which is invisible once the bar is white too. */
    .nav-tabs > li.active > a,
    .nav-tabs > li.active > a:hover,
    .nav-tabs > li.active > a:focus {
        border-color: #bcd8fb !important;
        background: #eaf2fd !important;
        color: #1a56db !important;
    }

    /* The pane's own toolbar sat hard against the strip. */
    .nav-tabs-custom > .tab-content {
        padding-top: 12px;
    }

    /* ------------------------------------------------------------ dashboard */

    /* AdminLTE stat widgets, where any page still uses them. */
    .small-box .inner h3 {
        font-size: 26px;
    }

    .info-box,
    .small-box {
        margin-bottom: 12px;
    }

    /* ------------------------------------------------- bottom navigation bar */

    .mobile-nav {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1030;
        /* Beats the tw-hidden on the element, which is what keeps the bar off
           the desktop even if this stylesheet never loads. */
        display: flex !important;
        height: calc(var(--mobile-nav-height) + var(--mobile-safe-bottom));
        padding-bottom: var(--mobile-safe-bottom);
        background: #fff;
        border-top: 1px solid #e5e7eb;
        box-shadow: 0 -1px 6px rgba(0, 0, 0, .06);
    }

    .mobile-nav__item {
        flex: 1 1 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        padding: 6px 2px;
        color: #6b7280;
        font-size: 11px;
        line-height: 1.2;
        text-align: center;
        text-decoration: none;
        background: none;
        border: 0;
        /* Stops the grey tap flash that makes a web page feel like a web page. */
        -webkit-tap-highlight-color: transparent;
    }

    .mobile-nav__item:focus,
    .mobile-nav__item:hover {
        color: var(--mobile-accent);
        text-decoration: none;
    }

    .mobile-nav__item i {
        font-size: 19px;
        line-height: 1;
    }

    .mobile-nav__item.is-active {
        color: var(--mobile-accent);
        font-weight: 600;
    }

    /* A label long enough to wrap would change the bar's height. */
    .mobile-nav__label {
        display: block;
        max-width: 100%;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* The drawer overlay must sit above content but below the drawer itself. */
    .overlay {
        z-index: 1035;
    }

    .side-bar.small-view-side-active {
        z-index: 1040;
    }
}

/*
 * The narrowest phones still in use.
 *
 * A separate breakpoint from the one above because it answers a different
 * question: that one is "is this a phone", this is "does one line fit at all".
 * On the invoice card, unit price, line total and the remove cross come to
 * about 280px of content against 230px of card at 320px wide, so the cross
 * wrapped -- and wrapped differently from one line to the next, depending on
 * how long the figure beside it happened to be. Giving the price a line of its
 * own makes the shape the same on every line: the price, then the total with
 * the cross beside it.
 */
@media (max-width: 359.98px) {
    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(4) {
        flex: 0 0 100%;
        padding-bottom: 2px;
    }

    .page-sells #products_table.mobile-cards > tbody > tr > td:nth-child(5) {
        padding-top: 0;
    }

    .page-sells #products_table.mobile-cards > tbody > tr > td.is-action {
        padding-top: 0;
    }
}

/* The bar is chrome, not content. */
@media print {
    .mobile-nav {
        display: none !important;
    }
}

/* Shown only on small screens; hidden outright everywhere else so it can never
   appear on a desktop that happens to have a narrow window class applied. */
@media (min-width: 992px) {
    .mobile-nav {
        display: none !important;
    }
}
