/* The application on a phone.
 *
 * Everything in this file is presentation, and all of it is behind a width:
 * the desktop application is the approved one and is not touched by a line
 * here. Nothing below changes what a page holds, what a button does or which
 * record is shown — only how it sits on a screen the width of a hand.
 *
 * Two widths matter. Up to 899px the sidebar becomes a drawer and the top bar
 * is rebuilt around the three things a phone needs: the menu, where you are,
 * and a new quotation. Up to 700px the operational tables become cards, and
 * dialogs come up from the bottom edge.
 */

/* ------------------------------ safe areas ------------------------------- */

:root {
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
}

/* ------------------------------- the shell ------------------------------- */

@media (max-width: 899px) {
  /* iOS zooms the whole page into any box whose text is under 16px. Sixteen
     is the size at which it leaves the page alone. */
  .app-shell input, .app-shell select, .app-shell textarea,
  dialog input, dialog select, dialog textarea { font-size: 16px !important; }

  /* A finger is not a pointer: 44px is the height Apple asks for. */
  .btn, .nav-item, .tabs button, .stat, .combobox-option { min-height: 44px; }
  .btn-sm { min-height: 40px; }
  .row-action { width: 40px; height: 40px; }
  .link-button { min-height: 32px; }
  /* The SOF and the tracking are buttons — the cell itself opens the booking
     window — and a dashed outline the height of a line of text is not a thing
     a thumb can find. */
  .ship-ref { min-height: 36px; min-width: 3rem; padding-inline: .5rem; }

  .app-topbar {
    padding: calc(.45rem + var(--safe-top)) calc(.75rem + var(--safe-right)) .45rem calc(.75rem + var(--safe-left));
    gap: .4rem;
  }
  .app-topbar .nav-toggle { display: inline-flex; width: 44px; height: 44px; padding: 0; font-size: 1.25rem; }
  /* Three things across the top: the menu, where you are, a new quotation. The
     rest of the bar lives in the drawer on a phone. */
  .app-topbar .sync-chip, .app-topbar #user-avatar, .app-topbar #sign-out, .app-topbar #theme-toggle { display: none; }
  .app-topbar .breadcrumb { flex: 1 1 auto; min-width: 0; }
  .breadcrumb .crumb-section, .breadcrumb .crumb-sep { display: none; }
  .breadcrumb .crumb-page { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .app-topbar .spacer { display: none; }
  .topbar-actions .btn { padding: .45rem .7rem; white-space: nowrap; }
  .topbar-new { display: inline-flex; width: 44px; height: 44px; padding: 0; font-size: 1.5rem; line-height: 1; flex: none; text-decoration: none; }
  /* The page's own action already says "+ New quotation" on the quotations
     list; the permanent one steps back there. */
  .topbar-actions:not(:empty) + .topbar-new { display: none; }

  .app-main { padding: .9rem calc(.9rem + var(--safe-right)) calc(1rem + var(--safe-bottom)) calc(.9rem + var(--safe-left)); }
  .app-footer { padding-bottom: calc(1.25rem + var(--safe-bottom)); flex-direction: column; gap: .25rem; }

  /* The drawer: over the page, the width of a thumb's reach, scrolling on its
     own while the page behind it holds still. */
  .app-sidebar {
    width: min(84vw, 20rem); flex-basis: auto;
    padding: calc(.9rem + var(--safe-top)) .7rem calc(1.25rem + var(--safe-bottom)) calc(.7rem + var(--safe-left));
    overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  .nav-item { padding: .6rem .7rem; font-size: 1rem; }
  .nav-backdrop {
    position: fixed; inset: 0; z-index: 39; background: rgba(9, 14, 24, .45);
    opacity: 0; pointer-events: none; transition: opacity .15s ease;
  }
  body.drawer-open { overflow: hidden; }
  body.drawer-open .nav-backdrop { opacity: 1; pointer-events: auto; }

  /* Who is signed in, the theme, and the way out — at the foot of the drawer,
     since the top bar no longer has room for them. */
  .drawer-account {
    display: flex; flex-direction: column; gap: .5rem;
    margin-top: auto; padding-top: 1rem; border-top: 1px solid rgba(255, 255, 255, .1);
  }
  .drawer-account .drawer-who { display: flex; align-items: center; gap: .6rem; color: var(--nav-text); font-size: .88rem; min-width: 0; }
  .drawer-account .drawer-who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .drawer-account .btn { justify-content: center; background: rgba(255, 255, 255, .06); color: #fff; border-color: rgba(255, 255, 255, .12); }

  .storage-warning, .banner { margin-inline: 0; }
}

@media (min-width: 900px) {
  .topbar-new, .nav-backdrop, .drawer-account { display: none; }
}

/* ------------------------------- the pages ------------------------------- */

/* The secondary filters of a list. On a desktop the wrapper is transparent and
   the toolbar is exactly what it was; on a phone they fold behind one row, so
   the first record is on the first screen, and the search box stays out. */
/* Both the wrapper and its body vanish from the layout on a desktop, so the
   filters are flex items of the toolbar exactly as they were before — which is
   why this is a div and not a <details>: Chromium ignores display:contents on
   an element with a shadow tree of its own, and the toolbar grew a line. */
.filters, .filters > .filters-body { display: contents; }
.filters > .filters-summary { display: none; }
@media (max-width: 700px) {
  .filters { display: block; width: 100%; order: 1; }
  .filters > .filters-summary {
    display: flex; align-items: center; justify-content: space-between; width: 100%;
    min-height: 44px; padding: .5rem .75rem; border: 1px solid var(--border);
    border-radius: var(--radius-sm); background: var(--surface-2);
    font: inherit; font-weight: 600; color: var(--text); cursor: pointer; text-align: left;
  }
  .filters > .filters-summary::after { content: '\25BE'; color: var(--text-faint); }
  .filters.open > .filters-summary::after { content: '\25B4'; }
  .filters > .filters-body {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .4rem .5rem;
    padding: .6rem 0 .2rem;
  }
  .filters:not(.open) > .filters-body { display: none; }
  .filters .field-label { grid-column: 1 / -1; margin: .2rem 0 -.2rem; font-size: .8rem; }
  .filters .muted { display: none; }
  .filters select { grid-column: 1 / -1; }
  /* A pair is a label over its box, the whole width of the fold; the period is
     the one that holds two boxes, and they share the line. */
  .filters .qt-pair { grid-column: 1 / -1; display: grid; gap: .15rem; }
  .filters .qt-pair-period { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .filters .qt-pair-period .field-label { grid-column: 1 / -1; }
  /* The search box leads, full width; the buttons follow the fold. */
  .toolbar { gap: .45rem; }
  .toolbar > input[type="search"] { order: 0; width: 100%; }
  .toolbar > .spacer { display: none; }
  .toolbar > .btn { order: 2; }
  /* No keyboard on a phone, so nothing to hint at; the utilities take the
     width between them rather than trailing off the edge. */
  .qt-search-key { display: none; }
  .qt-tools { order: 2; width: 100%; margin-left: 0; }
  .qt-tools .btn { flex: 1 1 auto; justify-content: center; }
  .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4rem; }
  .stat { padding: .5rem .55rem; }
  .stat span { font-size: .66rem; }
  .stat strong { font-size: 1.1rem; }
}

@media (max-width: 700px) {
  .page-title p { font-size: .85rem; }
  .stats-aside .stat { max-width: none; }
  /* The dashboard on a phone: the numbers three to a line and small, and
     what needs a person first — that is what the page is opened for. */
  .kpi-row { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .4rem; }
  .kpi { padding: .45rem .55rem; gap: .05rem; }
  .kpi-label { font-size: .62rem; letter-spacing: .03em; }
  .kpi-value { font-size: 1.2rem; }
  .kpi-money { font-size: .95rem; }
  .app-main:has(#attention) { display: flex; flex-direction: column; }
  .app-main:has(#attention) > .panel:has(#attention) { order: -1; }
  /* Five tabs wrap to a second line rather than hiding beyond the edge:
     a tab that has to be scrolled to is a tab nobody finds. */
  .tabs { flex-wrap: wrap; gap: .15rem; }
  .tabs button { white-space: nowrap; padding-inline: .6rem; }
  .toolbar input[type="search"], .toolbar select, .toolbar input[type="date"] { width: 100%; min-width: 0; }
  .toolbar > label.field-label { flex-basis: 100%; margin-bottom: -.35rem; }
  .card { padding: .9rem; }
  .grid-2 { gap: .75rem; }

  /* Long values wrap rather than pushing the page sideways. */
  .record-title h2, .record-facts strong, .detail-grid dd, .plain-list li, .event-head, .review-list li,
  .mail-facts strong, td, .notice { overflow-wrap: anywhere; }
  .record-head { flex-direction: column; align-items: stretch; gap: .6rem; }
  /* Seven actions, two to a line, tight: they are the top of the page and the
     record is what somebody came to read. */
  .record-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .35rem; }
  .record-actions .btn { justify-content: center; min-height: 40px; padding: .4rem .5rem; }
  /* The paragraph explaining Smart Paste is read once, not every time. */
  #paste-panel small { font-size: .78rem; line-height: 1.4; }
  .record-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
  .detail-grid { grid-template-columns: minmax(6rem, auto) 1fr; }
  .review-list li { flex-direction: column; align-items: flex-start; gap: .15rem; }
  .review-what { white-space: normal; }
  .mode-bar { align-items: stretch; flex-direction: column; gap: .5rem; }
  .mode-bar .spacer { display: none; }
  .mode-bar .btn { justify-content: center; }
  .mail-facts { gap: .4rem .8rem; }

  /* A tariff matrix is nine columns of figures and cannot be anything else on a
     phone; it scrolls sideways, deliberately, with its weight column held. The
     boxes keep the width a price needs — a cell reading "52." is worse than a
     cell that has to be scrolled to. */
  .table-compact td:first-child, .table-compact th:first-child {
    position: sticky; left: 0; z-index: 1; background: var(--surface);
    min-width: 5.5rem; white-space: nowrap;
  }
  .table-compact th:first-child { background: var(--surface-2); z-index: 2; }
  .table-compact input { min-width: 5rem; }
  .table-compact td, .table-compact th { white-space: nowrap; }

  /* The discount and currency row under the services. Four columns will not
     fit a phone, so it becomes one block per service — the same boxes, the
     same handlers, stacked. */
  #service-settings .table-compact { display: block; }
  #service-settings thead { display: none; }
  #service-settings tbody, #service-settings tr { display: block; }
  #service-settings tr { padding: .5rem 0; border-bottom: 1px solid var(--border); }
  #service-settings tr:last-child { border-bottom: 0; }
  #service-settings td {
    display: flex; align-items: center; justify-content: space-between; gap: .6rem;
    padding: .18rem 0; border: 0; white-space: normal;
  }
  #service-settings td::before {
    content: attr(data-label); font-size: .72rem; text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-faint);
  }
  #service-settings td:first-child { font-weight: 600; }
  #service-settings td:first-child::before { display: none; }
  #service-settings td input, #service-settings td select { min-width: 0; width: 8rem; text-align: right; }
}

/* -------------------------- tables become cards -------------------------- */

/* The same rows, the same cells, laid out one record to a card: each cell
   becomes a labelled line, the label being the column heading it sat under.
   Nothing is fetched twice and nothing is hidden that the office reads —
   only the disclosure arrow and the empty heading go. */
@media (max-width: 700px) {
  table.as-cards, table.as-cards tbody, table.as-cards tr, table.as-cards td { display: block; }
  table.as-cards thead { display: none; }
  table.as-cards { min-width: 0 !important; }
  table.as-cards tr {
    padding: .7rem .8rem .5rem; border-bottom: 1px solid var(--border);
    position: relative;
  }
  table.as-cards tr.detail { padding: 0 .8rem .8rem; }
  /* A table inside an opened row stays a table: the card rules are the list's,
     not those of the record the list is showing. */
  table.as-cards tr.detail table { display: table; }
  table.as-cards tr.detail thead { display: table-header-group; }
  table.as-cards tr.detail tbody { display: table-row-group; }
  table.as-cards tr.detail tbody tr { display: table-row; padding: 0; border: 0; }
  table.as-cards tr.detail td { display: table-cell; padding: .35rem .45rem; }
  table.as-cards tr.detail td::before { content: none; }
  table.as-cards tr.detail > td { display: block; padding: 0; }
  table.as-cards tbody tr:hover { background: transparent; }
  table.as-cards td {
    display: flex; justify-content: space-between; align-items: baseline; gap: .2rem .8rem; flex-wrap: wrap;
    padding: .18rem 0; border: 0; text-align: left;
  }
  /* A cell that carries a second line — the contact under the company, the
     country names under their codes — keeps it under, not beside. */
  table.as-cards td > div { flex-basis: 100%; padding-left: calc(6.2rem + .8rem); }
  table.as-cards td::before {
    content: attr(data-label); flex: 0 0 6.2rem;
    font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint);
  }
  table.as-cards td[data-label=""]::before, table.as-cards td.card-lead::before { display: none; }
  /* A figure keeps its place on the right through the flex gap, not through
     text-align, which would push the label along with it. */
  table.as-cards td.num { text-align: left; }
  table.as-cards td.num > :last-child { margin-left: auto; text-align: right; }
  /* The first cell with a label is the name of the record: it reads as a title. */
  table.as-cards td.card-lead { display: block; padding: 0 0 .25rem; font-size: 1.02rem; }
  table.as-cards td.card-lead strong, table.as-cards td.card-lead .row-toggle + * { font-size: 1.02rem; }
  table.as-cards td.card-lead .btn-link, table.as-cards td.card-lead button { min-height: 40px; }
  /* The actions row: full width, spaced for fingers, at the foot of the card. */
  table.as-cards td.actions {
    display: flex; justify-content: flex-end; flex-wrap: wrap; gap: .15rem;
    padding-top: .45rem; margin-top: .3rem; border-top: 1px dashed var(--border);
    position: static; box-shadow: none; background: transparent;
  }
  table.as-cards td.actions::before { display: none; }
  table.as-cards td.card-toggle { position: absolute; right: .5rem; top: .55rem; padding: 0; }
  table.as-cards td.card-toggle::before { display: none; }
  table.as-cards td.card-lead { padding-right: 2.4rem; }
  table.as-cards .row-toggle { width: 36px; height: 36px; }
  /* The status box is a control; it keeps its own width. */
  table.as-cards select.select-sm { max-width: 60%; }
  table.as-cards tr.is-flagged { background: var(--warn-soft); }
  table.as-cards tr.is-flagged > td:first-child { box-shadow: none; }
  table.as-cards tr.is-flagged { box-shadow: inset 3px 0 0 var(--warn); }

  /* ------------------------ a quotation, as a card ----------------------- */

  /* Eleven columns laid out one under the other made a card half a screen
     long, with "Chargeable kg" wide enough to push its own figure onto the
     next line. The same eleven cells, placed: the number, who it is for, where
     it is going, what it weighs, what it costs and where it stands — seven
     lines, and three quotations to a screen instead of one. */
  #quotation-rows > tr:not(.detail) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: .05rem .7rem;
    /* A cell that runs to three lines — an approved offer naming its service
       and its shipment — should not push the price beside it down the card. */
    align-items: start;
    padding: .55rem .8rem .4rem;
  }
  #quotation-rows > tr:not(.detail) > td { padding: .08rem 0; min-width: 0; }
  /* The order on the card is not the order of the columns, and a pair has to
     be next to its other half to share a line with it: the number, the
     customer, then the date against the country, the services, the two
     weights, and the price against the status. */
  #quotation-rows td.card-lead { order: 0; }
  #quotation-rows td[data-label="Customer"] { order: 1; }
  #quotation-rows td[data-label="Date"] { order: 2; }
  #quotation-rows td[data-label="Country"] { order: 3; }
  #quotation-rows td[data-label="Services"] { order: 4; }
  #quotation-rows td[data-label="Actual kg"] { order: 5; }
  #quotation-rows td[data-label="Chargeable kg"] { order: 6; }
  #quotation-rows td[data-label="Price"] { order: 7; }
  #quotation-rows td[data-label="Status"] { order: 8; }
  #quotation-rows td.actions { order: 9; }

  #quotation-rows td.card-lead,
  #quotation-rows td[data-label="Customer"],
  #quotation-rows td[data-label="Services"],
  #quotation-rows td.actions { grid-column: 1 / -1; }
  #quotation-rows td[data-label="Date"],
  #quotation-rows td[data-label="Actual kg"],
  #quotation-rows td[data-label="Price"] { grid-column: 1; }
  /* The right-hand half keeps to its half: a figure that outgrows it wraps
     inside its own column rather than sliding over the one beside it. */
  #quotation-rows td[data-label="Country"],
  #quotation-rows td[data-label="Chargeable kg"],
  #quotation-rows td[data-label="Status"] {
    grid-column: 2; justify-self: end; text-align: right; max-width: 100%;
  }
  /* Two services mean two figures, and they read down, one under the other,
     in the same order in both columns — so the weight and the price on a line
     belong to the same service. */
  #quotation-rows td[data-label="Price"] { display: block; }
  #quotation-rows td[data-label="Chargeable kg"] .service-line { justify-content: flex-end; }
  #quotation-rows td[data-label="Price"] .service-line { justify-content: flex-start; }

  /* A date is a date and a country is a country: those two say what they are.
     The figures keep their names, because 24 kg and 24 kg are not the same
     thing and only the label says which is which. */
  #quotation-rows td[data-label="Date"]::before,
  #quotation-rows td[data-label="Country"]::before,
  #quotation-rows td[data-label="Customer"]::before,
  #quotation-rows td[data-label="Services"]::before,
  #quotation-rows td[data-label="Price"]::before,
  #quotation-rows td[data-label="Status"]::before { display: none; }
  /* The two weights stand as a pair, each under its own name: half a phone is
     not enough for "Chargeable kg" and a figure side by side, and a heading
     that wraps away from its figure is worse than one above it. The figure
     says kg, so the heading gives the unit up. */
  #quotation-rows td[data-label="Actual kg"],
  #quotation-rows td[data-label="Chargeable kg"] { display: block; }
  #quotation-rows td[data-label="Actual kg"]::before,
  #quotation-rows td[data-label="Chargeable kg"]::before {
    display: block; margin: 0 0 .02rem; font-size: .6rem; letter-spacing: .06em; white-space: nowrap;
  }
  #quotation-rows td[data-label="Actual kg"]::before { content: 'Actual'; }
  #quotation-rows td[data-label="Chargeable kg"]::before { content: 'Chargeable'; }
  #quotation-rows td[data-label="Actual kg"],
  #quotation-rows td[data-label="Chargeable kg"] { font-size: .85rem; }
  #quotation-rows td.num > :last-child { margin-left: 0; }

  /* The customer is the title under the number; the contact follows the name
     rather than hanging under it. */
  #quotation-rows td[data-label="Customer"] { display: block; }
  #quotation-rows td[data-label="Customer"] strong { font-size: .95rem; }
  #quotation-rows td[data-label="Customer"] > div { display: inline; }
  #quotation-rows td[data-label="Customer"] > div::before { content: ' · '; }

  /* The date and the country are one quiet line across the card. */
  #quotation-rows td[data-label="Date"],
  #quotation-rows td[data-label="Country"] { font-size: .8rem; color: var(--text-soft); }
  #quotation-rows td[data-label="Country"] { display: block; }
  #quotation-rows td[data-label="Country"] > div { display: inline; }
  #quotation-rows td[data-label="Country"] > div::before { content: ' · '; }

  /* What was quoted, on its own line, and the price it came to in bold. */
  #quotation-rows td[data-label="Services"] { display: flex; flex-wrap: wrap; gap: 0 .9rem; font-size: .85rem; }
  #quotation-rows td[data-label="Price"] { font-size: 1.02rem; font-weight: 700; }
  #quotation-rows td[data-label="Status"] { display: block; }
  #quotation-rows td[data-label="Status"] > div { padding-left: 0; font-size: .72rem; }
  #quotation-rows > tr:not(.detail) > td > div { padding-left: 0; }
  #quotation-rows td.card-lead { padding-bottom: .1rem; }
  #quotation-rows td.actions { margin-top: .3rem; }

}

/* --------------------------------- dialogs ------------------------------- */

/* A screen this narrow cannot hold three figures across without "1224.00 kg"
   running out of its box, so the six go two at a time. */
@media (max-width: 360px) {
  .summary-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* One column under the phone's width: two halves of a record side by side is
   two halves of nothing. */
@media (max-width: 700px) {
  .qd { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  /* Up from the bottom edge, full width, scrolling inside, with the buttons
     that decide always in reach and never under the keyboard. */
  .app-dialog, .list-dialog, .email-dialog, .print-dialog, .form-dialog {
    width: 100vw; max-width: 100vw; margin: auto 0 0;
    border-radius: 16px 16px 0 0;
    max-height: calc(100dvh - var(--safe-top) - .5rem);
  }
  .dialog-body {
    padding: 1rem 1rem calc(1rem + var(--safe-bottom));
    max-height: calc(100dvh - var(--safe-top) - .5rem); overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  .dialog-actions {
    position: sticky; bottom: calc(-1rem - var(--safe-bottom)); z-index: 1;
    margin: .8rem -1rem calc(-1rem - var(--safe-bottom)); padding: .7rem 1rem calc(.7rem + var(--safe-bottom));
    background: var(--surface); border-top: 1px solid var(--border);
    flex-wrap: wrap;
  }
  .dialog-actions .btn { flex: 1 1 auto; justify-content: center; }
  .email-dialog textarea { min-height: 14rem; }
  .print-body { max-height: calc(100dvh - var(--safe-top) - .5rem); }
  .print-toolbar { flex-direction: column; }
  .print-toolbar .dialog-actions { position: static; margin: 0; padding: 0; border: 0; }
  /* The A4 page stays A4; it is shown smaller, and scrolls if it must. */
  .print-stage { padding: .6rem; overflow: auto; }
  .print-sheet { zoom: .46; }
  .review-list { max-height: 50dvh; }
}

/* ------------------------------ new quotation ---------------------------- */

@media (max-width: 899px) {
  /* The price, always in view once there is one: a bar over the bottom edge
     that shows what the Prices panel below already holds, and jumps to it. */
  .mobile-price {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    display: flex; align-items: center; gap: .6rem;
    padding: .55rem calc(.9rem + var(--safe-right)) calc(.55rem + var(--safe-bottom)) calc(.9rem + var(--safe-left));
    background: var(--surface); border-top: 1px solid var(--border);
    box-shadow: 0 -6px 24px rgba(16, 24, 40, .12);
    font: inherit; color: var(--text); text-align: left; cursor: pointer; width: 100%;
  }
  .mobile-price[hidden] { display: none; }
  .mobile-price .mp-lines { display: flex; flex-direction: column; gap: .1rem; min-width: 0; flex: 1 1 auto; }
  .mobile-price .mp-line { display: flex; justify-content: space-between; gap: .6rem; align-items: baseline; }
  .mobile-price .mp-service { font-size: .82rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mobile-price .mp-price { font-size: 1.15rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .mobile-price .mp-line.is-chosen .mp-service { color: var(--text); font-weight: 600; }
  .mobile-price .mp-go { color: var(--accent); font-size: .8rem; font-weight: 600; white-space: nowrap; }
  body.has-mobile-price .app-main { padding-bottom: calc(5.5rem + var(--safe-bottom)); }
  /* A bar fixed over the bottom edge can come to rest on top of the control the
     browser has just scrolled to — a service checkbox, a box being focused —
     and swallow the tap meant for it. Scrolling stops short of the bar. */
  html { scroll-padding-bottom: calc(6rem + var(--safe-bottom)); }

  /* One height for every box in a package row, so a select and an input sit
     on the same line to the pixel. */
  .package input, .package select { height: 44px; min-height: 44px; }
  .direction-toggle button, .quote-direction button { min-height: 48px; font-size: 1rem; }
  .service-option, .service-card { min-height: 52px; }
  .btn-hero { min-height: 52px; }
  .card-head .btn-sm { min-height: 36px; }
}

@media (min-width: 900px) {
  .mobile-price { display: none !important; }
}
