/* ==========================================================================
   Supplygoods — design system
   One stylesheet for every surface. Dark is the primary theme: near-black
   canvas, hairline borders, tabular numerals, restraint over decoration.
   Light theme is a token override at the bottom.
   Source of truth for tokens: design.md
   ========================================================================== */

/* --- tokens: dark (primary) ---------------------------------------------- */
:root,
[data-theme="dark"] {
  /* Said to the browser, not only to the stylesheet. Without it every native
     control is drawn light: an unticked checkbox on a black card is a solid
     white block, which on a panel of nine of them is the loudest thing on the
     screen and the one thing on it that means nothing. The ticked one, which
     is the whole answer, was the quiet one. Scrollbars, selects and date
     pickers come with it. */
  color-scheme: dark;
  --canvas: #000000;
  --shell: #050506;
  --surface-1: #0e0e10;
  --surface-2: #16161a;
  --card: #0a0a0b;
  --card-hover: #101012;
  --grid: rgba(255, 255, 255, .022);

  --text: #f5f5f7;
  /* Measured rather than eyeballed, in a real browser over ten screens in both
     themes: the dim tone was 2.96:1 against the canvas and 3.08:1 against a
     card, on nine and ten pixel type. That is what a SKU, a timestamp, a table
     heading and a breadcrumb were being drawn in, which is information rather
     than decoration, and it is a screen people read all day.

     WCAG AA asks 4.5:1 of ordinary text. Dim clears it now and muted moves
     with it so the ladder is still a ladder: quiet is a relationship between
     three tones rather than one of them being nearly invisible. */
  --text-muted: rgba(245, 245, 247, .66);
  --text-dim: rgba(245, 245, 247, .5);

  --border: rgba(255, 255, 255, .07);
  --border-strong: rgba(255, 255, 255, .14);
  --border-focus: rgba(226, 64, 47, .55);

  /* the mark has its own colour, so it does not move when the interface
     accent does */
  --brand-mark: #e2402f;

  --accent: #e2402f;
  /* The red does two jobs and only one of them is read. A dot, a line, a bar
     in a chart and the mark itself carry no words, so they are the brand and
     stay exactly this. A panel with white on it is text, and text has a floor:
     white on #E2402F measures 4.19:1 and misses AA. `--accent-fill` is what
     goes under white, and the light theme has been carrying it since it was
     written, because on white the red had to darken anyway. So this is the
     light theme's own answer brought across rather than a colour invented
     here, and nothing about the mark moves. */
  --accent-fill: #cf3a29;
  --accent-ink: #ffffff;
  --accent-soft: rgba(226, 64, 47, .13);
  --accent-line: rgba(226, 64, 47, .32);

  /* the ink the temple on the auth panel is drawn in: the accent red taken
     down to a bordeaux that only just separates from the panel */
  --art-ink: #e2402f;
  --art-op: .1;

  /* a pane of glass: the light along its top edge, the sheen across its
     shoulder, the rim around it, and the fill a control on it takes */
  --glass-lip: rgba(255, 255, 255, .1);
  --glass-sheen: rgba(255, 255, 255, .16);
  --glass-rim: rgba(255, 255, 255, .13);
  --glass-fill: rgba(255, 255, 255, .07);
  --glass-fill-hover: rgba(255, 255, 255, .13);
  --glass-base: rgba(0, 0, 0, .35);
  --glass-shadow: 0 18px 40px -22px rgba(0, 0, 0, .9);

  /* the four steps a density cell can sit at before it turns red */
  --viz-cell-0: rgba(255, 255, 255, .035);
  --viz-cell-1: rgba(255, 255, 255, .075);
  --viz-cell-2: rgba(255, 255, 255, .14);
  --viz-cell-3: rgba(255, 255, 255, .22);

  /* The tone a status is drawn in, and the tone its word is written in, are
     two different jobs. A dot, a line or a fill is a mark and the tone does
     it; a chip is text on a tint of that same tone, and on a light background
     that lands around 3.4:1, which is under what ordinary text needs. Dark
     does not have the problem for the four status tones, so those inks are the
     tone here and are darkened only in the light theme. */
  --good-ink: var(--good);
  --warn-ink: var(--warn);
  --info-ink: var(--info);
  --bad-ink: var(--bad);
  /* The accent is the exception, and it fails in the opposite direction from
     the light theme. The brand red on its own tint over the card measures
     4.28:1 here, so the word is lifted rather than darkened: the mirror of the
     `#b6301f` the light theme already carries. 5.32:1. Only the word on a
     tint reads it, which is the accent badge, the hot menu count and the hot
     pill. */
  --accent-ink-on-soft: #ef5a48;

  --good: #22c07a;
  --good-soft: rgba(34, 192, 122, .12);
  /* Chosen. Darker than the good green, because this is a state a whole
     grid can be in at once and the good green at that count is a lot. */
  --picked: #2f8f63;
  --picked-line: #2b6f50;
  --picked-soft: rgba(47, 143, 99, .13);
  --warn: #f5a623;
  --warn-soft: rgba(245, 166, 35, .13);
  /* the accent is red now, so danger has to be told apart by lightness
     rather than hue, or every warning reads as a button */
  --bad: #ff9d8a;
  --bad-soft: rgba(255, 157, 138, .13);
  --info: #7ea1ff;
  --info-soft: rgba(126, 161, 255, .13);
  --shopify: #95bf47;

  --radius-lg: 18px;
  --radius: 13px;
  --radius-sm: 9px;
  --radius-pill: 980px;

  --shadow-card: none;
  --shadow-pop: 0 1px 2px rgba(0, 0, 0, .6), 0 24px 64px rgba(0, 0, 0, .7);
  --ring: 0 0 0 3px rgba(226, 64, 47, .28);

  --font-display: 'Poppins', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
  --font-accent: 'Montserrat', 'Poppins', -apple-system, 'Helvetica Neue', sans-serif;
  /* The label face. Not a monospace any more: the same family, set in capitals
     and tracked out, which is what carries a reference number here. */
  --font-mono: 'Poppins', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;

  --sidebar-w: 244px;
  --topbar-h: 60px;
  --gutter: 34px;

  /* Apple-ish easing: quick out, settled in. Never bounce. */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --fast: 140ms var(--ease);
  --med: 260ms var(--ease);
}

/* Anything on the label face is uppercase and slightly tracked, so a code
   reads as a code. Set once here rather than on forty selectors. */
[class*="mono"],
.eyebrow, .ngroup, .brandblock .bs, .userrow .ur, .kpi .klabel,
thead th, .pager .meta, .sla, .badge, .pill, .chip, .tcount,
.msg .mrole, .msg .mchan, .msg .mtime, .throw .ttime, .feedrow .ftime,
.feedrow .fname, .feedrow .fmeta, .prodcell .ps, .store-id .sdomain,
.leg .lrole, .parcel .pmeta, .lastmile,
.mpill .mno, .rolerow .rtxt span,
.defrow .dt, .barcol .blabel, .rrow .rl, .filerow .fname, .msg .att,
.qhead b.mono, .codebox {
  text-transform: uppercase;
  letter-spacing: .04em;
}
/* an address someone has to type back is not a label, and neither is an
   email address somebody has to read out or copy.

   `.is-email` rather than `.ps.is-email`, because the exemption belongs to the
   thing rather than to the one place it was first noticed: the console's order
   drawer printed a customer's address as CUSTOMER13@EXAMPLE.COM, in capitals,
   under a heading that says Email, on the screen somebody uses to write to
   them. Anything on the label face carrying an address opts out with this one
   class. */
.addr, .mpill b, .prodcell .pt, .composer textarea, .msg .mbody,
.is-email, .pcmail { text-transform: none; letter-spacing: 0; }

/* a face on every name: photo when there is one, initials on a tone from the
   id when there is not */
.avat {
  display: inline-grid; place-items: center; flex: none;
  font-family: var(--font-display); font-weight: 600; letter-spacing: .01em;
  color: color-mix(in srgb, var(--av, var(--text-muted)) 70%, var(--text));
  background: color-mix(in srgb, var(--av, var(--text-dim)) 22%, var(--surface-2));
  background-size: cover; background-position: center;
  overflow: hidden; user-select: none;
}
.avat.is-photo { color: transparent; background-color: var(--surface-2); }
.avat.is-ring { box-shadow: 0 0 0 1px var(--border-strong); }
.avstack { display: inline-flex; align-items: center; }
.avstack .avat + .avat { margin-left: -8px; box-shadow: 0 0 0 2px var(--card); }
.avname { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.avname b { font-weight: 500; }
.avname > span { min-width: 0; }
.avname .ps { margin-top: 1px; }

.photopick { display: flex; align-items: flex-start; gap: 16px; }
.photopick .ppview { flex: none; }

/* The crop surface. What is inside the square is what gets stored, and the
   circle over it is where the avatar will actually cut: a square preview of a
   round hole is a preview of the wrong thing. */
.ppcrop {
  position: relative; width: 176px; height: 176px;
  border-radius: var(--radius); overflow: hidden;
  border: 1px solid var(--border-strong); background: #0a0a0b;
}
.ppcrop canvas {
  display: block; width: 176px; height: 176px;
  cursor: grab; touch-action: none;
}
.ppcrop canvas.is-dragging { cursor: grabbing; }
.ppcrop canvas:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Drawn over the canvas, and never in the way of a drag. */
.ppcrop .ppmask {
  position: absolute; inset: 0; pointer-events: none;
  border-radius: 50%;
  /* Dark enough to show where the cut is, light enough that what is about to
     be cut off is still visible: aiming at something you cannot see is not
     aiming. The hairline is the edge itself. */
  box-shadow: 0 0 0 999px rgba(0, 0, 0, .5), inset 0 0 0 1px rgba(255, 255, 255, .22);
}
.ppcrop.is-square .ppmask { border-radius: var(--radius-sm); }

.ppzoom { display: flex; align-items: center; gap: 8px; margin-top: 10px; width: 176px; }
.ppzoom input[type=range] { flex: 1; min-width: 0; accent-color: var(--accent); }
.ppzoom .btn { padding: 2px 9px; }
.ppzoom .btn:last-child { margin-left: auto; }
@media (max-width: 520px) {
  .photopick { flex-direction: column; }
}
.photopick .ppside { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.photopick .ppside .hint { font-size: 12px; max-width: 34ch; }

/* --- reset --------------------------------------------------------------- */
* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--text);
  background-color: var(--canvas);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "cv11", "ss01";
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
button { font-family: inherit; }

::selection { background: var(--accent-soft); color: var(--text); }

/* keyboard focus only. Never kill the ring, never show it on mouse clicks. */
:focus { outline: none; }
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}

/* thin, unobtrusive scrollbars */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: content-box;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* The hidden attribute means hidden. Any class carrying a display of its own
   beats the browser's own rule for it, which is how a row of buttons went on
   being drawn after the code that hid it said so. */
[hidden] { display: none !important; }

/* --- shell --------------------------------------------------------------- */
.shell { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--shell);
  border-right: 1px solid var(--border);
  padding: 20px 12px 24px;
  position: sticky;
  top: 0;
  height: 100vh;
  /* Both axes, named. Setting only overflow-y turns the other one to auto by
     itself, so the glow that is meant to bleed past the edge became four
     pixels of sideways swipe on a menu that has nothing to scroll to. */
  overflow: hidden auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
}

/* the gap between the work and the account block, which the beams show through */
.sidespace {
  flex: 1;
  min-height: 40px;
  position: relative;
  overflow: hidden;
}
/* Beams of light drifting up behind the menu, in the one red this product
   already uses. The blur lives here rather than on the canvas: a canvas
   filter is the sort of thing a browser can be late to, and a missing blur
   would read as a mistake rather than as a plain page.

   Faded at both ends, hardest in the middle, so it is ground under the menu
   and never a thing competing with it. */
/* --- the light behind the menu --------------------------------------------
   Two soft masses of the one red this product already uses, crossing each
   other at a fiftieth of lava lamp speed. Twenty seven seconds and thirty
   nine: they share no factor worth noticing, so they never quite line up and
   the column is never the same twice.

   Masked away at both ends, because a wash that runs into the brand at the
   top or the account block at the foot stops being weather and starts being
   a panel with a tint on it.
   -------------------------------------------------------------------------- */
.sidedrift {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  opacity: .5;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 91%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 91%, transparent 100%);
}
.sidedrift i {
  position: absolute;
  display: block;
  left: -35%;
  width: 170%;
  height: 40%;
  border-radius: 50%;
  filter: blur(58px);
}
.sidedrift i:first-child {
  background: radial-gradient(closest-side, rgba(226, 64, 47, .22), transparent 100%);
  animation: sidedrift-a 27s ease-in-out infinite;
}
.sidedrift i:last-child {
  background: radial-gradient(closest-side, rgba(143, 31, 22, .30), transparent 100%);
  animation: sidedrift-b 39s ease-in-out infinite;
}
/* Travelling the same way rather than past each other. Two masses starting
   at opposite ends spent most of the cycle at opposite ends, which read as
   two separate lights. Overlapping ranges and different periods keep them
   near one another: they slide together into one larger mass, come apart by
   half a column, and go back, without ever repeating. */
@keyframes sidedrift-a {
  0%, 100% { top: 2%;  transform: translateX(-6%); }
  50%      { top: 48%; transform: translateX(7%); }
}
@keyframes sidedrift-b {
  0%, 100% { top: 18%; transform: translateX(8%); }
  50%      { top: 62%; transform: translateX(-6%); }
}
/* Everything in the menu sits above it. Without this the light would be
   drawn over the nav rather than behind it. */
.sidebar > *:not(.sidedrift) { position: relative; z-index: 1; }

/* Light is paper, and light added to paper is a stain rather than a glow.
   Multiplying darkens instead, so the shape survives on white. */
html[data-theme="light"] .sidedrift {
  opacity: .18;
  mix-blend-mode: multiply;
}

/* Somebody who asked their machine to stop moving things gets the wash and
   none of the travel. Two masses standing still still read as light. */
@media (prefers-reduced-motion: reduce) {
  .sidedrift i { animation: none; }
  .sidedrift i:first-child { top: 6%; }
  .sidedrift i:last-child { top: 58%; }
}

.sidebottom { flex-shrink: 0; }
.sidebottom .ngroup { padding-top: 10px; }

.brandblock {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  padding: 4px 10px 2px;
  /* It is a button in the sidebar so the mark can draw itself when somebody
     presses it, and nothing else about it changes. */
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  text-align: left;
  width: 100%;
  cursor: pointer;
}
button.brandblock:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 10px; }
.brandblock .mark { overflow: visible; }

/* A store wears its own mark wherever it is listed. */
.rolerow .av .storelogo {
  width: 100%; height: 100%;
  object-fit: contain;
  border-radius: 6px;
  display: block;
}
.brandblock .mark { width: 26px; height: 26px; color: var(--brand-mark); flex: none; }
.authside .brandblock .mark { width: 30px; height: 30px; }
.brandblock .bn {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: .04em;
}

/* The name is the logo, so it is drawn rather than typed: Manrope 600 with
   one capital S, as outlines, masked so it takes the colour of the text around
   it. Typed, it would need a second font on every page for one word.
   assets/brand/word.svg is made by tools/build-brand.js. */
.wordmark {
  display: block;
  height: 17.5px;
  aspect-ratio: 583 / 100;
  background: currentColor;
  -webkit-mask: url(brand/word.svg) no-repeat center / contain;
          mask: url(brand/word.svg) no-repeat center / contain;
}
.authside .brandblock .wordmark { height: 20px; }
.brandblock .bs {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--text-dim);
  letter-spacing: .12em;
  text-transform: uppercase;
  margin-top: 2px;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

/* connected store identity card */
.store-id {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  margin: 0 2px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  transition: border-color var(--fast), background var(--fast);
}
.store-id:hover { border-color: var(--border-strong); background: var(--card-hover); }
.store-id .sg {
  width: 26px; height: 26px;
  border-radius: 7px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
}
/* No logo of its own yet, so it wears ours rather than Shopify's green */
.sg { background: var(--accent-fill); color: #fff; overflow: hidden; }
.sg.has-logo { background: var(--card-hover); border: 1px solid var(--border); }
.sg img { width: 100%; height: 100%; object-fit: cover; }
.store-id .sname {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.2;
}
.store-id .snm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the connection, said in one dot rather than a chip of its own */
.sdot {
  width: 6px; height: 6px;
  border-radius: 50%;
  flex: none;
  background: var(--good);
  animation: pulse 2.4s var(--ease) infinite;
}
.sdot.is-wait { background: var(--warn); animation: none; }
.sdot.is-bad { background: var(--bad); animation: none; }
.store-id .sdomain {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--text-dim);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.store-id.is-disconnected { border-color: var(--warn-soft); }
.store-id.is-disconnected .sg { background: var(--surface-2); }

/* nav */
.ngroup {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 16px 12px 7px;
}
.navitem {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  font-weight: 450;
  color: var(--text-muted);
  margin-bottom: 1px;
  position: relative;
  transition: background var(--fast), color var(--fast);
}
.navitem svg { width: 16px; height: 16px; flex-shrink: 0; opacity: .8; }
.navitem:hover { background: var(--surface-1); color: var(--text); }
.navitem:hover svg { opacity: 1; }
.navitem.active {
  background: var(--surface-2);
  color: var(--text);
  font-weight: 600;
}
.navitem.active svg { color: var(--accent); opacity: 1; }
.navitem.active::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 17px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}
/* --- one level of nesting ------------------------------------------------
   A child row is the same row, stepped in, with a hairline running down the
   branch so the indent reads as belonging rather than as a mistake in the
   padding. The thread stops short at the bottom so it points at the last
   child rather than running past it into the next group.

   The icon is smaller and the text is a step quieter: the parent is where
   somebody goes by default and the child is the way to the other half.
   The active marker is the same accent bar at the same distance, because a
   child that is on has to look as on as a parent that is. */
.navbranch {
  position: relative;
  margin: 1px 0 1px 11px;
  padding-left: 13px;
}
.navbranch::before {
  content: "";
  position: absolute;
  left: 0;
  top: 2px;
  bottom: 14px;
  width: 1px;
  background: var(--border);
}
.navitem.navsub {
  padding-top: 6px;
  padding-bottom: 6px;
  font-size: 13px;
  color: var(--text-dim);
}
.navitem.navsub svg { width: 14px; height: 14px; }
.navitem.navsub:hover { color: var(--text-muted); }
.navitem.navsub.active { color: var(--text); }
.navitem.navsub.active::before { left: -25px; }

.navitem .nvalue {
  margin-left: auto;
  display: inline-flex; align-items: baseline; gap: 5px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .01em;
  color: var(--text-muted); white-space: nowrap;
}
.navitem.active .nvalue { color: var(--text); }
/* a top-up still being checked: nearly yours, so it sits a step back */
.navitem .nvalue i { font-style: normal; color: var(--text-dim); }
.navitem .nvalue + .nbadge { margin-left: 6px; }

/* Not built yet, said in the menu rather than found on the screen. Grey and
   quiet on purpose: it is a note about the roadmap, not a thing to press. The
   row keeps its full weight, because a menu item at half opacity reads as
   disabled and this one still opens. */
.navitem .nsoon {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
/* Not a link, and it has to look like one it is not worth trying to press.
   It stays in the menu so a merchant knows it is coming, and it does nothing:
   no pointer, no hover, no selection. */
.navitem.soon {
  color: var(--text-dim);
  cursor: default;
  user-select: none;
}
.navitem.soon:hover { background: none; color: var(--text-dim); }
.navitem.soon svg,
.navitem.soon:hover svg { opacity: .4; }

.navitem .nbadge {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 2px 6px;
  border-radius: var(--radius-pill);
}
/* The word on a tint, so it takes the ink rather than the tone, the way the
   accent badge beside it already does. It read `--accent` and measured
   4.44:1 in the dark and 3.94:1 in the light over the sidebar, which is
   lower than the same badge on a card because the sidebar is the lighter
   surface of the two. It is the one badge on the menu that says work is
   waiting, so it is the last string on this app that should be hard to
   read. */
.navitem .nbadge.hot { color: var(--accent-ink-on-soft); border-color: var(--accent-line); background: var(--accent-soft); }

.sidefoot {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
  padding: 12px 11px 0;
  border-top: 1px solid var(--border);
}
.sidefoot .userrow { flex: 1; min-width: 0; }

.sidefoot .iconbtn { width: 28px; height: 28px; flex: none; }
.userrow { display: flex; align-items: center; gap: 9px; min-width: 0; }
/* overflow:hidden is what makes min-width:0 mean anything here. Without it a
   name too long for the column does not shrink and does not ellipsise: it
   simply draws past the edge, under the two buttons beside it. */
.userrow .uname {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-width: 0; max-width: 100%; overflow: hidden;
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--text); font: inherit; text-align: left;
}
/* Both lines clip the same way. The role line was free to wrap, and the one
   thing it holds is the merchant number, so it broke at the hyphen in
   ECS-0001 and put four digits on a line of their own. */
.userrow .un, .userrow .ur {
  display: block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.userrow .un { font-size: 12.5px; font-weight: 500; }
.userrow .ur { font-family: var(--font-mono); font-size: 9px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .08em; }

/* the face doubles as the way to change it */
.facebtn {
  position: relative; flex: none; padding: 0; border: 0; background: none; cursor: pointer;
  line-height: 0; border-radius: 11px;
}
.facebtn .fcam {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: inherit; color: #fff; background: rgba(0, 0, 0, .55);
  opacity: 0; transition: opacity var(--fast);
}
.facebtn:hover .fcam, .facebtn:focus-visible .fcam { opacity: 1; }
.facebtn:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
.tb-face { margin-left: 2px; }

/* main + topbar */
.main { flex: 1; min-width: 0; }

.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  height: var(--topbar-h);
  padding: 0 var(--gutter);
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--canvas) 72%, transparent);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  position: sticky;
  top: 0;
  z-index: 40;
}
.topbar .tb-sub { font-size: 12.5px; color: var(--text-muted); }
.topbar .tb-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

/* In the topbar, written by the shell. One per app rather than one per page. */
.syncstate {
  display: flex;
  white-space: nowrap;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  color: var(--text-muted);
}
/* A phone has no room for a sentence beside a face and a menu, and the dot
   carries the state on its own: green is synced, amber is not syncing, red is
   not answering. The words are the title attribute. */
.tb-sync { overflow: hidden; }
@media (max-width: 720px) {
  .tb-sync { gap: 0; font-size: 0; }
}

/* A heading and one short fact beside it, on the same baseline. Used by the
   console for a section heading with its count. */
.titlerow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 12px; }

.iconbtn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}
.iconbtn:hover { color: var(--text); border-color: var(--border-strong); background: var(--card-hover); }
.iconbtn svg { width: 15px; height: 15px; }
.iconbtn.spinning svg { animation: spin 700ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* content */
.content {
  padding: 28px var(--gutter) 72px;
  /* Wide enough to fill a large screen rather than leaving a third of it
     black. Not unlimited: prose is capped separately, but a table that runs
     to the edge of a 27 inch monitor is a table nobody can read across.
     1420 stopped short of a 1920 screen by three hundred pixels, which read
     as a page that had not finished loading. */
  max-width: 1680px;
}
.content.narrow { max-width: 880px; }

.crumb {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 14px;
}
.crumb b { color: var(--text-muted); font-weight: 400; }

.pagehead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 26px;
}
.pagetitle {
  font-size: clamp(30px, 3.6vw, 44px);
  font-weight: 800;
  letter-spacing: -.035em;
  line-height: 1.02;
  margin: 0 0 10px;
}
.pagesub {
  font-size: 14.5px;
  color: var(--text-muted);
  margin: 0;
  max-width: 640px;
  line-height: 1.55;
}
.pagesub em { font-family: var(--font-accent); font-style: italic; color: var(--text); }
.statusline {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 8px;
}
.statusline .lbl {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--good);
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.statusline .lbl::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
}
.statusline em { font-family: var(--font-accent); font-style: italic; color: var(--text-muted); }

.eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.eyebrow .right { margin-left: auto; text-transform: none; letter-spacing: .04em; }
.eyebrow .right b { color: var(--good); font-weight: 500; }

/* --- layout helpers ------------------------------------------------------ */
.grid { display: grid; gap: 14px; }
.grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.split { display: grid; grid-template-columns: minmax(0, 1.32fr) minmax(0, 1fr); gap: 14px; }
/* Four figures that belong together: four across while there is room for them,
   then two by two, then one. Never three, which leaves one tile orphaned on a
   row of its own. */
.grid-fit { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 1340px) {
  .grid-fit { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 1240px) {
  .grid-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* minmax(0, 1fr) rather than 1fr, the way the two column rule above already
     writes it. A bare 1fr is as wide as its widest child, so the one column
     the thread lands in on a phone was 383px inside a 358px screen and took
     the whole page sideways with it. Measured at 390px on the gallery, which
     is the one screen that draws every component at once. */
  .split { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 1040px) {
  .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 680px) {
  .grid-5, .grid-4, .grid-3, .grid-2, .grid-fit { grid-template-columns: 1fr; }
}

/* --- cards --------------------------------------------------------------- */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  position: relative;
  box-shadow: var(--shadow-card);
}
.card.pad-0 { padding: 0; overflow: hidden; }
.card.tall { display: flex; flex-direction: column; }
a.card { transition: border-color var(--fast), background var(--fast), transform var(--fast); }
a.card:hover { border-color: var(--border-strong); background: var(--card-hover); }
a.card:active { transform: scale(.995); }

.cardhead {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  /* The controls drop to their own line rather than crushing the title. A
     period picker of seven segments in the head of a narrow card turned
     "Sales by country" into three words on three lines, each one word wide.
     Wrapping costs a row of height on a narrow card and nothing at all on a
     wide one, and a title nobody can read costs the card. */
  flex-wrap: wrap;
}
.cardhead h3 {
  margin: 0; font-size: 13.5px; font-weight: 600;
  /* Never narrower than its own words, which is what makes the row wrap
     instead of squeezing. */
  flex: 0 1 auto; min-width: max-content;
}
/* The buttons keep their width and the name gives way, rather than a long
   shop name pushing them off and wrapping itself over two lines. */
.cardhead .right { margin-left: auto; display: flex; align-items: center; gap: 8px; flex: none; }
.cardhead > .teamstrip { min-width: 0; }

.scroller { overflow-y: auto; overscroll-behavior: contain; }
.scroller.h-560 { max-height: 560px; }

/* --- KPI ----------------------------------------------------------------- */
.kpi { padding: 18px; }
.kpi .klabel {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 14px;
}
.kpi .knum {
  font-size: clamp(28px, 3vw, 40px);
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.kpi .knum .unit { font-size: .55em; font-weight: 600; letter-spacing: -.02em; color: var(--text-muted); }
.kpi .knum.is-empty { color: var(--text-dim); font-weight: 500; }
.kpi .kfoot {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-dim);
  margin-top: 12px;
  display: flex;
  align-items: center;
  gap: 7px;
}
.kpi .kfoot b { color: var(--text-muted); font-weight: 500; }

/* --- pills, badges, chips ------------------------------------------------ */
.pill {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .04em;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
/* What a price did between two answers. Up is the one worth a colour, because
   it is the one that changes what a merchant can sell the thing for; down is
   good news and reads as good news without spending one. The percentage is
   beside the amount rather than instead of it: two euros on twelve and two on
   two hundred are the same figure and not the same news. */
.pricestep { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.pricestep em { font-style: normal; font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); }
.pricestep.is-up { color: var(--bad); }
.pricestep.is-down { color: var(--good); }
.pill.up { background: var(--good-soft); color: var(--good); }
.pill.down { background: var(--bad-soft); color: var(--bad); }
.pill.flat { background: var(--surface-1); color: var(--text-dim); }
/* Money in flight: neither good news nor bad, but not something to read past. */
.pill.warn { background: var(--warn-soft); color: var(--warn-ink); }

/* --- what happened, in order ---------------------------------------------
   The step that has not happened yet is drawn as still to come rather than
   left off, because a timeline that stops at today does not say what today is
   waiting for.
   -------------------------------------------------------------------------- */
.tline { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
.tstep {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  gap: 12px;
  padding: 0 0 14px;
  position: relative;
}
.tstep:last-child { padding-bottom: 0; }
/* The line between the dots, drawn from each step down to the next. */
.tstep:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 14px;
  bottom: 0;
  width: 1px;
  background: var(--border);
}
.tdot {
  width: 9px; height: 9px;
  margin-top: 4px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--card);
}
.tstep.is-done .tdot { background: var(--good); border-color: var(--good); }
/* The middle of a long history, folded into one line. It really happened, so
   it keeps the line running through it, and it is not an event anybody acts
   on, so the dot is hollow and the words are the quiet ones. */
.tstep.is-dim .tdot { background: var(--border); border-color: var(--border); }
.tstep.is-dim .ttxt b { font-weight: 400; color: var(--text-dim); }
.ttxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ttxt b { font-size: 12.5px; font-weight: 500; }
.tstep:not(.is-done) .ttxt b { color: var(--text-muted); }
.ttxt span { font-size: 11.5px; color: var(--text-dim); line-height: 1.5; }
.ttxt em {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* The transfer slip, in the row. It was a button saying Open proof, which is
   a receipt for a picture: what anybody wants from this column is to see
   whether the amount and the reference are on the screenshot, and a word
   cannot answer that.

   Kept small on purpose. Row height is what decides how much of a queue fits
   on a screen, and this column sits in a list the floor works all day; 34px
   is the height of the two lines of text beside it, so the row does not grow
   at all. The picture is cropped to fill rather than fitted, because a
   screenshot of a banking app is portrait and fitting one leaves a tile that
   is mostly frame. */
.proofthumb {
  width: 46px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface-1);
  overflow: hidden;
  position: relative;
  display: block;
  cursor: pointer;
  transition: border-color var(--fast), transform var(--fast);
}
.proofthumb:hover, .proofthumb:focus-visible { border-color: var(--border-strong); transform: translateY(-1px); }
.proofthumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The frame before the bytes arrive, and what is left of it for a PDF, which
   has no picture to show. */
.proofthumb .pth-mark {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-dim);
}
.proofthumb.is-on .pth-mark { display: none; }
.proofthumb.is-pdf .pth-mark { color: var(--text-muted); }

/* Proof of payment on hover. Below the overlay layer and above everything
   else: it appears over a table row and has to go away the moment a real
   sheet opens on top of it. */
.proofpop {
  position: fixed;
  z-index: 90;
  padding: 6px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .55);
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .12s ease, transform .12s ease;
}
.proofpop.is-on { opacity: 1; transform: translateY(0); }
.proofpop img {
  display: block;
  max-width: 300px;
  max-height: 220px;
  border-radius: 6px;
}
.proofpop .pfile {
  display: block;
  padding: 10px 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
}

/* --- evidence, as photographs ---------------------------------------------
   A dispute is the photograph. It was a filename in a grey box, which is a
   receipt for a photo nobody on the deciding side could look at. Tiles the
   width of the panel they sit in, a lift on hover so it is clear they open,
   and a lightbox behind the press with zoom and a download, because the one
   thing somebody does with damage is look closely at it.
   -------------------------------------------------------------------------- */
.evshots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 8px;
  margin: 8px 0 4px;
}
.evshot {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2);
  cursor: zoom-in;
  transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.evshot:hover, .evshot:focus-visible {
  transform: scale(1.04);
  border-color: var(--border-strong);
  box-shadow: 0 14px 30px rgba(0, 0, 0, .5);
  z-index: 2;
}
.evshot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.evshot .evname {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 14px 8px 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .04em;
  color: #fff;
  text-align: left;
  background: linear-gradient(to top, rgba(0, 0, 0, .74), transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Nothing to show yet, or nothing to show at all. Said in the tile rather
   than left as a broken image, because the two are different facts. */
.evshot .evwait {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px;
  font-size: 10.5px;
  color: var(--text-dim);
  text-align: center;
}
@media (prefers-reduced-motion: reduce) {
  .evshot, .evshot:hover, .evshot:focus-visible { transition: none; transform: none; }
}

/* --- the lightbox --------------------------------------------------------- */
.lbox {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  flex-direction: column;
  background: rgba(0, 0, 0, .92);
  animation: fade var(--fast) both;
}
.lbox .lbbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
  color: #fff;
}
.lbox .lbname {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lbox .lbcount { font-size: 11px; color: rgba(255, 255, 255, .58); }
.lbox .lbtools { margin-left: auto; display: flex; align-items: center; gap: 6px; }
.lbox .lbstage {
  flex: 1;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  touch-action: none;
}
.lbox .lbstage.is-drag { cursor: grabbing; }
.lbox .lbstage img {
  max-width: 100%;
  max-height: 100%;
  transform-origin: center center;
  user-select: none;
  -webkit-user-drag: none;
}
.lbox .lbstage embed { width: 100%; height: 100%; border: 0; }
.lbox .lbnav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .18);
  background: rgba(0, 0, 0, .5);
  color: #fff;
  cursor: pointer;
}
.lbox .lbnav.prev { left: 14px; }
.lbox .lbnav.next { right: 14px; }
.lbox .lbnav:hover { background: rgba(255, 255, 255, .14); }
/* The set along the foot of the lightbox. One line that scrolls rather than
   a grid that wraps: a wrapping rail takes height off the picture, which is
   the one thing anybody opened this for. */
.lbox .lbfilm {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
  padding: 10px 14px;
  overflow-x: auto;
  scrollbar-width: thin;
  border-top: 1px solid rgba(255, 255, 255, .12);
}
.lbox .lbfilm button {
  flex: 0 0 auto;
  width: 56px; height: 56px;
  padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .06);
  color: rgba(255, 255, 255, .6);
  font-family: var(--font-mono); font-size: 10px;
  cursor: pointer;
  opacity: .55;
  transition: opacity var(--fast), border-color var(--fast);
}
.lbox .lbfilm button img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.lbox .lbfilm button:hover { opacity: .85; }
.lbox .lbfilm button.is-on { opacity: 1; border-color: #fff; }

.badge {
  font-family: var(--font-mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .07em;
  padding: 4px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.badge.good, .badge.paid, .badge.fulfilled, .badge.live { background: var(--good-soft); color: var(--good-ink); }
.badge.warn, .badge.unfulfilled, .badge.unpaid, .badge.waiting { background: var(--warn-soft); color: var(--warn-ink); }
.badge.bad, .badge.failed { background: var(--bad-soft); color: var(--bad); }
/* shipped is not in here any more: a parcel that has left is good news, and
   it used to wear the same blue as one still on the bench. */
.badge.info, .badge.process { background: var(--info-soft); color: var(--info-ink); }
.badge.neutral, .badge.soon { background: var(--surface-2); color: var(--text-dim); }
.badge.accent { background: var(--accent-soft); color: var(--accent-ink-on-soft); }

.chip {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-1);
  color: var(--text-dim);
  border: 1px solid var(--border);
}
.chip.sample { color: var(--warn); border-color: var(--warn-soft); background: var(--warn-soft); }

.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.dot.good { background: var(--good); }
.dot.warn { background: var(--warn); }
.dot.bad { background: var(--bad); }
.dot.accent { background: var(--accent); }
.dot.dim { background: var(--text-dim); }
/* The two that were missing. `neutral` and `info` are answers the tone table
   has given since it was written, and a dot with no colour is a dot that is
   not there: on the parcels card In transit and Out for delivery drew an
   empty gap where every other row had a mark. Neutral is the ordinary case
   and reads as one; info is the blue that keeps Out for delivery off the red,
   because that is the good news at the end of the journey and two reds beside
   each other is neither of them standing out. */
.dot.neutral { background: var(--text-muted); }
.dot.info { background: var(--info); }


/* On the way: the globe on the merchant's overview, and the stages beside it.

   The globe is the marketing page's, drawn in white dots on black, and it
   stays black in the light theme too: it is an instrument with its own
   ground, the way a map in a car is, and white dots on a light card are a
   globe nobody can see. The notes that land on it are the site's notes.

   The stages are rows rather than a bar, for the reason the card before it
   gave: what a merchant asks is how many are in each state and which ones
   need them. A stage with nothing in it keeps its row, and its count goes
   quiet, so the rows that hold something carry the eye. */
.onway .ow-body { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(280px, 1fr); border-top: 1px solid var(--border); }
.onway.no-globe .ow-body { grid-template-columns: 1fr; }
.onway.no-globe .ow-globe { display: none; }
.ow-globe {
  position: relative; aspect-ratio: 1; max-height: 620px; overflow: hidden;
  background: #000; border-right: 1px solid var(--border);
  border-bottom-left-radius: inherit; touch-action: pan-y; cursor: grab;
}
.ow-globe:active { cursor: grabbing; }
.ow-globe canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.ow-globe .chip { position: absolute; left: 0; top: 0; z-index: 2; pointer-events: none; width: max-content; max-width: min(300px, calc(100% - 12px)); opacity: 0; transition: opacity .35s ease; will-change: transform; }
.ow-globe .chip.on { opacity: 1; }
.ow-globe .note {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; gap: 10px; align-items: center;
  padding: 8px 11px; border-radius: 13px; font-size: 12.5px; line-height: 1.3; color: #f5f5f7;
  background: rgba(18, 18, 21, .92); border: 1px solid rgba(255, 255, 255, .13);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 18px 34px -18px rgba(0, 0, 0, .95);
}
.ow-globe .note.warn { border-color: rgba(245, 166, 35, .35); }
.ow-globe .note .ic { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .14); }
.ow-globe .note .ic svg { width: 14px; height: 14px; }
.ow-globe .note .ic.good { background: rgba(34, 192, 122, .12); border-color: rgba(34, 192, 122, .28); color: #22c07a; }
.ow-globe .note .ic.warn { background: rgba(245, 166, 35, .13); border-color: rgba(245, 166, 35, .32); color: #f5a623; }
.ow-globe .note b { font-weight: 500; display: block; overflow-wrap: anywhere; text-transform: none; letter-spacing: 0; }
.ow-globe .note small { display: block; text-transform: none; letter-spacing: 0; color: rgba(245, 245, 247, .5); font-size: 11px; overflow-wrap: anywhere; }
.ow-tip {
  position: absolute; z-index: 3; pointer-events: none; transform: translate(-50%, -100%);
  background: #151517; color: #f5f5f7; border: 1px solid rgba(255, 255, 255, .14); border-radius: 10px;
  padding: 9px 12px; font-size: 12px; line-height: 1.5; min-width: 200px; box-shadow: 0 12px 30px rgba(0, 0, 0, .55);
}
.ow-tip b { display: block; font-size: 13px; }
.ow-tip span { display: block; color: rgba(245, 245, 247, .66); }
.ow-tip span.w { color: #f5a623; }
.ow-tip em { display: block; font-style: normal; color: rgba(245, 245, 247, .5); font-size: 11px; margin-top: 4px; }
.ow-side { padding: 16px 16px 14px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ow-total { display: flex; align-items: baseline; gap: 8px; padding: 0 8px 10px; }
.ow-total b { font-size: 30px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.ow-total span { color: var(--text-muted); }
.ow-stage {
  display: grid; grid-template-columns: 8px minmax(0, 1fr) auto auto; align-items: center; column-gap: 10px;
  padding: 7px 8px; border-radius: 10px; color: var(--text); transition: background var(--fast);
}
.ow-stage:hover { background: var(--surface-1); }
.ow-stage .d { width: 8px; height: 8px; border-radius: 50%; }
.ow-stage .l { font-size: 13px; min-width: 0; }
/* Wrapped rather than cut: what the carrier said is the one line on this
   card somebody reads to the end, and half a sentence is not an answer. */
.ow-stage small { display: block; color: var(--text-dim); font-size: 11px; overflow-wrap: anywhere; }
.ow-stage .badge { grid-column: 3; }
.ow-stage .n { grid-column: 4; font-weight: 600; font-variant-numeric: tabular-nums; }
.ow-stage .n.is-none { color: var(--text-dim); font-weight: 400; }
.ow-stage .bar { grid-column: 2 / 5; height: 3px; margin-top: 4px; border-radius: 2px; background: var(--surface-1); overflow: hidden; }
.ow-stage .bar i { display: block; height: 100%; border-radius: 2px; opacity: .6; }
.ow-stage.t-warn .d, .ow-stage.t-warn .bar i { background: var(--warn); }
.ow-stage.t-info .d, .ow-stage.t-info .bar i { background: var(--info); }
.ow-stage.t-plain .d, .ow-stage.t-plain .bar i { background: var(--text); }
.ow-stage.t-good .d, .ow-stage.t-good .bar i { background: var(--good); }
.ow-stage.t-bad .d, .ow-stage.t-bad .bar i { background: var(--warn); opacity: 1; }
.ow-stage.t-bad .n:not(.is-none) { color: var(--warn-ink); }
.ow-foot { display: flex; justify-content: space-between; gap: 12px; padding: 8px 8px 0; font-size: 13px; color: var(--text-muted); }
.ow-foot b { color: var(--text); font-variant-numeric: tabular-nums; }
.ow-side .ow-foot:first-of-type { margin-top: auto; border-top: 1px solid var(--border); padding-top: 10px; }
/* The opening, once a session: a box is packed in the middle of the globe and
   closes into the red mark that is the warehouse. Drawn in the page on the
   tokens the rest of the card uses, and gone by the time the camera moves. */
.ow-pack { position: absolute; left: 50%; top: 50%; width: 124px; height: 124px; margin: -62px 0 0 -62px; z-index: 3; pointer-events: none; }
.ow-pack .pk-body { position: absolute; inset: 0; transform-origin: 50% 70%; animation: pkBody 1.7s linear forwards; }
.ow-pack svg { width: 100%; height: 100%; overflow: visible; display: block; }
.ow-pack .pk-top { fill: #ffffff; }
.ow-pack .pk-left { fill: #e4e4e8; }
.ow-pack .pk-right { fill: #c6c6cc; }
.ow-pack .pk-edge { fill: none; stroke: rgba(0, 0, 0, .12); stroke-width: .8; stroke-linejoin: round; }
.ow-pack .pk-tape-top { fill: #e2402f; }
.ow-pack .pk-tape-side { fill: #b3301f; }
.ow-pack .pk-sheen { fill: none; stroke: rgba(255, 255, 255, .32); stroke-width: .9; stroke-linecap: round; }
.ow-pack .pk-sheen-side { stroke: rgba(255, 255, 255, .16); }
.ow-pack .pk-reveal { fill: none; stroke: #fff; stroke-width: 16; stroke-dasharray: 1; stroke-dashoffset: 1; }
.ow-pack .pk-reveal-top { animation: pkTapeTop 1.7s linear forwards; }
.ow-pack .pk-reveal-side { animation: pkTapeSide 1.7s linear forwards; }
.ow-pack .pk-spark { transform-origin: 50px 45px; fill: none; stroke: #ffffff; stroke-width: 2.2; stroke-linecap: round; opacity: 0; animation: pkSpark 1.7s linear forwards; }
.ow-pack .pk-shadow { position: absolute; left: 22%; right: 22%; bottom: 8%; height: 10%; border-radius: 50%; background: radial-gradient(closest-side, rgba(226, 64, 47, .35), transparent); opacity: 0; animation: pkShadow 1.7s linear forwards; }
.ow-pack .pk-dot { position: absolute; left: 50%; top: 50%; width: 8.4px; height: 8.4px; margin: -4.2px 0 0 -4.2px; border-radius: 50%; background: #e2402f; opacity: 0; transform: scale(3); animation: pkDot 1.7s ease forwards; }
@keyframes pkBody {
  0% { opacity: 0; transform: translateY(-46px) scale(.72); animation-timing-function: cubic-bezier(.3, 0, .6, 1); }
  12% { opacity: 1; transform: translateY(0) scale(1.07, .93); animation-timing-function: ease-out; }
  19% { transform: translateY(-5px) scale(.98, 1.02); animation-timing-function: ease-in; }
  25% { transform: translateY(0) scale(1); }
  58% { transform: scale(1); animation-timing-function: ease-in; }
  62% { transform: scale(1.06, .9); animation-timing-function: ease-out; }
  68% { transform: scale(1); }
  76% { opacity: 1; transform: scale(1) rotate(0deg); animation-timing-function: cubic-bezier(.6, 0, .9, .4); }
  90% { opacity: 0; transform: scale(.07) rotate(18deg); }
  100% { opacity: 0; transform: scale(.07) rotate(18deg); }
}
@keyframes pkTapeTop { 0%, 30% { stroke-dashoffset: 1; } 45%, 100% { stroke-dashoffset: 0; } }
@keyframes pkTapeSide { 0%, 45% { stroke-dashoffset: 1; } 56%, 100% { stroke-dashoffset: 0; } }
@keyframes pkSpark { 0%, 60% { opacity: 0; transform: scale(.85); } 64% { opacity: 1; } 74%, 100% { opacity: 0; transform: scale(1.12); } }
@keyframes pkShadow { 0%, 8% { opacity: 0; } 14%, 74% { opacity: 1; } 88%, 100% { opacity: 0; } }
@keyframes pkDot { 0%, 82% { opacity: 0; transform: scale(3); } 92% { opacity: 1; transform: scale(1); } 100% { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .ow-pack { display: none; } }

/* Nothing on the way yet. The globe says it is an example in a corner rather
   than in a sentence over the picture, and the stages sit dimmed so what the
   card will count is readable before there is anything to count. */
.ow-flag {
  position: absolute; left: 14px; top: 14px; z-index: 3; display: none;
  font-size: 10.5px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(245, 245, 247, .7); padding: 4px 9px; border-radius: 999px;
  border: 1px dashed rgba(245, 245, 247, .28); background: rgba(0, 0, 0, .5);
}
.onway.is-preview .ow-flag { display: inline-block; }
.ow-globe .note.example { border-style: dashed; }
.ow-intro { padding: 0 8px 12px; }
.ow-intro b { display: block; font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.ow-intro p { margin: 0; color: var(--text-muted); font-size: 13px; line-height: 1.5; max-width: 42ch; }
.ow-stage.is-ghost { opacity: .55; transition: opacity var(--fast), background var(--fast); }
.ow-stage.is-ghost:hover { opacity: 1; }
.ow-stage.is-ghost .bar i { width: 0; }
.ow-cta .hint { font-size: 12px; color: var(--text-dim); margin: 0; }
.ow-cta { margin-top: auto; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 8px 0; border-top: 1px solid var(--border); }
@media (max-width: 900px) {
  .onway .ow-body { grid-template-columns: 1fr; }
  .ow-globe { border-right: 0; border-bottom: 1px solid var(--border); max-height: none; border-radius: 0; }
}
@media (max-width: 720px) {
  .ow-globe .chip { max-width: min(230px, calc(100% - 12px)); }
  .ow-globe .note { font-size: 11.5px; grid-template-columns: 24px minmax(0, 1fr); padding: 7px 9px; }
  .ow-globe .note .ic { width: 24px; height: 24px; }
  .ow-globe .chip.dyn:nth-of-type(n+4) { display: none; }
}

/* A row that arrived since the last read, on the console's balance log. It
   lights once where it landed rather than moving to the top: a row that jumps
   is a row somebody loses their place in. Once and then still, on the rule in
   design.md, because a list that keeps flashing is decoration and this one is
   watched all day. */
.just-in > td { animation: just-in 2.6s ease-out both; }
@keyframes just-in {
  0% { background-color: var(--accent-soft); }
  60% { background-color: var(--accent-soft); }
  100% { background-color: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .just-in > td { animation: none; background-color: var(--surface-1); }
}

/* --- buttons ------------------------------------------------------------- */
.btn {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  padding: 0 16px;
  height: 34px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  border: 1px solid transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  white-space: nowrap;
  transition: transform var(--fast), background var(--fast), border-color var(--fast), color var(--fast), opacity var(--fast);
}
.btn:active { transform: scale(.97); }
.btn svg { width: 14px; height: 14px; }
.btn.accent { background: var(--accent-fill); color: var(--accent-ink); }
.btn.accent:hover { background: color-mix(in srgb, var(--accent) 88%, white); }
.btn.solid { background: var(--text); color: var(--canvas); }
.btn.ghost { background: var(--card); color: var(--text); border-color: var(--border-strong); }
.btn.ghost:hover { background: var(--card-hover); border-color: var(--text-dim); }
.btn.quiet { background: transparent; color: var(--text-muted); }
.btn.quiet:hover { background: var(--surface-1); color: var(--text); }
.btn.danger { background: var(--bad-soft); color: var(--bad); border-color: transparent; }
.btn.sm { height: 28px; font-size: 12px; padding: 0 12px; }
.btn.lg { height: 42px; font-size: 14px; padding: 0 22px; }
.btn.block { width: 100%; }
.btn:disabled, .btn.is-disabled { opacity: .42; cursor: not-allowed; transform: none; }
.btn.is-loading { color: transparent; position: relative; pointer-events: none; }
.btn.is-loading::after {
  content: "";
  position: absolute;
  /* Centred on the button rather than left at its static position, which is
     where the label would have been and is not the middle of anything. */
  top: 50%; left: 50%;
  margin: -7px 0 0 -7px;
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: var(--accent-ink);
  animation: spin 600ms linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::after { animation-duration: 2s; }
}

/* What the sign-in is doing while it is doing it. Centred under the button
   because that is where the eye already is, and it takes no room when there
   is nothing to say. */
.loginstep { text-align: center; margin-top: 10px; }

.action-btn {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .04em;
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--card);
  color: var(--text);
  cursor: pointer;
  transition: background var(--fast), border-color var(--fast);
}
.action-btn:hover { background: var(--surface-2); }
.action-btn.primary { background: var(--accent-fill); color: var(--accent-ink); border-color: transparent; font-weight: 600; }
.action-btn.done { background: transparent; color: var(--text-dim); border-color: var(--border); cursor: default; }

/* segmented control (1D / 7D / 30D / MTD) */
.segmented {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.segmented button {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 4px 10px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  border-radius: 6px;
  cursor: pointer;
  transition: background var(--fast), color var(--fast);
}
.segmented button:hover { color: var(--text-muted); }
.segmented button[aria-selected="true"] { background: var(--accent-fill); color: var(--accent-ink); font-weight: 600; }

/* tabs */
.tabbar { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; margin-bottom: 16px; }
.tabb {
  padding: 6px 13px;
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid transparent;
  color: var(--text-muted);
  background: transparent;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: background var(--fast), color var(--fast);
}
.tabb:hover { background: var(--surface-1); color: var(--text); }
.tabb[aria-selected="true"] { background: var(--surface-2); color: var(--text); font-weight: 600; border-color: var(--border); }
.tcount {
  font-family: var(--font-mono);
  font-size: 9px;
  background: var(--surface-2);
  color: var(--text-dim);
  padding: 2px 6px;
  border-radius: var(--radius-pill);
}
.tabb[aria-selected="true"] .tcount { background: var(--accent-fill); color: var(--accent-ink); }
.tab-actions { margin-left: auto; display: flex; gap: 8px; }

/* --- forms --------------------------------------------------------------- */
label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  display: block;
  margin-bottom: 6px;
}
.field { margin-bottom: 14px; }
/* Two dates side by side, because a period is one thing with two ends and
   stacking them reads as two questions. One column on a phone, where two date
   inputs beside each other are two inputs nobody can tap. */
.pickrow { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* --- a product opened, when nothing about it is per variant --------------
   The names, because that is the only thing that differs before there is a
   price on them. Chips rather than rows: fifty four of these read as one
   product in fifty four sizes, and fifty four table rows read as a
   catalogue. */
.varsum { padding: 12px 2px 14px; }
.varsum .ps { margin: 0 0 10px; }
/* The head of a sourcing request: the picture, the name beside it, and the
   sizes as pills under the pair, which is how every list in this app draws a
   product. It was a heading with nothing but a title on it over a `<details>`
   of checkbox tiles three to a row, so which sizes were even in the request
   was behind a press. */
.srchead { display: flex; align-items: center; gap: 14px; margin: 0 0 12px; }
.srchead h3 { margin: 0; font-size: 16px; font-weight: 600; }
.srchead p { margin: 3px 0 0; }
/* The sizes on a request are one line that scrolls, not a block that wraps.
   Fifteen of them wrapped to two rows and pushed the category and the
   countries off the screen, and this strip is a reading rather than a thing
   to work through: what somebody does here is glance at it. The same shape
   the product drawer's own size strip has had since it was written. */
.varchips.is-flat {
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  margin: 0 0 14px; padding-bottom: 2px;
}
.varchips.is-flat::-webkit-scrollbar { display: none; }
.varchips.is-flat .varchip {
  flex: none; padding: 3px 8px; font-size: 11.5px; cursor: default;
}
.varchips.is-flat .varchip:hover { color: var(--text-dim); border-color: var(--border); }

.varchips.is-pick { margin: 0 0 18px; }
.varchips.is-pick.is-rest { margin-top: -10px; }
/* The one that opens the rest rather than being one of them. */
.varpick.is-more input:checked + span,
.varpick.is-more span { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .06em; }
/* Everything else to choose from, behind one press rather than behind a fold
   per category: eleven shut rows read as a filing cabinet. The press itself
   is `.varpick.is-more`, the same pill the countries use, because it is the
   same question asked of a different set. */
.morewrap { margin-bottom: 10px; }
.morecat { margin: 10px 0 6px; }
/* A pill that is also a tick. The box is there for the keyboard and for
   anything reading the form; what anybody sees is the pill filling in. */
.varpick { display: inline-flex; position: relative; }
.varpick input {
  position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none;
}
.varpick span {
  display: inline-block; font-size: 12px; padding: 4px 9px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text-dim); background: var(--surface-1); cursor: pointer;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}
.varpick span:hover { color: var(--text); border-color: var(--border-strong); }
.varpick input:checked + span {
  color: var(--accent-ink-on-soft); border-color: var(--accent-line); background: var(--accent-soft);
}
.varpick input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A field straight after a fold. A summary is one line with nothing under it,
   so the next label landed on top of it and read as part of the fold. */
.sheetbody details + .field { margin-top: 16px; }

.varchips { display: flex; flex-wrap: wrap; gap: 6px; }
.varchip {
  display: inline-block;
  font-size: 12px;
  padding: 4px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  background: var(--surface-1);
  text-decoration: none;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}
.varchip:hover { color: var(--text); border-color: var(--border-strong); }
.varchip.is-gone { opacity: .55; text-decoration: line-through; }
/* The one that is not a variant. It says what it opens rather than wearing
   the same face as the things it opens. */
.varchip.is-more { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .06em; }
/* The rest of the names are held in one element so a single press can reveal
   them. Shown, that element has to stop being a box: as a flex item its
   children are its content and they came out one per line, in a column down
   the middle of a row that wraps. `display: contents` makes them chips of the
   row they belong to. Guarded on [hidden], which has to keep winning. */
.varchips [data-morevars]:not([hidden]) { display: contents; }
tr.gsummary.is-shut { display: none; }
@media (max-width: 560px) { .pickrow { grid-template-columns: 1fr; } }
.field .hint { font-size: 11.5px; color: var(--text-dim); margin-top: 6px; }

input, select, textarea {
  width: 100%;
  font-family: var(--font-display);
  font-size: 13.5px;
  height: 36px;
  padding: 0 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-1);
  color: var(--text);
  transition: border-color var(--fast), background var(--fast);
}
textarea { height: auto; min-height: 76px; padding: 10px 11px; resize: vertical; line-height: 1.5; }
input::placeholder, textarea::placeholder { color: var(--text-dim); }
input:hover, select:hover, textarea:hover { border-color: var(--text-dim); }
input:focus-visible, select:focus-visible, textarea:focus-visible { border-color: var(--border-focus); background: var(--surface-2); }
input[type="checkbox"], input[type="radio"] { width: auto; height: auto; accent-color: var(--accent); cursor: pointer; }
input[type="date"] { color-scheme: dark; }
select {
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 14px;
  padding-right: 30px;
}
.filterbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 14px; }
.filterbar .search { flex: 1; min-width: 200px; }
.filterbar input[type="date"], .filterbar select { width: auto; min-width: 150px; }

/* --- tables -------------------------------------------------------------- */
.tablewrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--text-dim);
  font-weight: 400;
  padding: 11px 14px;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
/* Seven columns of money in the side of a drawer, where the panel beside it
   takes half the width. The ordinary fourteen is four and a half columns of
   padding across a table whose widest cell is six characters, and what it
   bought was the last column sitting behind the edge of the card. */
.pricegrid th, .pricegrid td { padding-left: 10px; padding-right: 10px; }
/* A column head that carries the fact every one of its rows would otherwise
   repeat. The handling is the product's, so it is the same on every row of a
   price table: saying it in front of each is one fact printed six times in the
   column with the least room. */
.pricegrid th em.sub {
  display: block;
  font-style: normal;
  font-weight: 400;
  font-size: 9.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--dim);
  margin-top: 1px;
  white-space: nowrap;
}
/* An amount is one thing, so it never breaks between the symbol and the
   number: "EUR" alone on a line over "10.53" is two readings of one figure,
   and it grows the row besides. The same for "2d + 5d". */
.pricegrid td.num { white-space: nowrap; }

/* A head of two rows: the unit is said once over the quantity columns and the
   numbers sit under it, because four heads each reading "2 sale units" spend a
   hundred pixels apiece on the same two words. Both rows stick, so the second
   is offset by the height of the first rather than landing on top of it. */
.pricegrid.grouped thead tr:first-child th { padding-bottom: 3px; }
.pricegrid.grouped thead tr:first-child th.colgroup {
  text-align: center;
  border-bottom: 0;
  color: var(--text-dim);
}
.pricegrid.grouped thead tr + tr th { top: 26px; padding-top: 0; }

thead th.sortable { cursor: pointer; user-select: none; }
thead th.sortable:hover { color: var(--text-muted); }
thead th[aria-sort] { color: var(--text); }
thead th .arrow { opacity: .5; margin-left: 4px; }
td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  vertical-align: middle;
}
td.num { text-align: right; }
th.num { text-align: right; }
tbody tr:last-child td { border-bottom: none; }

/* --- a table on a phone is a list of cards -------------------------------- */
/* A table needs width and a phone has none, so what was happening was a
   sideways swipe through columns: order, customer and item visible, and the
   status, the amount and the button you actually came for off the right-hand
   edge. Nobody swipes a queue.

   So each row becomes a card, each cell a line with its column name in front,
   and nothing is off screen. The markup is the same table: one place decides
   what a row says, and this only decides how it stacks. */
/* Drawn only on a phone, and only by a table that has one. */
.phonecell { display: none; }

@media (max-width: 720px) {
  .tablewrap { overflow-x: visible; }
  /* A table that says what its rows look like on a phone shows that and
     nothing else. Selection sits in the row rather than over it: floated top
     right it landed on top of the amount and took the last two digits off
     the price, which on a screen about money is the wrong thing to cover. */
  .tablewrap tbody tr:has(.phonecell) > td:not(.phonecell):not(.cbcell) { display: none; }
  .tablewrap tbody tr:has(.phonecell) { display: flex; align-items: center; padding: 0; }
  .tablewrap tbody tr:has(.phonecell) > td.cbcell {
    position: static; flex: none; width: 38px; padding: 0 0 0 12px;
  }
  .tablewrap tbody td.phonecell { display: block; flex: 1; min-width: 0; padding: 0; }
  /* A column that says it is not for a phone. The stack draws one labelled
     line per cell, so this is the difference between a list you scan and a
     list you read. */
  .tablewrap td.no-phone, .tablewrap th.no-phone { display: none; }
  .tablewrap table, .tablewrap tbody, .tablewrap tr, .tablewrap td { display: block; width: auto; }
  /* The header row has nothing to head once the columns are gone. */
  .tablewrap thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

  .tablewrap tbody tr {
    position: relative;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card); margin-bottom: 10px; padding: 4px 2px;
  }
  .tablewrap tbody tr.clickable:hover td { background: none; }
  .tablewrap tbody tr.is-open { border-color: var(--accent-line); }

  .tablewrap tbody td {
    display: flex; align-items: baseline; gap: 12px;
    border: 0; padding: 7px 14px; text-align: left;
    min-height: 0;
  }
  .tablewrap tbody td.num { justify-content: flex-start; }
  /* A value that is more than a word goes under its label rather than pushing
     the page sideways. The ledger's top-up row carries a badge, a chip naming
     the file and a button, all in one cell, and on a phone that row made the
     whole balance screen scroll left to right: the one screen where a number
     half off the edge is worst. */
  .tablewrap tbody td { flex-wrap: wrap; }
  .tablewrap tbody td > * { min-width: 0; max-width: 100%; }
  .tablewrap tbody td::before {
    content: attr(data-label);
    flex: none; width: 8.5em;
    font-family: var(--font-accent); font-size: 9.5px; letter-spacing: .09em;
    text-transform: uppercase; color: var(--text-dim);
  }
  /* A cell with no label is a cell that speaks for itself, usually the one
     holding the button. `attr()` on a missing attribute is the empty string
     rather than nothing, so a table written by hand rather than through
     `U.table` got an empty label box on every cell: eight and a half ems of
     indent in front of each line, which is what turned the price table in the
     product drawer into a column of amounts pushed to the right with nothing
     saying what any of them was. */
  .tablewrap tbody td[data-label=""]::before,
  .tablewrap tbody td:not([data-label])::before { display: none; }

  /* The first cell that carries anything is the one somebody scans for, so it
     reads as a heading rather than as another labelled line. Not
     :first-of-type: with selection on, the first td is the checkbox, and the
     heading treatment landed on that instead of on the order number. */
  .tablewrap tbody tr > td:not(.cbcell):first-child,
  .tablewrap tbody tr > td.cbcell + td {
    font-size: 15px; font-weight: 600; color: var(--text);
    padding: 12px 14px 4px;
  }
  .tablewrap tbody tr:not(.grouprow) > td:not(.cbcell):first-child::before,
  .tablewrap tbody tr:not(.grouprow) > td.cbcell + td::before { display: none; }

  /* Selection moves out of the flow, top right, where it does not push the
     first line off its own left edge. */
  .tablewrap tbody td.cbcell {
    position: absolute; top: 8px; right: 8px; width: auto; padding: 0;
  }
  .tablewrap tbody td.cbcell::before { display: none; }
  /* Out of the flow is not out of the way: the tick sits over the top right
     corner, which is where the first line ends, so a product whose name and
     shop fill the row ran underneath it. The line that shares that corner
     keeps clear of it, and only where there is a tick to keep clear of. */
  .tablewrap tbody tr:has(> td.cbcell) > td.cbcell + td { padding-right: 40px; }

  .tablewrap tbody tr.grouprow {
    border: 0; background: none; margin: 14px 0 4px; padding: 0;
  }
  .tablewrap tbody tr.grouprow > td { padding: 0 2px; }
  .tablewrap tbody tr.grouprow:has(> td.cbcell) > td.cbcell + td { padding-right: 34px; }
  .tablewrap tbody tr.grouprow > td::before { display: none; }
}
tbody tr.clickable { cursor: pointer; transition: background var(--fast); }
tbody tr.clickable:hover td { background: var(--surface-1); color: var(--text); }
/* Where the keyboard is standing. A row is not an element a browser draws a
   ring around usefully, so the mark is the row itself: the same lift as a
   hover plus a bar down the leading edge, which is what the sidebar already
   uses to say "here". Only for the keyboard, so a mouse never leaves one row
   looking picked out after a click. */
tbody tr[data-key]:focus { outline: none; }
tbody tr[data-key]:focus-visible td { background: var(--surface-1); color: var(--text); }
tbody tr[data-key]:focus-visible td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
tbody tr.is-open td { background: var(--surface-1); }
tr.detail td { background: var(--surface-1); padding: 0; }

.mono { font-family: var(--font-mono); font-size: 11.5px; }
.strong { color: var(--text); font-weight: 600; }
.thumb {
  width: 34px; height: 34px;
  border-radius: 8px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: 8.5px;
  color: rgba(255, 255, 255, .8);
  font-weight: 600;
}
/* the drawn stand-in for a product photo, and the frame it sits in */
.pthumb {
  flex: none;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: linear-gradient(180deg, var(--card-hover), var(--card));
  overflow: hidden;
}
.pthumb svg {
  width: 27px;
  height: 27px;
  fill: none;
  stroke: var(--tint, var(--text-muted));
  stroke-width: 1.35;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pthumb img { width: 100%; height: 100%; object-fit: cover; }
.pthumb.is-lg { width: 64px; height: 64px; border-radius: 13px; }
.pthumb.is-lg svg { width: 46px; height: 46px; stroke-width: 1.2; }
html[data-density="compact"] .prodcell .pthumb:not(.is-lg) { width: 32px; height: 32px; border-radius: 8px; }
html[data-density="compact"] .prodcell .pthumb:not(.is-lg) svg { width: 23px; height: 23px; }

.prodcell { display: flex; align-items: center; gap: 11px; }
.prodcell .pt { color: var(--text); font-weight: 500; }
/* The sub line, wherever it is. It was only ever styled inside `.prodcell`,
   `.varcell` and two more, so the same class did nothing at all in an ordinary
   cell: on the console's tracking list the hour under a carrier's sentence was
   drawn at the sentence's own size and read as a third line of it, and the
   costs screen had already worked around this with a rule of its own. A class
   that means one thing in four places and nothing in the rest is a class
   nobody can use. */
.ps { font-size: 11px; color: var(--text-dim); }
.prodcell .ps { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); margin-top: 2px; }
/* the same cell, sized for the top of a form rather than a row in a table */
.prodcell.is-lg { gap: 14px; margin-bottom: 12px; }
.prodhead { display: flex; align-items: center; gap: 15px; }
.prodcell.is-lg .pt { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.prodcell.is-lg .ps { font-size: 10.5px; margin-top: 4px; }

.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.pager .meta { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim); }
.pager .pages { display: flex; gap: 4px; }
.pager .pg {
  min-width: 30px; height: 30px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-muted);
  font-size: 12.5px;
  cursor: pointer;
  transition: background var(--fast), color var(--fast);
}
.pager .pg:hover { background: var(--surface-1); color: var(--text); }
.pager .pg[aria-current="page"] { background: var(--accent-fill); color: var(--accent-ink); border-color: transparent; font-weight: 600; }

/* --- activity feed ------------------------------------------------------- */
.feed { display: flex; flex-direction: column; }
.feedrow {
  display: grid;
  grid-template-columns: 62px 14px 1fr;
  gap: 10px;
  align-items: start;
  padding: 11px 18px;
  border-bottom: 1px solid var(--border);
  transition: background var(--fast);
}
.feedrow:hover { background: var(--surface-1); }
.feedrow:last-child { border-bottom: none; }
.feedrow .ftime { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim); padding-top: 2px; }
.feedrow .fdot { padding-top: 4px; min-width: 20px; }
.feedrow .fbody { min-width: 0; }
.feedrow .fname { font-family: var(--font-mono); font-size: 11.5px; color: var(--text); }
.feedrow .fmsg { font-size: 12.5px; color: var(--text-muted); }
.feedrow .fmeta { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); margin-top: 3px; }

/* sparkline bars */
.spark { display: flex; align-items: flex-end; gap: 2px; height: 34px; }
.spark i {
  flex: 1;
  min-width: 2px;
  border-radius: 1px;
  background: var(--good);
  opacity: .55;
  transition: opacity var(--fast);
}
.spark i.hi { opacity: 1; }
.spark.accent i { background: var(--accent); }
.card:hover .spark i { opacity: .8; }
.card:hover .spark i.hi { opacity: 1; }

/* bar chart */
.bars { display: flex; align-items: flex-end; gap: 8px; height: 130px; }
.barcol { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; gap: 8px; align-items: center; }
.barcol .bar {
  width: 100%;
  border-radius: 5px 5px 2px 2px;
  background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 45%, transparent));
  transition: filter var(--fast);
}
.barcol:hover .bar { filter: brightness(1.25); }
.barcol .blabel { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-dim); }

/* progress */
.prog { height: 5px; border-radius: 4px; background: var(--surface-2); overflow: hidden; }
.prog i { display: block; height: 100%; background: var(--accent); border-radius: 4px; transition: width var(--med); }
.prog i.good { background: var(--good); }
.prog i.bad { background: var(--bad); }

/* rail (key/value column) */
.rail { border-left: 1px solid var(--border); padding: 4px 20px; display: flex; flex-direction: column; justify-content: center; }
.rrow { padding: 11px 0; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.rrow:last-child { border-bottom: none; }
.rrow .rl { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim); }
.rrow .rv { font-size: 16px; font-weight: 700; letter-spacing: -.02em; }
.linerow { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 12.5px; color: var(--text-muted); }
.linerow:last-child { border-bottom: none; }
.linerow b { color: var(--text); font-weight: 600; }

/* --- Shopify connection states ------------------------------------------ */
.connbar {
  --bt: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
  border-radius: 17px;
  border: 1px solid color-mix(in srgb, var(--bt) 17%, transparent);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--bt) 8%, transparent),
      color-mix(in srgb, var(--bt) 2%, transparent)),
    color-mix(in srgb, var(--card) 62%, transparent);
  backdrop-filter: blur(24px) saturate(190%);
  -webkit-backdrop-filter: blur(24px) saturate(190%);
  box-shadow:
    inset 0 1px 0 var(--glass-lip),
    0 1px 2px rgba(0, 0, 0, .22),
    0 10px 26px -16px rgba(0, 0, 0, .7);
}
.connbar .cicon {
  width: 30px; height: 30px;
  border-radius: 50%;
  display: grid; place-items: center;
  flex-shrink: 0;
  color: var(--bt);
  background: color-mix(in srgb, var(--bt) 13%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bt) 20%, transparent);
}
.connbar .ctext { min-width: 0; flex: 1; }
.connbar .ct { font-size: 13px; font-weight: 600; letter-spacing: -.01em; }
.connbar .cs { font-size: 12.5px; color: var(--text-muted); margin-top: 1px; line-height: 1.45; }
.connbar .cact { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

.connbar.state-connected { --bt: var(--good); }
.connbar.state-connected .cicon { color: var(--shopify); }
.connbar.state-syncing { --bt: var(--info); }
.connbar.state-syncing .cicon svg { animation: spin 900ms linear infinite; }
.connbar.state-disconnected { --bt: var(--warn); }
.connbar.state-error, .connbar.state-reauth { --bt: var(--bad); }
.connbar.state-scopes { --bt: var(--accent); }

/* scope list, shown before the merchant authorises */
.scopelist { display: flex; flex-direction: column; gap: 1px; }
.scoperow {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.scoperow:last-child { border-bottom: none; }
.scoperow .sk { font-family: var(--font-mono); font-size: 11.5px; color: var(--accent); flex-shrink: 0; min-width: 148px; }
.scoperow .sw { font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
.scoperow .sw b { color: var(--text); font-weight: 600; }

/* --- empty / skeleton / error states ------------------------------------ */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 8px;
  padding: 54px 24px;
}
.empty .eicon {
  width: 42px; height: 42px;
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--text-dim);
  display: grid; place-items: center;
  margin-bottom: 6px;
}
.empty h3 { margin: 0; font-size: 15px; font-weight: 600; }
.empty p { margin: 0; font-size: 13px; color: var(--text-muted); max-width: 380px; line-height: 1.55; }
.empty .eact { margin-top: 12px; display: flex; gap: 8px; }

/* The same state inside a card in a column, where the page version spends
   more room on its own padding than on what it says. */
.empty.is-sm { padding: 22px 16px; gap: 6px; }
.empty.is-sm .eicon { width: 32px; height: 32px; border-radius: 10px; margin-bottom: 2px; }
.empty.is-sm h3 { font-size: 14px; }
.empty.is-sm p { font-size: 12.5px; }
.empty.is-sm .eact { margin-top: 8px; }

.skel {
  border-radius: 6px;
  background: linear-gradient(90deg, var(--surface-1) 25%, var(--surface-2) 37%, var(--surface-1) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
.skel.line { height: 11px; margin-bottom: 8px; }
.skel.num { height: 32px; width: 60%; }
.skel.w-40 { width: 40%; } .skel.w-60 { width: 60%; } .skel.w-80 { width: 80%; }

/* --- overlays: every modal and drawer in the app ----------------------- */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, .62);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: none;
  z-index: 100;
  padding: 20px;
}
.overlay.open { display: flex; align-items: center; justify-content: center; animation: fade var(--fast) both; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }


/* --- modal --------------------------------------------------------------- */
.modal {
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  max-width: 560px;
  width: 100%;
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-pop);
  animation: rise var(--med) both;
}
.modal .mhead { display: flex; align-items: center; gap: 12px; padding: 18px 22px; border-bottom: 1px solid var(--border); }
.modal .mhead h3 { margin: 0; font-size: 16px; font-weight: 650; letter-spacing: -.01em; }
.modal .mbody { padding: 22px; overflow-y: auto; }
.modal .mfoot { padding: 14px 22px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 9px; }
.mclose { margin-left: auto; }

/* --- the outbox ----------------------------------------------------------
   Work that has finished being typed and has not finished being sent. It sits
   in the corner the toaster is in, above the modal layer on purpose: the whole
   point is that somebody can open the next quote while this one goes, and a
   dock behind the dialog would be invisible at exactly that moment. */
.outbox {
  position: fixed;
  bottom: 22px;
  right: 22px;
  z-index: 210;
  width: 340px;
  max-width: calc(100vw - 32px);
  display: none;
  flex-direction: column;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .55);
  overflow: hidden;
}
.outbox.is-on { display: flex; }
.obhead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 9px 9px 9px 13px;
  border-bottom: 1px solid var(--border);
}
.obheadline { font-size: 12.5px; font-weight: 600; letter-spacing: -.01em; }
.oblist { max-height: min(48vh, 420px); overflow-y: auto; }
.obcard {
  position: relative;
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}
.obcard:last-child { border-bottom: 0; }
/* The one mark, on the right where the eye ends up after reading the row.
   Three states and no reading required: turning while it is out, a tick when
   it landed, a cross when it did not. */
.obmark {
  flex: 0 0 auto; align-self: center;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  color: var(--dim);
}
.obmark.is-good { color: var(--good); }
.obmark.is-bad { color: var(--bad); }
.obnote {
  font-size: 11px; line-height: 1.4; color: var(--dim);
  margin-top: 5px; padding-left: 8px;
  border-left: 2px solid var(--line);
}
.obthumb { flex: 0 0 auto; }
.obthumb .pthumb, .obthumb img { width: 30px; height: 30px; border-radius: 6px; }
.obtext { min-width: 0; flex: 1 1 auto; }
.obtitle { font-size: 12.5px; font-weight: 600; }
.obsub { font-size: 11px; color: var(--dim); }
.obstate { font-size: 11px; color: var(--dim); margin-top: 2px; }
.obcard.is-bad .obstate { color: var(--bad); }
.obacts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 7px; }
/* A band that travels rather than a bar that fills. `fetch` cannot say how
   much of a body has gone, and a bar drawn from nothing is a number somebody
   would believe. */
.obbar { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; overflow: hidden; background: var(--line); }
.obbar span {
  position: absolute; top: 0; bottom: 0; width: 40%;
  background: var(--accent);
  animation: obslide 1.25s ease-in-out infinite;
}
@keyframes obslide {
  0% { left: -40%; } 100% { left: 100%; }
}
/* Amber while it is going, because that is the honest colour for a thing that
   has not finished: green is a claim and red is a verdict, and neither is true
   yet. Dim and still while it is only waiting its turn. */
.obspin {
  display: inline-block; width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--line);
}
.obmark.is-live .obspin {
  border-color: color-mix(in srgb, var(--warn) 28%, transparent);
  border-top-color: var(--warn);
  animation: obspin .8s linear infinite;
}
@keyframes obspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .obbar span { animation: none; left: 0; width: 100%; opacity: .5; }
  .obspin { animation: none; }
}
@media (max-width: 720px) {
  .outbox { left: 10px; right: 10px; bottom: calc(env(safe-area-inset-bottom, 0px) + 10px); width: auto; }
}

/* --- toast --------------------------------------------------------------- */
.toaster {
  position: fixed;
  /* Clear of the outbox when there is one, which sits in the same corner. */
  bottom: calc(22px + var(--outbox-h, 0px));
  right: 22px;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-end;
  pointer-events: none;
}
/* On a phone the bottom right corner is where the browser puts its own
   toolbar and the home indicator, so an answer to something somebody just
   pressed landed under both of them. It goes to the top, full width, clear of
   the notch: the whole job of a toast is being seen without being looked
   for. */
@media (max-width: 720px) {
  .toaster {
    top: calc(env(safe-area-inset-top, 0px) + 10px);
    bottom: auto;
    left: 10px;
    right: 10px;
    align-items: stretch;
  }
  .toast { width: 100%; }
}
/* The notice anatomy carries the mark, the title and the line under it; what
   is a toast's own is that it floats, that it is darker than the page it
   covers, and that it leaves. */
/* Two classes rather than one, and on purpose: `.notice` is defined further
   down this file, so a single `.toast` would lose to it on order alone and the
   toast would take the page's own surface instead of the darker one it needs
   to sit over it. */
.notice.toast {
  padding: 12px 15px;
  background:
    color-mix(in srgb, var(--bt) 7%, transparent),
    color-mix(in srgb, var(--surface-1) 80%, transparent);
  box-shadow: inset 0 1px 0 var(--glass-lip), var(--shadow-pop);
  animation: toastin var(--med) both;
  pointer-events: auto;
  max-width: 380px;
}
.toast.out { animation: toastout 180ms var(--ease) both; }
@keyframes toastin { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes toastout { to { opacity: 0; transform: translateY(6px) scale(.98); } }

/* --- reveal on scroll ----------------------------------------------------
   Content is visible by default. Only when the shell has confirmed a working
   observer (html.reveal-on) does it start hidden, so a dead observer can
   never leave the dashboard blank. */
.reveal { transition: opacity 460ms var(--ease), transform 460ms var(--ease); }
html.reveal-on .reveal:not(.in) { opacity: 0; transform: translateY(14px); }

/* --- mobile -------------------------------------------------------------- */
.mobilebar { display: none; }
@media (max-width: 900px) {
  :root { --gutter: 18px; }
  .sidebar {
    position: fixed;
    z-index: 60;
    transform: translateX(-100%);
    transition: transform var(--med);
    box-shadow: var(--shadow-pop);
  }
  .sidebar.open { transform: none; }
  .mobilebar { display: grid; place-items: center; }
}

/* --- light theme override ----------------------------------------------- */
[data-theme="light"] {
  color-scheme: light;
  --canvas: #fbfbfd;
  --shell: #f6f6f8;
  --surface-1: #f1f1f4;
  --surface-2: #e9e9ee;
  --card: #ffffff;
  --card-hover: #fafafc;
  --grid: rgba(0, 0, 0, .028);

  --text: #1d1d1f;
  /* The same measurement on white: 2.6:1 for the dim tone, which is worse
     than the dark theme because the canvas is brighter. Same answer. */
  --text-muted: rgba(29, 29, 31, .74);
  --text-dim: rgba(29, 29, 31, .64);

  --border: rgba(0, 0, 0, .09);
  --border-strong: rgba(0, 0, 0, .16);

  --accent: #cf3a29;
  /* Already the darker red, and already 4.89:1 under white, so here the two
     are one colour and this theme does not move by a pixel. */
  --accent-fill: var(--accent);
  --accent-ink: #ffffff;
  --accent-soft: rgba(207, 58, 41, .10);
  --accent-line: rgba(207, 58, 41, .32);

  --art-ink: #7d1f16;
  --art-op: .085;

  --glass-lip: rgba(255, 255, 255, .85);
  --glass-sheen: rgba(255, 255, 255, .75);
  --glass-rim: rgba(0, 0, 0, .09);
  --glass-fill: rgba(255, 255, 255, .55);
  --glass-fill-hover: rgba(255, 255, 255, .8);
  --glass-base: rgba(0, 0, 0, .05);
  --glass-shadow: 0 10px 26px -20px rgba(18, 18, 24, .5);

  --viz-cell-0: rgba(0, 0, 0, .04);
  --viz-cell-1: rgba(0, 0, 0, .09);
  --viz-cell-2: rgba(0, 0, 0, .16);
  --viz-cell-3: rgba(0, 0, 0, .26);

  --good: #17915a;
  --good-soft: rgba(23, 145, 90, .11);
  --good-ink: #0f7a4a;
  --warn: #b57500;
  --warn-soft: rgba(181, 117, 0, .12);
  /* Deep enough to clear 4.5:1 on the group rows as well as on a card: a
     chip sits on whatever the row behind it is, and `--surface-2` is darker
     than white. */
  --warn-ink: #7d5100;
  /* Measured on the darkest ground this ink lands on, which is the badge on
     the active menu row: the accent tint sits over that row's own tint, not
     over the card, and #b6301f was 4.43:1 there while reading 6.12:1 on a
     card. The ground decides the ink, not the tint. */
  --accent-ink-on-soft: #ad2d1d;
  /* on white the accent red is dark, so danger goes cooler and deeper to
     stay a different signal */
  --bad: #b0203f;
  --bad-soft: rgba(176, 32, 63, .10);
  --info: #3f6ae0;
  --info-ink: #3257c4;
  --info-soft: rgba(63, 106, 224, .11);

  --shadow-card: 0 1px 2px rgba(0, 0, 0, .04);
  --shadow-pop: 0 1px 2px rgba(0, 0, 0, .06), 0 18px 48px rgba(0, 0, 0, .12);
}
[data-theme="light"] input[type="date"] { color-scheme: light; }
[data-theme="light"] .btn.solid { background: var(--text); color: #fff; }

/* ==========================================================================
   Components used by every screen. Rendered by assets/ui.js.
   ========================================================================== */

/* --- table extras: selection, bulk bar ------------------------------------ */
.cbcell { width: 38px; }
.cbcell input[type="checkbox"] {
  width: 15px; height: 15px;
  accent-color: var(--accent);
  cursor: pointer;
}
/* A variant on its own row, with the product it belongs to on the header
   above it. Only what differs. */
.varcell .pt { font-size: 13px; font-weight: 600; text-transform: none; }
.varcell .ps { font-size: 11px; color: var(--text-dim); }
.varcell.is-gone .pt { color: var(--text-dim); text-decoration: line-through; }

/* The line between one product and the next, and on a collapsible table the
   product itself: shut, this row is the whole of what is on screen for it. */
.grouprow > td {
  padding: 14px 12px 8px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
tbody tr:first-child.grouprow > td { padding-top: 10px; }

/* Shut. Specific enough to hold on a phone, where a row is a card and every
   tr is display: block. */
.tablewrap tbody tr.is-shut { display: none; }

.ghead {
  display: flex; align-items: center; gap: 9px;
  cursor: pointer;
  border-radius: 8px;
  margin: -4px -6px;
  padding: 4px 6px;
  transition: background var(--fast);
}
.ghead:hover { background: var(--surface-1); }
.ghead:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ghead .prodcell { flex: 1 1 auto; min-width: 0; }
/* The one thing on the row that says it can be opened, so it turns rather
   than swapping for a different mark: a shape that moves is read as the same
   shape somewhere else, and a shape that is replaced is read as news. */
.gchev {
  flex: none;
  /* Said rather than taken from the glyph, because a group with nothing to
     open draws an empty one to hold the place: without a width it collapses
     and that one product sits thirteen pixels right of every other. */
  width: 13px; height: 13px;
  display: inline-flex;
  color: var(--text-dim);
  transition: transform var(--fast), color var(--fast);
}
.ghead:hover .gchev { color: var(--text); }
.ghead[aria-expanded="true"] .gchev,
.gchev[aria-expanded="true"] { transform: rotate(90deg); color: var(--text); }
@media (prefers-reduced-motion: reduce) { .gchev { transition: none; } }
/* Where the row opens something of its own, the arrow is a control rather
   than a decoration on one: pressing the product opens the product, pressing
   the arrow beside it unfolds the sizes. It is given a little room around it
   so a finger can find it without hitting the row. */
button.gchev {
  appearance: none; background: none; border: 0; font: inherit;
  box-sizing: content-box;
  padding: 5px; margin: -5px -2px -5px -5px;
  border-radius: 6px;
  cursor: pointer;
  align-items: center; justify-content: center;
}
button.gchev:hover { color: var(--text); background: var(--surface-2); }
button.gchev:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
tr.grouprow.clickable { cursor: pointer; }
tr.grouprow.clickable:hover > td { background: var(--surface-1); }
/* The head is the row now, so it must not draw a second panel of its own
   under the pointer. */
tr.grouprow.clickable .ghead { margin: 0; padding: 0; border-radius: 0; }
tr.grouprow.clickable .ghead:hover { background: none; }

.bulkbar {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 14px;
  margin-bottom: 10px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  border-radius: var(--radius);
  animation: fade var(--fast) both;
}
.bulkbar b { font-size: 12.5px; color: var(--text); margin-right: 4px; }
/* What the buttons beside it can still do today. Pushed to the right of the
   count and left of the actions, quiet, because it is a fact rather than a
   warning. */
.bulknote { font-size: 12px; color: var(--text-muted); margin-right: auto; }
.bulkbar .btn:last-child { margin-left: auto; }
.pager .pg.is-gap { border: none; background: none; color: var(--text-dim); cursor: default; }

/* --- skeleton rows -------------------------------------------------------- */
/* --- a list filling up ---------------------------------------------------
   Rows land from the right, one after another, and then a pulse runs down
   them for as long as the reading takes. A sweep of light across a grey
   block says something is busy; this says rows are arriving, which is what
   is actually happening.
   ------------------------------------------------------------------------ */
.skelwrap { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }

.skelwrap .skelrow {
  animation: skel-land 420ms cubic-bezier(.2, .8, .2, 1) backwards;
  animation-delay: calc(var(--i, 0) * 65ms);
}
@keyframes skel-land {
  from { opacity: 0; transform: translateX(22px); }
  to { opacity: 1; transform: translateX(0); }
}

/* Then a band of light crosses the block, and each row comes up as it
   passes. One thing moving over the rows rather than every row pulsing on
   its own: a list where all of it throbs at once says nothing about order,
   and rows arrive in an order. */
.skelwrap { position: relative; }

.skelwrap::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 42%;
  pointer-events: none;
  background: linear-gradient(180deg,
    transparent 0,
    var(--accent-soft) 45%,
    var(--accent-soft) 55%,
    transparent 100%);
  animation: skel-sweep 1500ms linear infinite;
}
@keyframes skel-sweep {
  from { transform: translateY(-100%); }
  to { transform: translateY(238%); }
}

.skelwrap .skel.line {
  /* The row itself warms as the band reaches it and cools behind it, which is
     what makes the band look like it is touching the rows rather than
     floating over them. The delay is the band's own arrival time at this row,
     handed down from the markup. */
  animation: skel-pass 1500ms ease-in-out infinite;
  animation-delay: var(--sweep, 0ms);
  background: var(--surface-1);
}
@keyframes skel-pass {
  0%, 100% { background-color: var(--surface-1); }
  12% { background-color: var(--surface-2); }
  22% { background-color: var(--accent-soft); }
  40% { background-color: var(--surface-2); }
}

/* Somebody who asked for less movement gets the rows and no sweep. */
@media (prefers-reduced-motion: reduce) {
  .skelwrap::after { display: none; }
  .skelwrap .skel.line { animation: none; }
  .skelwrap .skelrow { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .skelwrap .skelrow, .skelwrap .skel.line { animation: none; opacity: 1; transform: none; }
}
.skelrow {
  display: flex; gap: 18px; align-items: center;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.skelrow:last-child { border-bottom: none; }
.skelrow .skel.line { margin: 0; min-width: 0; }
.empty.is-error .eicon { color: var(--bad); background: var(--bad-soft); }

/* The price on a product row: the country it is quoted in, then the money.
   The flag never carries it alone, so `U.country` puts the code beside it and
   this only has to keep the three on one line and hard against the right. */
.qprice { display: inline-flex; align-items: baseline; gap: 7px; justify-content: flex-end; }
.qprice .ctry { font-size: 11px; color: var(--text-dim); letter-spacing: .04em; }
.qprice b { font-weight: 600; }
/* A product with nothing folded under it keeps the chevron's room so the
   thumbnails line up down the column, and gives up the pointer so it does not
   look like something that opens. */
.ghead.is-leaf { cursor: default; }
.ghead.is-leaf:hover { background: none; }

/* --- a notice ------------------------------------------------------------- */
/* One anatomy for everything that tells somebody something, wherever it
   appears: a small mark in the tone's colour on the first line, the sentence
   that names the thing beside it, and the explanation under that. `--bt` is
   the tone and the whole notice is mixed from it, so there is one variable to
   change and not five rules.

   The mark used to sit in a 30px disc of its own with a gradient fill, an
   inner ring and a glow around it, which is a slab: the loudest thing in the
   notice was a decoration, and the sentence it was about came second. The
   note above this block even claimed the type sat there "without a coloured
   slab behind the icon", which had not been true for some time.

   The glass is calmer than it was for the same reason. A banner carried a
   specular highlight, a 30px blur and three shadows; the toast, which says
   the same kind of thing, has always been plain tinted glass. They meet at
   the quieter of the two. */
.notice {
  --bt: var(--text-muted);
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border-radius: var(--radius);
  border: 1px solid var(--glass-rim);
  background:
    color-mix(in srgb, var(--bt) 7%, transparent),
    color-mix(in srgb, var(--surface-2) 94%, transparent);
  backdrop-filter: blur(20px) saturate(170%);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
  box-shadow: inset 0 1px 0 var(--glass-lip), var(--glass-shadow);
}
/* A box the height of one line rather than a disc: the mark centres itself on
   the title whatever the title wraps to, and it is 16px because at 14 it read
   as a smudge once the slab behind it was gone. */
.nico {
  flex: none;
  width: 16px; height: 19px;
  display: grid; place-items: center;
  color: var(--bt);
}
.ntxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ntxt b { font-size: 13.5px; font-weight: 600; letter-spacing: -.012em; line-height: 1.4; }
.ntxt span { font-size: 12.5px; color: var(--text-muted); line-height: 1.45; }

.banner {
  padding: 13px 15px;
  margin-bottom: 12px;
}
/* a control on glass is glass too, not a solid pill sitting on top of it */
.banner .btn {
  margin-left: auto;
  /* The notice aligns its parts to the first line so the mark sits on the
     title. A button does not belong on that line: it answers the whole
     notice, so it centres against it. */
  align-self: center;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border-color: var(--glass-rim);
  background: var(--glass-fill);
  box-shadow: inset 0 1px 0 var(--glass-lip);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.banner .btn:hover { background: var(--glass-fill-hover); border-color: var(--glass-rim); }
.banner .btn.danger { background: color-mix(in srgb, var(--bad) 16%, transparent); }

/* On a phone the button goes under the sentence rather than beside it. Three
   things on one line at 390px is a column of text about a hundred pixels wide
   with a button taking the rest: the notice that offers to put two orders in
   one parcel broke its own title over three lines to make room for Send them
   in one parcel. It lines up with the text rather than with the mark, which is
   the 16px icon and the 10px gap it stands in. */
@media (max-width: 720px) {
  .notice { flex-wrap: wrap; }
  .notice .ntxt { flex: 1 1 calc(100% - 26px); }
  .banner .btn { margin-left: 26px; align-self: flex-start; }
}

/* --- waiting on somebody else ------------------------------------------- */
/* The same glass as a banner, because it sits in the same place and answers
   the same kind of question. What it is not is a warning: the tone stays the
   muted one while the promise is running, and only takes the amber when it
   has actually been missed. */
/* It said it was the same glass as a banner, so it moves when the banner
   does. Written as the banner rather than beside it, or the next change has
   to be made twice and one of the two gets forgotten. */
.waitcard {
  --bt: var(--text-muted);
  position: relative;
  padding: 13px 15px 12px;
  margin-bottom: 12px;
  border-radius: var(--radius);
  border: 1px solid var(--glass-rim);
  background:
    color-mix(in srgb, var(--bt) 7%, transparent),
    color-mix(in srgb, var(--surface-2) 94%, transparent);
  box-shadow: inset 0 1px 0 var(--glass-lip), var(--glass-shadow);
}
.waitcard.is-over { --bt: var(--warn); }
.wc-head { display: flex; align-items: flex-start; gap: 10px; }
.wc-ico {
  flex: none;
  width: 16px; height: 19px;
  display: grid; place-items: center;
  color: var(--bt);
}
.wc-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.wc-txt b { font-size: 13.5px; font-weight: 600; letter-spacing: -.012em; line-height: 1.4; }
.wc-txt span { font-size: 12.5px; color: var(--text-muted); line-height: 1.45; }
.wc-act {
  margin-left: auto;
  align-self: center;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border-color: var(--glass-rim);
  background: var(--glass-fill);
  box-shadow: inset 0 1px 0 var(--glass-lip);
}
.wc-clock { display: flex; align-items: center; gap: 12px; margin: 12px 0 7px; }
.wc-track {
  position: relative;
  flex: 1 1 auto;
  height: 5px;
  border-radius: 3px;
  overflow: hidden;
  background: color-mix(in srgb, var(--text) 9%, transparent);
}
/* The fill moves on its own every half minute, so it is eased: a bar that
   jumps reads as a redraw rather than as time passing. */
.wc-track i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--bt);
  transition: width .6s cubic-bezier(.22, .61, .36, 1);
}
/* Not the accent. The accent is status and one emphasis point, and a red bar
   filling on the one screen that says nothing is wrong reads as the alarm
   this card exists to stop drawing. It goes amber only when the promise has
   really been missed. */
.waitcard:not(.is-over) .wc-track i { background: color-mix(in srgb, var(--text) 58%, transparent); }
.wc-left {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: .04em;
  white-space: nowrap;
  color: var(--text);
}
.wc-foot {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 11.5px;
  color: var(--text-dim);
}
@media (max-width: 560px) {
  .wc-act { margin-left: 0; }
  .wc-head { flex-wrap: wrap; }
  .wc-foot { flex-direction: column; gap: 2px; }
}

/* The tone, in one place for every notice there is. `info` is deliberately
   not a colour: most of what this app says is neither good news nor a
   problem, and a blue mark on all of it would spend the reader's attention on
   the ordinary case. */
.tone-warn { --bt: var(--warn); }
.tone-bad { --bt: var(--bad); }
.tone-good { --bt: var(--good); }
.tone-accent { --bt: var(--accent); }
.tone-info { --bt: var(--text-muted); }

/* --- SLA marker ----------------------------------------------------------- */
.sla {
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-dim);
  white-space: nowrap;
}
.sla.is-close { background: var(--warn-soft); color: var(--warn-ink); }
.sla.is-late { background: var(--bad-soft); color: var(--bad); }
.sla.is-met { background: var(--good-soft); color: var(--good-ink); }

/* --- modal and drawer ----------------------------------------------------- */
.sheet {
  width: min(560px, calc(100vw - 32px));
  max-height: min(84vh, 860px);
  display: flex; flex-direction: column;
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
  animation: pop var(--med) both;
}
.sheet.is-wide { width: min(880px, calc(100vw - 32px)); }
.sheethead {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--border);
}
.sheethead h3 { margin: 0; font-size: 15px; font-weight: 600; }
.sheethead p { margin: 3px 0 0; font-size: 12.5px; color: var(--text-muted); }
.sheethead .iconbtn { margin-left: auto; }
/* A control about the whole dialog sits beside the cross rather than at the
   top of the body, where it reads as the first thing in the form. */
.sheetacts { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.sheetacts ~ .iconbtn { margin-left: 0; }
/* The head of the price form: the product, and the four facts somebody
   prices on, in one strip beside the picture. All of it used to be in the
   body under a head that carried the title in a sentence and the merchant in
   another, so the first screenful of the one form this floor works all day
   was the same four things written twice. */
.qsheethead { display: flex; align-items: center; gap: 14px; min-width: 0; }
.qsheetwho { min-width: 0; }
.qsheetwho h3 { margin: 0 0 4px; font-size: 15px; font-weight: 600; }
.qsheetmeta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 18px; min-width: 0; }
.qsheetmeta > span { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.qsheetmeta em {
  font-style: normal; font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--text-dim); flex: none;
}
.qsheetmeta b { font-size: 12.5px; font-weight: 600; }
.qsheetmeta b.mono { text-transform: uppercase; letter-spacing: .04em; }
.qsheetmeta .mpill { min-width: 0; }
.qsheetmeta .mpill b { font-size: 12.5px; }
@media (max-width: 720px) {
  .qsheethead { align-items: flex-start; gap: 10px; }
  .qsheetmeta { gap: 4px 12px; }
}

.sheetbody { padding: 16px 18px; overflow-y: auto; }
.sheetbody .ctext { margin: 0 0 4px; font-size: 13px; color: var(--text-muted); line-height: 1.6; }
/* --- the chat channel, on the settings screen ---------------------------- */

/* One offer, because one is built. */
.chanoffer {
  display: flex; align-items: center; gap: 13px;
  padding: 14px; border: 1px solid var(--border); border-radius: var(--radius);
}
.chanoffer > div { flex: 1; min-width: 0; }
.chanoffer b { font-size: 13.5px; display: block; }
.chanoffer p { margin: 2px 0 0; }

.chanmark {
  flex: none; width: 42px; height: 42px; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  border: 1px solid var(--border); color: var(--text-muted);
}
.chanmark.is-on {
  color: var(--good);
  border-color: color-mix(in srgb, var(--good) 45%, var(--border));
  background: var(--good-soft);
}

.chanlive { border-color: color-mix(in srgb, var(--good) 26%, var(--border)); }
.chanhead { display: flex; align-items: center; gap: 13px; }
.chanwho { flex: 1; min-width: 0; }
.chanwho b { font-size: 14.5px; display: block; }
.chanwho .hint { display: block; margin-top: 2px; }

/* The one thing worth seeing without reading: it is alive. Slow, because a
   fast blink is an alarm and this is the opposite of one. */
.livedot {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--good);
}
.livedot i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--good);
  box-shadow: 0 0 0 0 var(--good);
  animation: livepulse 2.4s ease-out infinite;
}
.livedot.is-off { color: var(--text-dim); }
.livedot.is-off i { background: var(--text-dim); animation: none; box-shadow: none; }
@keyframes livepulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 55%, transparent); }
  70% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--good) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .livedot i { animation: none; }
}

/* Two directions, two proofs, and a tick only over the one that happened. */
.chanproof { margin-top: 14px; display: grid; gap: 8px; }
.chanleg {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.chanleg > svg { flex: none; margin-top: 1px; color: var(--good); }
.chanleg b { font-size: 12.5px; display: block; }
.chanleg .hint { display: block; margin-top: 2px; }
.chanleg.is-done { border-color: color-mix(in srgb, var(--good) 34%, var(--border)); }

/* --- connecting a chat, and waiting for it to prove itself --------------- */

/* Two ways in, side by side rather than one under the other: they are the same
   step for two different rooms, not a first choice and a fallback. */
.cchoice { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0 10px; }

.cwait, .cstep {
  display: flex; align-items: flex-start; gap: 11px;
  padding: 12px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 8px;
}
.cwait strong, .cstep strong { font-size: 13px; display: block; }
.cwait p, .cstep p { margin: 3px 0 0; }
.cstep code {
  font-family: var(--font-mono); font-size: 12px;
  padding: 1px 5px; border-radius: 4px;
  background: var(--surface-2); border: 1px solid var(--border);
}
/* Green only once it is true. A tick that is on the screen before the thing
   happened is the whole failure this flow exists to end. */
.cstep.is-done { border-color: color-mix(in srgb, var(--good) 40%, var(--border)); }
.cstep.is-done > svg { color: var(--good); flex: none; margin-top: 1px; }

/* White, always, and never a token: a phone camera reads black on white and
   an inverted code in dark mode is one it hunts for. The frame is what stops
   it looking like a hole cut in the sheet. */
.qrwrap {
  display: flex; justify-content: center;
  padding: 12px; margin: 4px 0 10px;
  background: #fff; border-radius: var(--radius-sm);
}
.qr { display: block; }

.spin {
  flex: none;
  width: 15px; height: 15px; margin-top: 1px;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  animation: spin 700ms linear infinite;
}

.sheetfoot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  background: var(--shell);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
/* A drawer reaches the edge, which is what coming out of the side means. The
   overlay pads itself by twenty pixels for a dialog floating in the middle,
   and on a side panel that reads as a panel that missed: a dark strip down the
   outside, and on a phone forty pixels of the width gone to it. */
.overlay.is-side { align-items: stretch; justify-content: flex-end; padding: 0; }
.drawer {
  width: min(520px, 100vw);
  display: flex; flex-direction: column;
  background: var(--surface-1);
  border-left: 1px solid var(--border-strong);
  animation: slidein var(--med) both;
}
/* A detail that is a screenful rather than a panel: a parcel carries its
   journey, its times, every scan and a map, and 520px is a column. Capped,
   because ninety percent of a 27 inch monitor is not a drawer any more, it is
   a page with a strip of list beside it, and a line of text that wide reads
   badly. */
.drawer.is-wide { width: min(90vw, 1200px); }
/* The panel takes focus when it opens so the keyboard starts inside it, and
   that is a cursor rather than a thing to draw a box around. */
.drawer:focus { outline: none; }
.drawerbody { padding: 16px 18px; overflow-y: auto; flex: 1; }

/* --- one parcel ----------------------------------------------------------- */
/* The facts on the left and the journey on a map to the right. The map is the
   half that can be left out: on a phone it is a smudge the size of a thumbnail
   and the strip above it says the same thing in words. */
.psheet { display: grid; grid-template-columns: 1.15fr 1fr; gap: 22px; align-items: start; }
/* A grid column will not shrink below what is in it unless it is told it may,
   and one wide thing then eats the other column. Seven sizes with names like
   "Grey / Green / Single" made the strip a thousand pixels of min-content, so
   the left column took everything and the right one was squeezed to a sliver
   with its cards running off the edge of the drawer. The strip scrolls; it was
   never asked to. */
.psheet > * { min-width: 0; }
/* A product is the other drawer on this grid, and its right column is
   reference rather than a picture: what it holds is read after the price
   rather than beside it, so it takes a little less width and keeps every row
   of it on a phone. */
.prodsheet { grid-template-columns: 1.1fr .9fr; }
.prodsheet .pside { position: static; display: flex; flex-direction: column; gap: 10px; }
.prodsheet .pmain { display: flex; flex-direction: column; gap: 10px; }

/* An order is the third drawer on this grid, and the one that came from a
   page laid out as a `.split` with two `.stack`s, which is the same shape by
   another name. What is new here is the head: the reference, what can be done
   and what is in the way are facts about the order rather than about a
   column, so they span the pair instead of sitting on top of the left one. */
.ordersheet { grid-template-columns: 1.15fr .85fr; }
.ordersheet .pside { position: static; display: flex; flex-direction: column; gap: 14px; }
.ordersheet .pmain { display: flex; flex-direction: column; gap: 14px; }
.odtop { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }
/* The same line the page carried under its title: what to quote, who it is
   for and where it goes. It wraps rather than being cut, because every part
   of it is a fact somebody reads rather than a label. */
.odsub {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  font-size: 12.5px; color: var(--text-muted);
}
.odtop .rowline:empty, .odtop > div:empty { display: none; }
/* What the merchant's own listing shows. Three, and a count over the third
   when there are more, rather than a strip that scrolls: the question this
   answers is what the thing looks like, and it is answered by the first
   picture. The rest are one press away.

   Square, because a listing mixes portraits and landscapes and a row of
   different heights reads as a row of different things. */
.pdshots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.pdshot {
  position: relative; display: block; padding: 0; border: 0; background: none;
  aspect-ratio: 1; border-radius: var(--radius-sm); overflow: hidden;
  cursor: pointer; font: inherit;
}
.pdshot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-1);
}
.pdshot:hover img { border-color: var(--border-strong); }
.pdshot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The count sits over the third one rather than beside it, so the row stays
   three wide however many there are. */
.pdmore {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .55);
  color: #fff; font-size: 17px; font-weight: 600; letter-spacing: -.01em;
  border-radius: var(--radius-sm);
}
.pdshot:hover .pdmore { background: rgba(0, 0, 0, .45); }

/* The product, the size in front and the one thing to do about it. */
.pdhead { display: flex; align-items: center; gap: 12px; }
.pdname { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* Cut rather than wrapped, both of them. The drawer head is one line of fact
   above the work, and a long title with a long SKU under it turned that into
   five lines on a phone before anything on the screen had been read. The whole
   title is in the drawer's own heading above it either way. */
.pdname b {
  font-size: 16px; letter-spacing: -.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pdname span {
  font-size: 12px; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pdacts { margin-left: auto; display: flex; align-items: center; gap: 8px; flex: none; }
/* The sizes stay reachable while the prices under them scroll: the drawer
   body is the scroller, so the strip pins to the top of it. */
.prodsheet .varstrip { position: sticky; top: -16px; z-index: 1; background: var(--surface-1); padding-top: 4px; margin-bottom: 0; }
.pkhead { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 16px; }
.pkhead > b { font-size: 17px; letter-spacing: -.02em; }
.prail { margin: 4px 0 18px; }
.ptimes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.ptime { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ptime span { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
.ptime b { font-size: 13.5px; font-weight: 600; }
.psplit { height: 1px; background: var(--border); margin: 20px 0 16px; }
.pside { position: sticky; top: 0; }

/* The product drawer gives up its second column sooner than the other two on
   this grid, and the table is why. Seven columns of delivered price need 479
   pixels at their narrowest, and a window of a thousand hands the left column
   441 to 472 of them: the panel beside it is taking half of a width that is
   not wide. Squeezing the table further is the wrong direction, because the
   next thing to go is the amounts, and an amount that wraps between the
   symbol and the number is two readings of one figure. Stacking costs nothing
   here for the reason that column is laid out the way it is: it is reference
   read after the price rather than beside it.

   Measured at the widths the drawer is drawn at: below this the table has the
   whole of the drawer, above it the left column is already past 479. */
@media (max-width: 1100px) {
  .prodsheet { grid-template-columns: 1fr; gap: 14px; }
}

@media (max-width: 900px) {
  .psheet { grid-template-columns: 1fr; gap: 0; }
  .prodsheet { grid-template-columns: 1fr; gap: 14px; }
  /* Not drawn rather than shrunk: a world map at this width is a smudge, and
     the journey above it already says where the parcel is in words. */
  /* Only the map. The half of a drawer that a phone is better off without is
     a picture of the world at thumbnail size, not a column of facts: hiding
     `.pside` outright took the product's prices, its details and its whole
     right-hand column off every phone the moment a second drawer reused this
     grid. */
  .pside.is-map { display: none; }
  /* The order drawer keeps its right-hand column on a phone: the address, the
     QC check and the activity are the half somebody opens it for. What goes is
     the map alone, for the same reason the parcel's does. */
  .ordersheet .shipmap { display: none; }
  .ptimes { grid-template-columns: 1fr 1fr; }
}
/* It slides in from the edge it is attached to. It used to move 24px and
   fade, which at this width reads as a panel appearing rather than as
   something being pulled out. */
@keyframes slidein { from { transform: translateX(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) {
  @keyframes slidein { from { opacity: 0; } to { opacity: 1; } }
}
@keyframes pop { from { transform: scale(.985); opacity: 0; } to { transform: none; opacity: 1; } }

/* --- thread --------------------------------------------------------------- */
.threadwrap { display: flex; flex-direction: column; min-height: 0; }
/* How much of a conversation is on screen at once. It was 420px on every
   screen there is, which on a laptop is four messages and a scrollbar: the
   thing being read is the conversation, and reading one four lines at a time
   is what makes people scroll back up to check what was already said. It
   follows the window now, with a floor for a phone and a ceiling so a tall
   screen does not put the composer somewhere nobody can see it. */
.msglist {
  display: flex; flex-direction: column; gap: 10px;
  overflow-y: auto; padding-right: 4px;
  max-height: clamp(320px, 54vh, 720px);
}
/* The inbox is the conversation and nothing else on the page competes with
   it, so it gets more than a thread that sits under an order. */
.inboxgrid .msglist { max-height: clamp(360px, 68vh, 900px); }
.msgempty { padding: 24px; text-align: center; font-size: 12.5px; color: var(--text-dim); }
.msg {
  display: flex; gap: 10px; align-items: flex-start;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  padding: 10px 12px;
}
.msg .mcol { min-width: 0; flex: 1; }
.msg.is-mine { background: var(--surface-2); border-color: var(--border-strong); }
.msg .mhead { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; flex-wrap: wrap; }
.msg .mhead b { font-size: 12.5px; }
.msg .mrole,
.msg .mchan,
.msg .mtime { font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-dim); }
.msg .mchan { color: var(--info-ink); }
.msg .mtime { margin-left: auto; }
.msg .mbody { font-size: 13px; color: var(--text-muted); line-height: 1.55; white-space: pre-wrap; }
.msg .matt { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.msg .att {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-mono); font-size: 10.5px;
  padding: 3px 8px; border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-muted);
}
.msg .att svg { width: 12px; height: 12px; }

/* A message you have just sent, before the server has it. It arrives rather
   than appears: the movement is what says something happened, and it is the
   whole reason this bubble exists. Held slightly back until it lands, so the
   difference between sent and sending is visible without being read. */
@keyframes msgin {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.msg.is-sending, .msg.is-failed { animation: msgin .18s ease-out both; }
.msg.is-sending { opacity: .62; }
.msg.is-sending .mstate { color: var(--text-dim); }
.msg.is-failed { border-color: color-mix(in srgb, var(--bad) 45%, var(--border)); }
.msg.is-failed .mstate {
  display: flex; align-items: center; gap: 8px; color: var(--bad);
}
.msg.is-failed .mstate .btn { padding: 2px 8px; }
@media (prefers-reduced-motion: reduce) {
  .msg.is-sending, .msg.is-failed { animation: none; }
}
.composer { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px; }
.composer textarea { width: 100%; resize: vertical; }
.composer .crow { display: flex; gap: 8px; margin-top: 8px; }
.composer .crow .btn:last-child { margin-left: auto; }
/* the top edge tints when the answer is yours to give */
.composer.is-owed { border-top-color: var(--accent-line); box-shadow: inset 0 2px 0 -1px var(--accent-line); }
.composer .cnotice { font-size: 11.5px; color: var(--text-dim); margin-top: 8px; }
.composer .crefslot { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.composer .crefslot .mref { flex: 1; margin-top: 0; }

/* the offer to move this thread to a phone: the same glass as a notice, one
   step quieter, because it is an offer and not an instruction */
/* An offer, on one line, above the box somebody is about to type in. It used
   to be a card with a paragraph in it, which is a lot of furniture for a
   question that takes one word to answer. */
.cinvite {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px 7px 10px; margin-bottom: 10px;
  border: 1px solid var(--border); border-radius: 999px;
  background: color-mix(in srgb, var(--info) 5%, transparent);
}
.cinvite .ciico {
  width: 20px; height: 20px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--info); background: color-mix(in srgb, var(--info) 14%, transparent);
}
.cinvite .citxt {
  min-width: 0; flex: 1; font-size: 12.5px; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Turning it down is a small mark, not a button competing with the offer. */
.cinvite .cino {
  flex: none; width: 22px; height: 22px; padding: 0; line-height: 1;
  border: 0; border-radius: 50%; cursor: pointer;
  background: transparent; color: var(--text-dim); font-size: 15px;
}
.cinvite .cino:hover { background: var(--surface-2); color: var(--text); }
.composer .csent {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-dim); align-self: center;
}
@media (max-width: 720px) {
  /* The line stays a line. What goes at this width is the sentence, not the
     offer: the icon and the button still say what it is. */
  .cinvite .citxt { display: none; }
}

/* what a message points at: the object screen, one click away, without the
   conversation moving there */
.mref {
  display: flex; align-items: center; gap: 10px; margin-top: 9px;
  padding: 7px 9px; border-radius: 12px; text-decoration: none; color: inherit;
  border: 1px solid var(--border); background: var(--surface-1);
  transition: border-color var(--fast), background var(--fast);
}
a.mref:hover { border-color: var(--border-strong); background: var(--card-hover); }
.mref .thumb { width: 34px; height: 34px; flex: none; }
.mref .mrl { min-width: 0; display: block; }
.mref .mrl b { display: block; font-size: 12.5px; font-weight: 600; }
.mref .mrl span { font-size: 11px; color: var(--text-dim); }
.mref .mrgo { margin-left: auto; font-family: var(--font-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }

/* An ask for one price for a whole order, on the open line. A task rather
   than a line of chat, so it is a card with its state on it, and it keeps the
   warn edge for as long as the warehouse owes it. */
.mtask {
  margin-top: 9px; padding: 10px 11px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface-1);
}
.mtask.is-open { border-color: color-mix(in srgb, var(--warn) 45%, var(--border)); }
.mtask .mref { margin-top: 8px; background: var(--card); }
.mtaskhead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.mtaskrows { margin-top: 8px; display: grid; gap: 3px; font-size: 12px; }
.mtaskrows > div { display: flex; justify-content: space-between; gap: 12px; }
.mtaskact { margin-top: 9px; display: flex; justify-content: flex-end; }
.msg .mtable { margin-top: 8px; }

/* The order price form and the question before it goes. */
.opitems { display: grid; gap: 6px; margin-bottom: 12px; }
.opitem { display: flex; align-items: center; gap: 10px; min-width: 0; }
.opitem .stack { min-width: 0; flex: 1; gap: 0; line-height: 1.3; }
.opnote {
  margin: 4px 0 12px; padding: 8px 11px; border-left: 2px solid var(--accent-line);
  background: var(--surface-1); border-radius: 0 10px 10px 0; font-size: 13px; white-space: pre-wrap;
}
.opcompare { display: flex; align-items: stretch; gap: 10px; }
.opcol {
  flex: 1; display: grid; gap: 4px; padding: 12px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--surface-1);
}
.opcol b { font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }
.opcol.is-new { border-color: var(--border-strong); }
.oparrow { display: flex; align-items: center; color: var(--text-dim); }
.opdiff { margin: 10px 0 4px; font-family: var(--font-mono); font-size: 12px; color: var(--text-dim); }
.opdiff.is-up { color: var(--warn-ink); }
.opdiff.is-down { color: var(--text); }

/* What a price answer did: the old figure struck through beside the new. */
.pcgroup + .pcgroup { margin-top: 16px; }
.pcgroup .eyebrow { margin-bottom: 6px; }
.pcgrid td.num s { margin-right: 4px; }
.pcdelta { margin-left: 6px; font-family: var(--font-mono); font-size: 10.5px; }
.pcdelta.is-up { color: var(--warn-ink); }
.pcdelta.is-down { color: var(--text-dim); }
.pcoff { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border); }
.pcoffrow { display: flex; gap: 12px; padding: 5px 0; font-size: 12.5px; }
.pcoffrow b { flex: none; min-width: 90px; font-weight: 600; }

/* what happened to a message after Send */
.msg .mstate { font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); margin-top: 6px; }
.msg .mstate.is-ok { color: color-mix(in srgb, var(--good) 78%, var(--text)); }

/* both clocks, and what they mean for an answer */
.mclocks {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 11px; margin-bottom: 12px;
  border: 1px solid var(--border); border-radius: 11px;
  background: var(--surface-1);
  font-size: 11.5px; color: var(--text-dim);
}
/* One line each, cut rather than wrapped. Who somebody is can be a shop name
   of any length, and a name that wraps inside a strip of clocks turns three
   short facts into a paragraph. */
.mclock {
  display: inline-flex; align-items: center; gap: 6px;
  min-width: 0; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mclock b { font-family: var(--font-mono); font-size: 11.5px; font-weight: 500; color: var(--text-muted); }
.cdot { width: 6px; height: 6px; border-radius: 50%; flex: none; background: var(--text-dim); }
.cdot.on { background: var(--good); box-shadow: 0 0 0 3px var(--good-soft); }
.csep { width: 1px; height: 12px; background: var(--border); }
.cnote { color: var(--text-muted); }

/* the room and who is in it. The line this replaces was a group chat, so the
   header says group rather than person. */
.teamstrip { display: flex; align-items: center; gap: 12px; min-width: 0; }
.teamstrip .avstack { gap: 4px; }
.teamstrip .avstack .avat + .avat { margin-left: 0; }
.teamstrip .tstxt { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.teamstrip .tstxt b {
  font-size: 14px; font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.teamstrip .tstxt span { font-size: 11.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deskteam { padding: 0 16px 12px; }
.deskteam .teamstrip { padding-top: 2px; }

/* --- who that face belongs to -------------------------------------------- */
/* A row of faces says a team answers rather than one person. It does not say
   which of them sources and which of them packs, and that is the thing worth
   knowing before you write. A browser tooltip was doing it: a second late, no
   room for a photograph, and invisible to a keyboard.

   It hangs off the body rather than the strip, because a strip with overflow
   on it would clip the one thing this is for. */
.pcardwrap {
  position: fixed;
  z-index: 90;
  pointer-events: auto;
  animation: pcin var(--fast) ease-out;
}
@keyframes pcin { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .pcardwrap { animation: none; } }

.pcard {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  min-width: 208px;
  max-width: 300px;
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 1px solid var(--line);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .5);
}
.pcard .avat { flex: none; }
.pcard .pctxt { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.pcard .pctxt > b { font-size: 14px; font-weight: 600; letter-spacing: -.01em; }
/* What they do. The reason the card exists: sourcing and packing are two
   people to write to. */
.pcard .pcjob { font-size: 12.5px; color: var(--text); }
/* Where they are and what time it is there, which is the only line that
   changes between opening this in the morning and opening it at six. */
.pcard .pcwhere { font-size: 11.5px; color: var(--text-muted); }
.pcard .pcwhere b { font-variant-numeric: tabular-nums; font-weight: 500; color: var(--text); }
.pcard .pcnote { display: block; color: var(--text-dim); }
.pcard .pcmail { font-size: 11px; color: var(--text-dim); word-break: break-all; }

/* A face a card hangs off is reachable by keyboard, so it has to show it. */
.avat[data-person] { cursor: default; }
.avat[data-person]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- the desk ------------------------------------------------------------ */
/* The conversations, on the home screen. The rail is the same glass as the
   notices: a capsule with the light catching its top left, and the selected
   segment lifted out of it on a lozenge of its own. */
.deskhead {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px 12px;
}
.deskhead .btn { margin-left: auto; }
/* Two clocks and one state, on the row that is already there. What somebody
   glances at is the hours; the sentence behind them is the tooltip. */
.clockline {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-muted); white-space: nowrap;
  min-width: 0; overflow: hidden;
}
.clockline b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.clockline .clshort {
  margin-left: 4px; padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-dim); font-size: 11px;
}
/* Only two states are worth a word here, so only two are drawn. Amber for the
   hour before they shut, because that is a thing to act on; grey once they
   have, because by then there is nothing to do but write it anyway. */
.clockline .cltag {
  margin-left: 4px; padding: 2px 8px; border-radius: var(--radius-pill);
  font-size: 10.5px; font-weight: 600; letter-spacing: .01em;
  border: 1px solid color-mix(in srgb, var(--warn) 32%, transparent);
  background: var(--warn-soft); color: var(--warn);
}
.clockline .cltag.is-shut {
  border-color: var(--border); background: var(--surface-2); color: var(--text-dim);
}
@media (max-width: 860px) { .deskhead .clockline { display: none; } }
.glassrail {
  display: inline-flex; gap: 3px; padding: 3px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--glass-rim);
  background:
    radial-gradient(130% 300% at 0% -60%, var(--glass-sheen), transparent 60%),
    color-mix(in srgb, var(--surface-2) 88%, transparent);
  backdrop-filter: blur(30px) saturate(200%);
  -webkit-backdrop-filter: blur(30px) saturate(200%);
  box-shadow: inset 0 1px 0 var(--glass-lip), inset 0 -1px 0 var(--glass-base);
}
.grpill {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: 12.5px; font-weight: 500;
  color: var(--text-muted); cursor: pointer;
  padding: 6px 14px; border: 1px solid transparent; border-radius: var(--radius-pill);
  background: none;
  transition: color var(--fast), background var(--fast), border-color var(--fast);
}
.grpill:hover { color: var(--text); }
.grpill.is-on {
  color: var(--text);
  border-color: var(--glass-rim);
  background:
    radial-gradient(120% 240% at 0% -40%, var(--glass-sheen), transparent 62%),
    var(--glass-fill);
  box-shadow: inset 0 1px 0 var(--glass-lip), 0 6px 14px -10px #000;
}
.grpill:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
.grcount {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .04em;
  padding: 1px 6px; border-radius: var(--radius-pill);
  background: var(--glass-fill); color: var(--text-dim);
}
.grcount.hot { background: var(--accent-fill); color: var(--accent-ink); }

.deskthread { padding: 0 16px 16px; }
.deskempty { padding: 22px 18px 26px; font-size: 13px; color: var(--text-dim); max-width: 60ch; }
.deskback { display: flex; align-items: center; gap: 10px; padding: 0 16px 12px; }
.deskback .btn:last-child { margin-left: auto; }

.desklist { display: flex; flex-direction: column; border-top: 1px solid var(--border); }
.deskrow {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 16px; border: 0; border-bottom: 1px solid var(--border);
  background: none; color: var(--text); cursor: pointer;
  transition: background var(--fast);
}
.deskrow:last-child { border-bottom: 0; }
.deskrow:hover { background: var(--surface-1); }
.deskrow .thumb { width: 34px; height: 34px; flex: none; }
.deskrow .dtxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.deskrow .dtop { display: flex; align-items: baseline; gap: 10px; }
.deskrow .dtop b { font-size: 12.5px; font-weight: 600; }
.deskrow .dwhen { margin-left: auto; font-family: var(--font-mono); font-size: 9.5px; color: var(--text-dim); flex: none; }
.deskrow .dprev { font-size: 12px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deskrow .dflag {
  flex: none; font-family: var(--font-mono); font-size: 9px; letter-spacing: .12em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--radius-pill);
  background: var(--accent-fill); color: var(--accent-ink);
}
@media (max-width: 720px) {
  .deskhead { padding: 12px 12px 10px; }
  .deskhead .btn { margin-left: 0; }
  .glassrail { width: 100%; justify-content: space-between; }
}

/* picking the order a message points at */
.picklist { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-height: 300px; overflow-y: auto; }
.pickrow {
  display: grid; grid-template-columns: 96px 1fr auto; gap: 12px; align-items: baseline;
  width: 100%; text-align: left; cursor: pointer;
  padding: 9px 11px; border: 1px solid var(--border); border-radius: 11px;
  background: var(--card); color: var(--text); font: inherit; font-size: 13px;
  transition: border-color var(--fast), background var(--fast);
}
.pickrow:hover { border-color: var(--border-strong); background: var(--card-hover); }
.pickrow .pn { font-size: 11px; color: var(--text-muted); }
.pickrow .pt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pickrow .pm { font-size: 11.5px; color: var(--text-dim); }

/* who owes a reply */
.mowed {
  display: flex; align-items: center; gap: 10px; margin: 2px 0 0;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, var(--accent) 82%, var(--text));
}
.mowed i { flex: 1; height: 1px; background: linear-gradient(90deg, var(--accent-line), transparent); }
.mowed i:first-child { background: linear-gradient(270deg, var(--accent-line), transparent); }

/* --- uploader ------------------------------------------------------------- */
/* A country and its flag, held together so a line never wraps between them.
   The flag sits a hair above the baseline of the text beside it, because an
   emoji's own box is taller than a letter's and lines up low without it. */
.ctry { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.ctry i {
  font-style: normal;
  font-size: 1.08em;
  line-height: 1;
  position: relative;
  top: -.5px;
  /* Not the label face: a flag in a tracked, uppercased run gets spaced away
     from the word it belongs to. */
  letter-spacing: 0;
}

/* a file the warehouse sent, shown as a file */
/* The sizes of the product, above the price. Each says whether it has a
   price, because which sizes are sellable is the question it answers. Scrolls
   rather than wraps: seven sizes on a phone is one line to swipe, not three
   rows pushing the price off the screen.

   In the drawer it is `is-flat`: a reading rather than a control. Pressing
   one used to turn the whole screen into that size, which is how a drawer
   about a product came to be about a variant. */
.varstrip {
  display: flex; gap: 8px; overflow-x: auto; padding: 0 0 4px;
  margin: 0 0 14px; scrollbar-width: none;
}
.varstrip::-webkit-scrollbar { display: none; }
.varchip {
  flex: none; display: flex; flex-direction: column; gap: 2px;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper); text-decoration: none; color: var(--text);
  transition: border-color .15s ease, background .15s ease;
  /* Inside the drawer it is a button rather than a link, because it changes
     what is in front of you instead of taking you somewhere. A button draws
     itself in the browser's own font unless it is told otherwise. */
  font: inherit; text-align: left; cursor: pointer;
}
.varchip:hover { border-color: var(--line2); background: var(--paper2); }
.varchip.is-on { border-color: var(--accent); background: var(--accent-soft); }
.varstrip.is-flat .varchip { cursor: default; }
.varstrip.is-flat .varchip:hover { border-color: var(--line); background: var(--paper); }

/* Which size the price table is on. A picker rather than a stack of rows:
   what somebody does on this card is look up one size, and a row each meant
   twenty eight presses to learn that twenty seven of them are identical. It
   is drawn only where the sizes really differ on price; one price over the
   lot is one table and nothing to choose. */
.pdpick {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--line);
}
.pdpick label {
  font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-dim); flex: none;
}
.pdpick select { flex: 1; min-width: 0; max-width: 340px; }
.pdpick .hint { margin: 0 0 0 auto; flex: none; white-space: nowrap; }
@media (max-width: 720px) {
  .pdpick { flex-wrap: wrap; }
  .pdpick select { max-width: none; }
  .pdpick .hint { margin: 0; width: 100%; }
}

/* A change in Shopify is per size, so the way to answer it is beside the size
   it happened to rather than on one button that cannot say which. */
.pdstale { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 14px; }
.pdstalerow {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper);
}
.pdstalerow b { font-size: 12.5px; font-weight: 600; }
.varchip b { font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.varchip em {
  font-style: normal; font-size: 10px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-dim);
}
.varchip em.is-ok { color: var(--good); }
.varchip em.is-wait { color: var(--warn); }

/* What moved in Shopify after the price was given: what it was, an arrow, and
   what it is now. Struck through on the left rather than labelled, because a
   was-and-now that needs a legend is two columns nobody reads. The stop half
   takes the amber every other held thing wears; the recorded half is plain,
   since it changes nothing about the price. */
.sincelist { display: flex; flex-direction: column; gap: 8px; }
.sincerow { display: flex; flex-direction: column; gap: 3px; }
.sincerow .dt {
  font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-dim);
}
.sincerow .dd { display: flex; align-items: center; gap: 7px; font-size: 12.5px; flex-wrap: wrap; }
.sincerow .dd s { color: var(--text-dim); text-decoration-color: var(--line2); }
.sincerow .dd i { color: var(--text-dim); display: flex; flex: none; }
.sincerow .dd b { font-weight: 600; }
.sincelist.is-stop {
  border-left: 2px solid var(--warn); padding-left: 10px;
}
.sincelist.is-stop .dd b { color: var(--warn); }

/* A section heading that also carries the way to the other section. Two long
   lists on one screen and the second starts under however many rows the first
   happens to have, so on a busy morning it is a scroll past two hundred rows
   to reach it. `scroll-margin-top` is what stops the sticky topbar landing on
   the heading somebody just jumped to. */
.eyerow { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.eyerow[id] { scroll-margin-top: 76px; }
.jump {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-dim); text-decoration: none;
  padding: 3px 8px; border: 1px solid var(--line); border-radius: 999px;
  transition: color .15s ease, border-color .15s ease;
}
.jump:hover { color: var(--text); border-color: var(--line2); }

/* How long a paid order has stood here, coloured on the ladder that already
   decides what to pick up first: four days, six, eight. Not a scale of its
   own, because two ladders on one row is two answers to how late this is.
   Under four days it stays plain: a mark on nearly every row is one nobody
   sees. */
.age.is-warm { color: var(--warn); }
.age.is-hot { color: var(--accent); font-weight: 600; }

/* One press to a backlog. The count on each chip is inside the tab that is
   open, so it never says forty about a queue of six. */
.agerow { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
.agerow .agelabel {
  font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-dim); margin-right: 2px;
}
.agerow .chip em {
  font-style: normal; margin-left: 6px; padding: 1px 5px;
  border-radius: 999px; background: var(--line); font-size: 10px;
}
.agerow .chip.is-on em { background: rgba(0, 0, 0, .18); }
.agerow .chip.is-clear { color: var(--text-dim); }

/* The shop code beside an order number. Three merchants each having a #1002
   is normal rather than a fault, and the thing that tells them apart was in
   another column reading as a myshopify domain. Same badge as the one in the
   merchant pill, because it is the same fact; only the margin differs, since
   here it follows text rather than sitting in a flex row. */
.mono .mno, .mono + .mno {
  display: inline-block; margin-left: 6px;
  font-size: 9.5px; letter-spacing: .06em;
  color: var(--text-dim); background: var(--surface-2);
  padding: 1px 6px; border-radius: var(--radius-pill);
  vertical-align: 1px;
}

/* A short string somebody is about to paste somewhere else. Read character by
   character, so monospace and a little wider than the text around it, and
   pressed to be copied rather than selected with a trackpad. It says it
   worked on itself, where the eye already is, as well as in the toast. */
.copyable {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 8px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface-2); color: var(--text);
  font: inherit; cursor: pointer; vertical-align: middle;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.copyable .mono { font-size: 12px; letter-spacing: .04em; }
.copyable svg { color: var(--text-dim); flex: none; }
.copyable:hover { border-color: var(--line2); }
.copyable:hover svg { color: var(--text); }
.copyable.is-done { border-color: var(--good); color: var(--good); }
.copyable.is-done svg { color: var(--good); }

/* In a table row: no box, no padding, and the icon only once the row is under
   the pointer, so the column reads as numbers rather than as buttons. */
.copyable.is-plain { padding: 0; border-color: transparent; background: none; gap: 4px; }
.copyable.is-plain svg { opacity: 0; transition: opacity .15s ease; }
tr:hover .copyable.is-plain svg, .copyable.is-plain:focus-visible svg { opacity: 1; }
.copyable.is-plain.is-done svg { opacity: 1; }

.filechips { display: flex; flex-wrap: wrap; gap: 6px; }
/* --- the warehouse's working days -----------------------------------------
   A month drawn as a month: seven columns, Monday first, and the days they
   work picked out. The alternative was a field holding the ranges as text,
   which is a shape somebody can get subtly wrong without noticing, and a
   schedule that is wrong says something untrue every day for a month. */
.wdgrid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  margin-top: 12px; max-width: 420px;
}
.wd-head {
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim); text-align: center; padding-bottom: 2px;
}
.wd-pad { visibility: hidden; }
.wd {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-1); color: var(--text-muted);
  font: 600 12.5px/1 var(--font-display); font-variant-numeric: tabular-nums;
  cursor: pointer; padding: 0;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.wd:hover { border-color: var(--border-strong); color: var(--text); }
.wd:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A day they work is filled rather than outlined. This grid is read at a
   glance to answer one question, are they in, and thirty outlines answer it
   more slowly than a fill does. Not the accent: that marks status and one
   emphasis point per screen, and a working day is neither. */
.wd.is-on {
  background: var(--text); border-color: var(--text); color: var(--canvas);
}
.wd.is-on:hover { border-color: var(--text); }

/* A pair of dates that says which is which. Two identical mm/dd/yyyy boxes
   beside each other is a guess, and the guess is wrong half the time. */
.datespan {
  display: inline-flex; align-items: center; gap: 6px;
}
/* From and to are two date fields with a word between them, and a date field
   in Chrome is 150px whatever you ask of it. Side by side that is 354px, so on
   a small phone the filter bar hung off the right of the screen and took the
   table with it. They wrap, and each takes what is left. */
@media (max-width: 720px) {
  .datespan { display: flex; flex-wrap: wrap; width: 100%; }
  .datespan input { flex: 1 1 120px; min-width: 0; }
}
.datespan em {
  font-style: normal; font-size: 11px; color: var(--text-dim);
  letter-spacing: .02em;
}

.filechip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px 4px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--card-hover);
  font-size: 11.5px;
  color: var(--text-muted);
}
.filechip svg { color: var(--text-dim); flex: none; }

/* Not found sits at the end of the label line, in the quietest grey the
   interface has, because it is an escape hatch and not an invitation. */
.fieldwrap { position: relative; }
/* The grid item is the wrap, not the field inside it, so a table that needs
   the row has to say so out here: `.field.is-wide` further down is spanning a
   column the wrap never gave it. A size chart in half a row is the picture it
   used to be, in another form. */
.fieldwrap.is-file, .fieldwrap.is-wide { grid-column: 1 / -1; }
.nfbtn {
  position: absolute;
  top: 0;
  right: 0;
  padding: 0 2px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 10.5px;
  line-height: 1.5;
  color: var(--text-dim);
  cursor: pointer;
  opacity: .7;
  transition: color var(--fast), opacity var(--fast);
}
.nfbtn:hover { color: var(--text-muted); opacity: 1; }
.nfbtn:focus-visible { outline: 1px solid var(--border-strong); outline-offset: 2px; border-radius: 4px; }
.fieldwrap.is-nf .nfbtn { color: var(--text-muted); opacity: 1; }
.missing-reason[hidden] { display: none; }
.fieldwrap.is-nf .field input,
.fieldwrap.is-nf .field textarea,
.fieldwrap.is-nf .tprev,
.fieldwrap.is-nf .uploader { opacity: .4; pointer-events: none; }
.fieldwrap.is-nf .field > label::after {
  content: " · not found";
  color: var(--text-dim);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* a file field in a form: the label above, the drop zone under it, and it
   takes the full width of the grid rather than half a column */
.field.is-file { grid-column: 1 / -1; }
.field.is-file > label { display: block; margin-bottom: 6px; }
.field.is-file .uploader { padding: 12px; }
.field.is-file .filelist { margin-top: 8px; }

.uploader {
  display: flex; align-items: center; gap: 12px;
  padding: 14px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--card);
  cursor: pointer;
  transition: border-color var(--fast), background var(--fast);
}
.uploader:hover, .uploader.is-over { border-color: var(--accent-line); background: var(--accent-soft); }
.uploader .uico { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; background: var(--surface-2); color: var(--text-muted); }
.uploader .utxt { display: flex; flex-direction: column; gap: 2px; }
.uploader .utxt b { font-size: 13px; }
.uploader .utxt span { font-size: 12px; color: var(--text-dim); }
.filelist { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.filerow {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
}
.filerow .fname { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-muted); }
.filerow .iconbtn { margin-left: auto; width: 24px; height: 24px; }

/* What was dropped, as the thing itself. A filename is a receipt for a
   photograph, and a swatch of the blue is dropped precisely so somebody looks
   at the blue. */
.filelist.is-tiles { flex-direction: row; flex-wrap: wrap; gap: 8px; }
.filetile {
  position: relative; width: 64px; height: 64px;
  border-radius: var(--radius-sm); overflow: hidden;
  border: 1px solid var(--border); background: var(--surface-1);
}
.filetile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.filetile .iconbtn {
  position: absolute; top: 2px; right: 2px; width: 20px; height: 20px;
  background: rgba(0, 0, 0, .55); color: #fff; border-color: transparent;
}
.filetile .iconbtn:hover { background: rgba(0, 0, 0, .78); }

/* A field that is itself the drop target says so while something is over it,
   the same way the box with the button does. */
.fieldwrap.is-over, .field.is-over { border-radius: var(--radius-sm); outline: 1px dashed var(--accent-line); outline-offset: 3px; }

/* --- switch --------------------------------------------------------------- */
.switch { display: flex; align-items: flex-start; gap: 11px; cursor: pointer; padding: 9px 0; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .track {
  width: 38px; height: 22px; flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  position: relative;
  transition: background var(--fast), border-color var(--fast);
}
.switch .track::after {
  content: '';
  position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform var(--fast), background var(--fast);
}
.switch input:checked + .track { background: var(--accent-soft); border-color: var(--accent-line); }
.switch input:checked + .track::after { transform: translateX(16px); background: var(--accent); }
.switch input:focus-visible + .track { box-shadow: var(--ring); }
.switch .slabel { display: flex; flex-direction: column; gap: 2px; }
.switch .slabel b { font-size: 13px; font-weight: 500; }
.switch .slabel span { font-size: 12px; color: var(--text-dim); line-height: 1.5; }

.is-plus { color: var(--good-ink); }

.checklist { display: flex; flex-direction: column; gap: 6px; }
.checklist .pick { align-items: center; }
.notebox {
  margin-top: 10px;
  padding: 9px 11px;
  border-left: 2px solid var(--warn);
  background: var(--warn-soft);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 12.5px;
  color: var(--text-muted);
}
.pkgcard .linerow { font-size: 12.5px; }

/* The logo on the packaging, drawn (assets/packmock.js). The stage keeps a
   dark ground in both themes, the same call the globe makes: a white bag on a
   white card is a bag nobody can see. */
.pm { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 0; padding: 0; overflow: hidden; margin-bottom: 22px; }
.pm-stage { position: relative; min-height: 330px; background: radial-gradient(120% 90% at 50% 20%, #1c1c20 0%, #0b0b0d 60%, #050506 100%); border-right: 1px solid var(--border); display: grid; place-items: center; }
.pm-art { width: 100%; max-width: 440px; padding: 18px 20px 8px; }
.pm-art svg { display: block; width: 100%; height: auto; }
.pm-art.is-in svg { animation: pmIn .5s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes pmIn { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
.pm-tag { position: absolute; left: 14px; top: 12px; font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: rgba(245, 245, 247, .55); border: 1px dashed rgba(255, 255, 255, .2); border-radius: 999px; padding: 3px 9px; }
.pm-side { padding: 18px 18px 16px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.pm-head b { display: block; font-size: 15px; font-weight: 600; }
.pm-head span { display: block; color: var(--text-muted); font-size: 12.5px; margin-top: 3px; }
.pm-kinds { display: grid; gap: 6px; }
.pm-kind { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 10px; align-items: center; text-align: left; padding: 8px 10px; border-radius: 10px; border: 1px solid var(--border); background: transparent; color: var(--text); cursor: pointer; font: inherit; transition: border-color var(--fast), background var(--fast); }
.pm-kind:hover { background: var(--card-hover); }
.pm-kind[aria-checked="true"] { border-color: var(--text-muted); background: var(--surface-1); }
.pm-kind b { display: block; font-size: 13px; font-weight: 500; }
.pm-kind em { display: block; font-style: normal; color: var(--text-dim); font-size: 11.5px; }
.pm-ico { width: 30px; height: 30px; border-radius: 8px; background: var(--surface-1); border: 1px solid var(--border); position: relative; }
.pm-ico::after { content: ''; position: absolute; inset: 8px; border: 1.5px solid var(--text-muted); border-radius: 3px; }
.pm-ico-sticker::after { border-radius: 50%; inset: 9px; }
.pm-ico-tape::after { inset: 13px 5px; border-radius: 2px; }
.pm-ico-card::after { inset: 10px 6px; border-radius: 2px; }
.pm-words { display: grid; gap: 6px; }
.pm-words .pm-lbl { width: auto; }
.pm-words textarea { width: 100%; resize: vertical; min-height: 54px; }
/* The same drawing on an approved item: no controls, a strip across the top
   of the card it belongs to. */
.pm-mini { margin: -2px 0 12px; border-radius: 10px; overflow: hidden; border: 1px solid var(--border); }
.pm-mini .pm-stage { min-height: 0; border-right: 0; }
.pm-mini .pm-art { max-width: 300px; padding: 30px 16px 4px; }
.pm-proof { margin-top: 10px; }
#pkDraw.pm-mini { margin: 0 0 12px; }
#pkDraw .pm-art { max-width: 280px; padding: 26px 12px 0; }
.pm-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.pm-lbl { width: 52px; color: var(--text-dim); font-size: 12px; }
.pm-sw { display: flex; gap: 8px; }
.pm-dot { width: 24px; height: 24px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, .22); cursor: pointer; padding: 0; box-shadow: 0 0 0 2px transparent; transition: box-shadow var(--fast); }
.pm-dot.is-clear { background: repeating-linear-gradient(45deg, #bdbdc2 0 4px, #f5f5f7 4px 8px); }
.pm-dot[aria-checked="true"] { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--text-muted); }
.pm-inks { display: inline-flex; padding: 2px; gap: 2px; background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-sm); }
.pm-ink { font: inherit; font-size: 12px; padding: 4px 10px; border: 0; border-radius: 6px; background: transparent; color: var(--text-dim); cursor: pointer; }
.pm-ink[aria-checked="true"] { background: var(--card-hover); color: var(--text); }
.pm-file { margin: 0; color: var(--text-dim); font-size: 12px; }
.pm-file button { font: inherit; color: var(--text); background: none; border: 0; padding: 0; text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.pm-foot { margin-top: auto; }
@media (prefers-reduced-motion: reduce) { .pm-art.is-in svg { animation: none; } }
@media (max-width: 720px) {
  .pm { grid-template-columns: 1fr; }
  .pm-stage { min-height: 0; border-right: 0; border-bottom: 1px solid var(--border); }
  .pm-dot { width: 32px; height: 32px; }
  .pm-ink { padding: 9px 12px; }
  .pm-foot .btn { width: 100%; }
}

/* --- impersonation bar ---------------------------------------------------- */
.imperbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  display: flex; align-items: center; gap: 14px;
  padding: 10px 18px;
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-size: 12.5px;
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .4);
}
.imperbar .imico { display: inline-flex; align-items: center; }
.imperbar .imtxt { line-height: 1.4; }
.imperbar svg { width: 14px; height: 14px; }
.imperbar b { font-weight: 700; }
.imperbar .btn { margin-left: auto; background: var(--accent-ink); color: var(--text); border-color: transparent; }
body.has-imper .content { padding-bottom: 70px; }

/* --- when we are the ones not answering ----------------------------------- */
/* The same shape as the bar above it and a different tone on purpose: that one
   is a state somebody chose, this is one that happened to them. It sits at the
   foot rather than the head so it never pushes the screen somebody is reading,
   and it is a condition rather than an event, so it stays while it is true. */
.offlinebar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 61;
  display: flex; align-items: center; gap: 14px;
  padding: 11px 18px;
  background: var(--bad-soft);
  border-top: 1px solid var(--bad);
  color: var(--text);
  font-size: 12.5px;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}
.offlinebar .ofico { display: inline-flex; align-items: center; color: var(--bad); }
.offlinebar .oftxt { line-height: 1.45; color: var(--text-muted); }
.offlinebar .oftxt b { color: var(--text); font-weight: 600; }
.offlinebar svg { width: 14px; height: 14px; }
.offlinebar .btn { margin-left: auto; flex: none; }
body.has-offline .content { padding-bottom: 76px; }
@media (max-width: 720px) {
  .offlinebar { flex-wrap: wrap; gap: 8px 12px; }
  .offlinebar .btn { margin-left: 26px; }
}

/* --- settings ------------------------------------------------------------- */
.storerow {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  grid-template-areas: "ico txt badge" ". rail rail";
  gap: 10px 12px;
  align-items: center;
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}
.storerow:last-of-type { border-bottom: none; }
.storerow .sg {
  grid-area: ico;
  width: 30px; height: 30px;
  border-radius: 9px;
  display: grid; place-items: center;
}
.storerow .stxt { grid-area: txt; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.storerow .stxt b { font-size: 13.5px; }
.storerow .stxt span { font-size: 10.5px; color: var(--text-dim); }
.storerow .badge { grid-area: badge; }
.storerow .srail { grid-area: rail; display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.storerow .srail .field { max-width: 150px; }
/* A refusal from Shopify runs to five sentences and belongs on the row, but
   printed in full it makes one row four rows tall. Shut by default, and the
   summary is red because the thing it hides is that a customer was not told. */
/* Where a parcel is, drawn. Beside the chip, never instead of it. Drawn in
   the page by U.parcel(), so there is nothing to load and it inherits the
   theme. It lifts and fades in once, on the rule in design.md: fifteen to
   twenty five pixels, four to six hundred milliseconds, never a bounce. And
   then it is still, because a figure that loops for ever is decoration and
   this one is looked at every day. */
.parcelfig {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  animation: parcelin .5s cubic-bezier(.22,.61,.36,1) both;
}
.parcelfig svg { overflow: visible; display: block; }
@keyframes parcelin { from { opacity: 0; transform: translateY(14px); } }

/* One detail answers a hover, and the detail is the state, so the movement
   carries the meaning the drawing already has rather than being laid on top. */
.parcelfig .pk-dash,
.parcelfig .pk-body,
.parcelfig .pk-stamp,
.parcelfig .pk-van,
.parcelfig .pk-wheel,
.parcelfig .pk-tick,
.parcelfig .pk-bang {
  transition: transform .5s cubic-bezier(.22,.61,.36,1), opacity .5s cubic-bezier(.22,.61,.36,1);
}
.parcelfig .pk-wheel, .parcelfig .pk-tick, .parcelfig .pk-bang, .parcelfig .pk-stamp {
  transform-box: fill-box;
  transform-origin: center;
}
tr:hover .parcelfig .pk-dash, .parcelrow:hover .pk-dash { transform: translateX(-5px); opacity: .85; }
tr:hover .parcelfig .pk-body, .parcelrow:hover .pk-body { transform: translateX(3px); }
tr:hover .parcelfig .pk-stamp, .parcelrow:hover .pk-stamp { transform: rotate(-4deg) translateY(-2px); }
tr:hover .parcelfig .pk-van, .parcelrow:hover .pk-van { transform: translateX(4px); }
tr:hover .parcelfig .pk-wheel, .parcelrow:hover .pk-wheel { transform: rotate(38deg); }
tr:hover .parcelfig .pk-tick, .parcelrow:hover .pk-tick { transform: scale(1.1); }
tr:hover .parcelfig .pk-bang, .parcelrow:hover .pk-bang { transform: translateY(-3px); }

@media (prefers-reduced-motion: reduce) {
  .parcelfig { animation: none; }
  .parcelfig [class^="pk-"] { transition: none; }
}

/* The phone row for a parcel: the figure, then the four things somebody
   opened this screen for. */
.parcelrow { display: flex; gap: 12px; align-items: flex-start; padding: 2px 0; }
.parcelrow .pr-txt { min-width: 0; flex: 1; }
.parcelrow .pr-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Two lines, then an ellipsis. For a carrier's own words on a row in a list
   long enough that row height is what decides how much fits on a screen. */
.clamp2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
}

.whydetail { margin-top: 4px; }
.whydetail > summary {
  cursor: pointer;
  font-size: 11px;
  color: var(--bad, #e2402f);
  list-style: none;
}
.whydetail > summary::-webkit-details-marker { display: none; }
.whydetail > summary::before { content: "\25B8 "; }
.whydetail[open] > summary::before { content: "\25BE "; }
.whydetail > div { margin-top: 3px; }

.pick.is-soon { opacity: .55; }
/* A country that already has a price. Ticked because the work is done, shut
   because there is nothing to ask for, and on the grid because taken off it
   the countries somebody set up were simply not there, which reads as a list
   that lost them. */
.pick.is-done {
  opacity: .58;
  cursor: default;
  border-color: var(--border);
  background: transparent;
}
.pick.is-done:hover { border-color: var(--border); }
.pick.is-done input { pointer-events: none; }
.pick.is-done em { color: var(--text-dim); }
.codebox {
  margin: 12px 0;
  padding: 14px;
  border: 1px dashed var(--accent-line);
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 18px;
  letter-spacing: .12em;
  text-align: center;
}

/* A label, not a status. This was drawn in the accent on a pill of accent, on
   nearly every row of a merchant's tracking list, beside the status chip that
   carries the actual news. The one row worth the red on that screen is the one
   that says Exception, and it was competing with twenty labels. */
.lastmile {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-dim);
  background: var(--surface-1);
  border: 1px solid var(--border);
  padding: 1px 5px; border-radius: var(--radius-pill);
}

/* --- parcel block --------------------------------------------------------- */
.parcel { padding: 14px 16px 16px; border-bottom: 1px solid var(--border); }
.parcel:last-child { border-bottom: none; }
.parcel .prow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.parcel .prow b { font-size: 13.5px; }
.parcel .pmeta { margin-left: auto; font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim); }
/* The date sits at the far end of the title row, which is where a date goes
   and is the one fact on this card the customer is actually asking about. */
.parcel .pwhen { margin-left: auto; font-size: 12px; color: var(--text-muted); white-space: nowrap; }
/* Whether the customer knows. Its own line, because it is the one thing here
   that can be wrong, and a dot rather than a chip, because most of the time
   it is simply true and a green badge on every parcel is decoration. */
.parcel .ptold {
  display: flex; align-items: baseline; gap: 8px;
  margin-top: 8px; font-size: 12px; color: var(--warn);
}
.parcel .ptold i {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--warn); transform: translateY(-1px);
}
.parcel .ptold.is-told { color: var(--text-dim); }
.parcel .ptold.is-told i { background: var(--good); }
.parcel .legs .leg:first-child { border-top: none; }

.flag { font-size: 15px; line-height: 1; margin-right: 2px; }

/* --- merchant pill -------------------------------------------------------- */
.mpill { display: inline-flex; align-items: baseline; gap: 7px; min-width: 0; }
/* The name gives way before the badge does: cut with an ellipsis rather
   than pushing the code out of a narrow column. */
.mpill b { font-weight: 500; color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mpill .mno {
  font-size: 9.5px;
  letter-spacing: .06em;
  color: var(--text-dim);
  background: var(--surface-2);
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
}
.mpill.is-block { flex-direction: column; align-items: flex-start; gap: 3px; }
/* Who to write to, under what the shop is called. */
.mpill .mwho {
  font-size: 11.5px; color: var(--text-muted);
  max-width: 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The domain is read rather than said, so it is quieter than the name and
   smaller than the person, and it never wraps: forty characters of myshopify
   over two lines is the paragraph this pill exists to avoid. */
.mpill .mwho.mono { font-size: 10.5px; color: var(--text-dim); text-transform: none; letter-spacing: 0; }

/* --- what a category adds ------------------------------------------------- */
/* This was nine cards in a grid, one per category, each carrying a switch in
   its head and its fields in its body. Picking what you sell is the same
   question as picking the countries you sell to, two panels up, so it is the
   same control: a row of `.pick tight` in `.pickgrid.is-choice`, green
   because being chosen is an answer rather than something to act on, and one
   Save at the end rather than a write on every press.

   What is left here is the other half of it: one line per category that is
   actually on, with what that category adds to every price and the way to
   change it. At most four of these, against nine cards of chips for the eight
   categories a shop does not sell. */
.catline {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 0;
  border-top: 1px solid var(--border);
}
.catline:last-of-type { border-bottom: 1px solid var(--border); }
.catline > b { font-size: 12.5px; min-width: 120px; }
.catline .fieldchips { margin-top: 0; flex: 1; }
.catline .btn { margin-left: auto; flex: none; }
.fieldchips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.fchip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 6px 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--card);
  font-size: 11.5px;
  color: var(--text-muted);
}
/* Dashed because these five cannot be taken off, not because they are less
   true than the rest. At --text-dim they measured 3.08:1 on the card, which is
   a fact somebody has to lean in to read: the dashed border is what says they
   are fixed, and the colour goes back to the one every other chip uses. */
.fchip.is-base { color: var(--text-muted); border-style: dashed; }
.fchip button {
  border: none; background: none; cursor: pointer;
  color: var(--text-dim); font-size: 13px; line-height: 1;
  padding: 0 2px; border-radius: 50%;
}
.fchip button:hover { color: var(--bad); background: var(--bad-soft); }
.chip.is-pick { cursor: pointer; border-color: var(--border-strong); }
.chip.is-pick:hover { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent); }

/* --- carrier chain -------------------------------------------------------- */
.legs { display: flex; flex-direction: column; margin-top: 10px; }
.leg {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: 10px;
  align-items: baseline;
  padding: 9px 0;
  border-top: 1px solid var(--border);
  font-size: 12.5px;
}
.leg .lrole {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-dim); padding-top: 2px;
}
.leg.is-last .lrole { color: var(--accent); }
.leg .lbody { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.leg .lwho { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.leg .lname { font-weight: 500; }
.leg .lwho .copyable { font-size: 11.5px; color: var(--text-muted); }
/* What the carrier said, which is a sentence and was being cut to fit a
   column. It wraps to two lines and stops there. */
.leg .lwhen {
  display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
  font-size: 11.5px; color: var(--text-muted); min-width: 0;
}
.leg.is-pending .lname, .leg.is-pending .lwhen { font-style: normal; }
@media (max-width: 760px) {
  .leg { grid-template-columns: 1fr; gap: 4px; }
}

/* --- inbox ---------------------------------------------------------------- */
.inboxgrid { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: 14px; align-items: start; }
/* The list follows the window the way the conversation beside it does. At a
   fixed 640px it stopped short on a big monitor while the pane next to it
   ran on to 900, which is the console's own queue ending early on the one
   screen with room for more of it. */
.thlist { display: flex; flex-direction: column; max-height: clamp(360px, 68vh, 900px); overflow-y: auto; }
.throw {
  display: flex; gap: 10px; align-items: flex-start;
  width: 100%;
  padding: 12px 14px;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background var(--fast);
}
.throw:hover { background: var(--surface-1); }
.throw.is-active { background: var(--surface-2); }
.throw.is-active::before {
  content: ''; position: absolute; left: 0; width: 2px; height: 34px; background: var(--accent);
}
.throw > .avat { margin-top: 1px; }
.throw .ttxt { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.throw .ttop { display: flex; align-items: baseline; gap: 8px; }
.throw .ttop b { font-size: 12.5px; }
/* The name wins the room. Flex shrinks both by their own width, so the
   longer of the two loses the most: a 340px list read "Lumiere…  ECS-…",
   with the one thing that identifies the row cut and a code nobody scans a
   chat list by cut beside it. The code gives up its width first. */
.throw .tmer { font-size: 11.5px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 6 auto; }
.throw .ttime { margin-left: auto; font-family: var(--font-mono); font-size: 9.5px; color: var(--text-dim); flex-shrink: 0; }
.throw .tprev { font-size: 12px; color: var(--text-dim); line-height: 1.45; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.throw .ttop b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; min-width: 0; }
/* A grid child is as wide as its content unless it is told otherwise, so a
   conversation with a long unbroken line in it pushed the card past the
   screen and took the page sideways with it. Measured at 390px. */
.inboxgrid > * { min-width: 0; }
@media (max-width: 900px) { .inboxgrid { grid-template-columns: 1fr; } }
/* On a phone the merchant number goes. The name, what is waiting and when are
   what the row is scanned for, and four things on that width wrapped the name
   over two lines and cut the number to ECS- anyway. */
@media (max-width: 720px) { .throw .tmer { display: none; } }

/* Which kind of conversation the rows under it are.

   The console holds two with the same merchant name on both, the open line and
   a question about a product. The tab bar answers that where somebody has
   chosen a kind; this is the divider inside the two views that hold both, and
   it carries no number, because the tab of the same name a few pixels above
   already does. */
.thgroup {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 12px 14px 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.thlist > .thgroup:first-child { padding-top: 8px; }
/* What the row is about, under the name. The product first, because that is
   what is being asked about, and what the quote is doing after it in the
   quiet face: one is the subject and the other is its state. */
.throw .tabout {
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: 11.5px;
  color: var(--text-muted);
  min-width: 0;
}
.throw .tabout { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.throw .tabout em {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  flex-shrink: 0;
}

/* The same thing on the conversation itself, with the one press that opens it.
   A question about a product arrives with the thing it is about on another
   screen, and remembering the name and going to find it in the queue is the
   whole of what this removes. */
.ibabout {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-1);
}
.ibabout .ibtxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.ibabout .ibtxt b { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ibabout .ibtxt span { font-size: 11.5px; color: var(--text-dim); }
.ibabout .btn { flex-shrink: 0; }

/* --- dispute cards -------------------------------------------------------- */
.dcard { cursor: pointer; transition: border-color var(--fast), background var(--fast); }
.dcard:hover { border-color: var(--border-strong); background: var(--card-hover); }
.drow { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 20px; }
.dmain .dtext { margin: 8px 0; font-size: 13px; color: var(--text-muted); line-height: 1.55; }
.dside { border-left: 1px solid var(--border); padding-left: 18px; }
.evgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 8px 0; }
.ev {
  display: flex; align-items: center; gap: 8px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  font-size: 11px;
}
.ev svg { color: var(--text-dim); flex-shrink: 0; }
/* A tile that opens the photo is a button, and has to feel like one. */
button.ev {
  width: 100%; text-align: left; color: inherit;
  font-family: inherit; font-size: 11px; cursor: pointer;
  transition: border-color var(--fast), background var(--fast);
}
button.ev:hover { border-color: var(--border-strong); background: var(--card-hover); }
@media (max-width: 900px) {
  .drow { grid-template-columns: 1fr; }
  .dside { border-left: none; border-top: 1px solid var(--border); padding: 12px 0 0; }
}
.proofbox {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 28px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--card);
  margin-bottom: 14px;
  text-align: center;
}
.proofbox svg { color: var(--text-dim); }
.proofbox b { font-family: var(--font-mono); font-size: 12px; }
.proofbox span { font-size: 11.5px; color: var(--text-dim); }

/* --- upload row ----------------------------------------------------------- */
.uploadrow { margin-bottom: 12px; }
.uploadrow input[type="file"] {
  width: 100%;
  padding: 10px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--text-muted);
  font-size: 12.5px;
}
.uploadrow input[type="file"]::file-selector-button {
  margin-right: 12px;
  padding: 6px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* --- console bits --------------------------------------------------------- */
.metric { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin-bottom: 6px; font-size: 12.5px; color: var(--text-muted); }
.metric b { font-size: 15px; color: var(--text); }
.qhead {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  margin-bottom: 12px;
}
.qhead .eyebrow { margin: 0 0 3px; }
/* Four columns of a fixed width, so anything in one of them has to give way
   rather than run into the next: a shop called by its myshopify domain sat on
   top of the variant list beside it. The name is cut with the whole of it on
   the title; a list of variants wraps, because a list is meant to be read to
   the end. */
.qhead > div { min-width: 0; }
.qhead b { font-size: 13px; display: block; min-width: 0; }
.qhead b.mono { overflow-wrap: anywhere; }
/* A flex item will not shrink below its own content unless it is told it may,
   which is why the name went on running into the column beside it with the
   ellipsis rule already on it: min-width is auto by default, and hiding the
   overflow of a box that never got narrower hides nothing. */
.qhead .mpill { display: flex; max-width: 100%; }
.qhead .mpill b, .qhead .mpill .mwho {
  min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shiptable input { height: 30px; padding: 0 8px; text-align: right; max-width: 120px; }
/* Under a column head, what the number in that column is. A grid of money
   with no unit on it is a grid somebody has to be told about once and then
   remember. */
.shiptable .thsub {
  display: block; font-size: 9px; letter-spacing: .06em;
  color: var(--text-dim); font-weight: 400; margin-top: 2px;
}
.cellwrap { position: relative; display: inline-block; }
.cellwrap input.is-sel { border-color: var(--accent-line); background: var(--accent-soft); }
.cellwrap input.is-filled { animation: fillflash 500ms var(--ease); }
@keyframes fillflash {
  from { background: var(--accent-soft); border-color: var(--accent); }
  to { background: var(--surface-1); border-color: var(--border); }
}
/* The currency on the box the number goes in, rather than in a sentence over
   the grid: a line saying every price is in euros is read once and then not
   again, and the box is where somebody is looking. */
.cellwrap.is-money input { padding-left: 20px; }
.cellcur {
  position: absolute; left: 7px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--text-dim); pointer-events: none;
}

/* A size to tick, with the thing itself on it. A colourway is what a factory
   cannot supply, and "Navy / 2XL" is not what anybody recognises it by. */
/* The picture is in the pill rather than beside it: a colourway is what a
   factory cannot supply, and a thumbnail sitting outside the border reads as
   a stray image rather than as part of the thing being ticked. It grows under
   the pointer, because 30px says which of two navies this is and 60px says
   what the seam looks like. */
.varpick.has-thumb span {
  display: inline-flex; align-items: center; gap: 8px;
  padding-left: 5px;
}
.varpick.has-thumb .pthumb {
  width: 30px; height: 30px; border-radius: 5px; flex: 0 0 auto;
  transition: transform var(--fast), box-shadow var(--fast);
  /* Clear of the pill rather than over it: the name beside it is what says
     which variant this is, and a preview that covers the name is a preview
     of nothing. The lift is in the pill's own units, before the scale, so
     the square ends up entirely above the row it came out of. */
  transform-origin: left bottom;
}
.varpick.has-thumb .pthumb svg { width: 18px; height: 18px; }
.varpick.has-thumb:hover { z-index: 3; }
.varpick.has-thumb:hover .pthumb {
  transform: scale(2.4) translateY(-17px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .45);
}
@media (prefers-reduced-motion: reduce) {
  .varpick.has-thumb .pthumb { transition: none; }
}

/* A section on the price form, said once and plainly. It was an eyebrow with
   a paragraph under it explaining what the section was for, to a floor that
   is onboarded once and then works this form all day. */
.qsection {
  font-size: 15px; font-weight: 600; letter-spacing: -.01em;
  margin: 18px 0 10px; color: var(--text);
}

/* Only there once somebody has said the requested thing cannot be made. */
.altoffer {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 14px; margin: 12px 0; background: var(--surface-1);
}
.altoffer .field:last-child { margin-bottom: 0; }

/* What the merchant wrote, five lines of it, with the rest behind a press.
   These run to a screenful and more, above the grid somebody opened the form
   to fill in. The press is only drawn where something is actually hidden,
   which is measured after the card is on the screen. */
/* Cut by height rather than by `-webkit-line-clamp`, which clamps the inline
   content of one box and quietly does nothing to a note made of a paragraph
   and a list, which is what these are. Five lines of the card's own line
   height, plus the padding it draws, and the last of them fades so the cut
   reads as more text rather than as a sentence that stops. */
.notewrap.is-clamped .notecard {
  max-height: calc(5 * 1.65em + 26px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent);
}
.notemore { margin-top: 6px; }

.fillgrip {
  position: absolute;
  right: -3px; bottom: -3px;
  width: 8px; height: 8px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 0;
  cursor: crosshair;
  transition: opacity var(--fast);
}
.cellwrap:hover .fillgrip,
.cellwrap:focus-within .fillgrip { opacity: 1; }
.shiptable td { padding: 6px 10px; }
@media (max-width: 720px) { .qhead { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* --- definition list for product details ---------------------------------- */
.deflist { display: flex; flex-direction: column; }
.defrow {
  display: grid; grid-template-columns: 190px minmax(0, 1fr);
  gap: 14px;
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
}
.defrow:last-child { border-bottom: none; }
.defrow .dt { font-family: var(--font-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); padding-top: 2px; }
.defrow .dd { color: var(--text); line-height: 1.55; }
/* The warehouse's reason beside a detail they could not supply. Under the pill
   rather than after it, because a reason is a sentence and a pill is two
   words: on one line the sentence pushes the value column into the label. */
.defrow .dwhy { display: block; margin-top: 3px; font-size: 11.5px; line-height: 1.45; color: var(--text-dim); }
@media (max-width: 720px) { .defrow { grid-template-columns: 1fr; gap: 4px; } }

.addr { font-size: 13px; color: var(--text-muted); line-height: 1.65; margin: 6px 0 12px; }
/* The address and the map beside it. The map takes what is left rather than a
   share, because the address is a fixed number of short lines and everything
   past it was empty. Under that width the two stack and the map goes first,
   since on a phone it is the half that says where at a glance. */
.shipsplit { display: flex; flex-wrap: wrap; gap: 16px 18px; align-items: flex-start; }
/* Wrapped rather than a media query. What decides whether these two fit side
   by side is the width of the column they are in, and that column is a
   fraction of the page: a breakpoint on the window would put them side by
   side on a narrow card and stack them on a wide one. */
/* The map with the address under it, which is how an address is drawn
   everywhere somebody has seen one before. */
.shipwhere { flex: 2 1 13rem; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.shipwhere .addr { margin: 0; }
.shipmap { min-width: 0; }
/* The two things that are not the address. Label over value rather than a
   row: in a column this narrow a right aligned value leaves a tracking
   number three characters of room, and these are looked up rather than
   read. */
.shipfacts { flex: 1 1 9rem; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.shipfacts .sf { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* The label by its own class rather than by being the first span. `.sftrack`
   is a span in the same place, and as `> span` it was taking the label's
   uppercase with it: a carrier called Tongyou printed TONGYOU. */
.shipfacts .sfl {
  font-size: 10px; letter-spacing: .07em; text-transform: uppercase; color: var(--text-dim);
}
.shipfacts .sf > b { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
.shipfacts .sf > b.dim { font-weight: 400; }
.shipfacts .copyable { font-size: 12px; max-width: 100%; }
.shipfacts .copyable .mono { overflow-wrap: anywhere; }
/* One parcel per line, with whose number it is under it. */
.sftrack { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sftrack + .sftrack { margin-top: 7px; }
.sftrack em { font-style: normal; font-size: 10.5px; color: var(--text-dim); }
.crumb a:hover { color: var(--accent); }

/* --- attention list ------------------------------------------------------- */
.attlist { display: flex; flex-direction: column; }
.attrow {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  padding: 13px 16px;
  border: none;
  border-bottom: 1px solid var(--border);
  background: none;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: background var(--fast);
}
.attrow:last-child { border-bottom: none; }
.attrow:hover { background: var(--surface-1); }
.attrow .aico {
  width: 30px; height: 30px; flex-shrink: 0;
  border-radius: 9px;
  display: grid; place-items: center;
  background: var(--warn-soft); color: var(--warn);
}
.attrow .atxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.attrow .atxt b { font-size: 13px; font-weight: 500; }
.attrow .atxt span { font-size: 12px; color: var(--text-dim); }
.attrow .acount {
  margin-left: auto;
  font-family: var(--font-mono); font-size: 12px; font-weight: 600;
  color: var(--text);
  background: var(--surface-2);
  padding: 3px 9px;
  border-radius: var(--radius-pill);
}
.attrow svg:last-child { width: 14px; height: 14px; color: var(--text-dim); flex-shrink: 0; }

.bigmoney {
  font-size: 30px; font-weight: 700; letter-spacing: -.03em;
  margin: 4px 0 12px;
}
.bigmoney.is-bad { color: var(--bad); }
.btn.block { width: 100%; }

/* --- flow bits: mini order list, pick grids, comparison -------------------- */
.ordermini { display: flex; flex-direction: column; margin-bottom: 12px; }
.omrow {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
}
.omrow:last-child { border-bottom: none; }
.omrow .muted { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.omrow b { margin-left: auto; color: var(--text); }
.omrow.dim { color: var(--text-dim); }
/* One line, cut rather than wrapped. A cell that wraps changes the height of
   its row, and a table whose rows are all different heights is one nobody can
   scan: the console works three hundred parcels in one list. */
.ell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.linerow b.is-short { color: var(--bad); }

/* A line that does not match is the one worth looking at, so it is the only
   one that carries any colour. The rest of the comparison stays quiet. */
tr.is-off td { background: var(--bad-soft); }
td.is-short, .num.is-short { color: var(--bad); }
/* Money arriving on a balance, on the log the console watches. Green because
   it is the only good news in that column and the eye has to tell the two
   apart at a glance; red is already what leaves. */
.lgamt.is-short { color: var(--bad); }
.lgamt.is-in { color: var(--good); }

/* A chip that opens the file is a button, and has to say so. */
button.filechip { cursor: pointer; font-family: inherit; }
button.filechip:hover { border-color: var(--border-strong); color: var(--text); }

/* what was quoted next to what was measured */
.qctable td { vertical-align: middle; }
.qctable input[type="text"] {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
  font: inherit;
  font-size: 12.5px;
}
.qctable input[type="text"]:focus { outline: none; border-color: var(--border-focus); }

/* the ladder as the merchant reads it: a step per quantity */
.tierrow { display: flex; gap: 8px; flex-wrap: wrap; }
.tiercell {
  display: flex; align-items: baseline; gap: 7px;
  padding: 9px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card-hover);
}
.tiercell .tq { font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
.tiercell b { font-size: 14px; }
.tiercell .tu { font-size: 11px; color: var(--text-dim); }

/* the volume ladder, three short steps side by side */
.tiergrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
/* A step is two answers now, how many pieces and what they cost, so each one
   is a pair rather than a single box with the quantity written into its
   label. */
.tierstep { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 8px; align-items: end; }
@media (max-width: 640px) { .tiergrid { grid-template-columns: 1fr; } }

/* The merchant chips above the console inbox are gone, and their stylesheet
   with them: they were a filter over a list that is already one row per
   merchant, and pressing one hid every conversation but that one. */

/* the same count on the thread it belongs to */
/* How many messages are waiting on a reply, and it says so in as many words:
   a bare number here was read as unread, and unread is not what it counts. */
.tmiss {
  font-family: var(--font-mono); font-size: 9.5px;
  letter-spacing: .04em; white-space: nowrap;
  padding: 1px 7px; border-radius: var(--radius-pill);
  background: var(--accent-fill); color: #fff;
}

/* --- waiting on a reply ---------------------------------------------------- */
/* Not a banner: a banner tells you something, this hands you the four people
   who are waiting and how long they have been. */
/* Not a banner, and built like one anyway: this had the whole recipe copied
   out by hand, so it drifted the moment the banner changed. Same glass, same
   radius, its own accent rim. */
.pingbar {
  --bt: var(--accent);
  padding: 13px 15px 15px;
  margin-bottom: 20px;
  border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
  background:
    color-mix(in srgb, var(--bt) 7%, transparent),
    color-mix(in srgb, var(--surface-2) 94%, transparent);
  backdrop-filter: blur(20px) saturate(170%);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
  box-shadow: inset 0 1px 0 var(--glass-lip), var(--glass-shadow);
}
.pinghead { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.pinghead b { font-size: 13.5px; letter-spacing: -.012em; }
.pinghead .hint { margin: 0; }
.pinghead .btn { margin-left: auto; }
.pingdot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--accent);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent);
  animation: ping 2s var(--ease) infinite;
}
@keyframes ping {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  70% { box-shadow: 0 0 0 9px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.pingrow { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 8px; margin-top: 12px; }
.pingcard {
  display: flex; flex-direction: column; gap: 3px;
  padding: 11px 13px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--text) 3%, transparent);
  color: var(--text);
  font: inherit; text-align: left; cursor: pointer;
  min-width: 0;
  transition: background var(--fast), border-color var(--fast), transform var(--fast);
}
.pingcard:hover { background: color-mix(in srgb, var(--text) 6%, transparent); border-color: var(--border-strong); transform: translateY(-2px); }
.pingcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pctop { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; font-weight: 600; }
.pcage { margin-left: auto; font-family: var(--font-mono); font-size: 9.5px; color: var(--accent); white-space: nowrap; }
.pcbody { font-size: 12px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pckind { font-family: var(--font-mono); font-size: 9px; letter-spacing: .11em; text-transform: uppercase; color: var(--text-dim); }
@media (prefers-reduced-motion: reduce) { .pingdot { animation: none; } }
@media (max-width: 900px) { .pingrow { grid-template-columns: 1fr; } }

/* --- the work board -------------------------------------------------------- */
/* The console opens on what is waiting, and a count on its own is a number to
   be believed rather than work to be recognised. So every card carries the
   things themselves: the products waiting for a price, the parcels waiting to
   be picked. A face on the pile is what makes somebody press it, and pressing
   one of the faces opens that one rather than the queue behind it. */
/* Five fixed columns rather than auto-fit: a queue that wraps on its own
   leaves one card alone on a second row, which reads as the odd one out
   rather than as the fifth of five. */
/* Six queues, and six divides into three and into two, so a narrower
   screen never leaves one card alone on a row reading as the odd one
   out. That is what the five was protecting, rather than the five. */
.workboard { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; margin-bottom: 20px; }
@media (max-width: 1240px) { .workboard { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.workcard {
  display: flex; flex-direction: column; gap: 12px;
  padding: 15px 16px 14px;
  border-radius: 19px;
  border: 1px solid var(--border);
  background: linear-gradient(180deg, var(--card-hover), var(--card));
  color: var(--text); font: inherit; text-align: left; cursor: pointer;
  min-width: 0;
  transition: border-color var(--fast), transform var(--fast), box-shadow var(--fast);
}
.workcard:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--glass-shadow); }
.workcard:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Nothing waiting is a state worth seeing at a glance rather than reading. */
.workcard.is-clear { background: var(--card); }
.workcard.is-clear .wc-n { color: var(--text-dim); }
.workcard.is-late { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }

.wc-top { display: flex; align-items: flex-start; gap: 10px; }
.wc-icon {
  width: 30px; height: 30px; flex: none; border-radius: 9px;
  display: grid; place-items: center;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--text) 4%, transparent);
  color: var(--text-muted);
}
.wc-icon svg { width: 15px; height: 15px; }
.workcard.is-late .wc-icon { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 34%, transparent); }
.wc-n { font-size: 30px; font-weight: 600; letter-spacing: -.035em; line-height: 1; margin-left: auto; }
.wc-label { font-size: 12.5px; font-weight: 600; letter-spacing: -.012em; }
.wc-sub { font-size: 11.5px; color: var(--text-muted); margin-top: 2px; }

.wc-thumbs { display: flex; align-items: center; gap: 6px; min-height: 42px; overflow: hidden; }
.wc-thumb {
  display: block; padding: 0; border: 0; background: none; cursor: pointer; border-radius: 11px;
  transition: transform var(--fast);
}
.wc-thumb:hover { transform: translateY(-2px); }
.wc-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.wc-thumb .pthumb { width: 40px; height: 40px; border-radius: 11px; }
.wc-thumb .pthumb svg { width: 28px; height: 28px; }
.wc-more {
  width: 40px; height: 40px; flex: none; border-radius: 11px;
  display: grid; place-items: center;
  border: 1px dashed var(--border-strong);
  font-family: var(--font-mono); font-size: 11px; color: var(--text-dim);
}
.wc-none { font-size: 11.5px; color: var(--text-dim); align-self: center; }

.wc-foot { display: flex; align-items: center; gap: 8px; }
.wc-age { font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em; color: var(--text-dim); }
.workcard.is-late .wc-age { color: var(--accent); }
/* Six days or more on the oldest thing in the queue, on the same ladder the
   orders list works to. Amber rather than the accent: the accent is for a
   promise that has actually been missed, which is what `is-late` says, and two
   reds beside each other is neither of them standing out. */
.wc-age.is-heavy { color: var(--warn-ink); }
.workcard.is-late .wc-age.is-heavy { color: var(--accent); }
.wc-go { margin-left: auto; font-size: 11.5px; color: var(--text-muted); }
.workcard:hover .wc-go { color: var(--text); }
/* Two cards to a phone screen, and a card that narrow holds two faces and the
   count of the rest. A third would be cut in half by the edge, which is worse
   than not drawing it. */
@media (max-width: 720px) {
  .workboard { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .wc-n { font-size: 24px; }
  .wc-thumbs:has(.wc-more) .wc-thumb:nth-of-type(3) { display: none; }
}

/* --- the fulfil sheet ------------------------------------------------------ */
/* What it costs and what it leaves, said once, before any of the detail. */
.payhead {
  padding: 16px 16px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--card-hover), var(--card));
  margin-bottom: 12px;
}
.payhead .ph-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.payhead .ph-num { font-size: 30px; font-weight: 600; letter-spacing: -.035em; line-height: 1.1; margin-top: 6px; }
.payhead .ph-count {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-dim); padding: 4px 10px; border: 1px solid var(--border); border-radius: var(--radius-pill);
}
.paybar { display: flex; height: 10px; margin-top: 16px; border-radius: 6px; overflow: hidden; background: var(--surface-1); }
.paybar > div { min-width: 3px; }
.pb-spent { background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 70%, #000)); }
.pb-left { background: color-mix(in srgb, var(--good) 42%, transparent); }
.pb-missing {
  background:
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--bad) 55%, transparent) 0 2px, transparent 2px 7px),
    color-mix(in srgb, var(--bad) 14%, transparent);
}
.pb-legend { display: flex; gap: 18px; flex-wrap: wrap; margin-top: 11px; font-size: 12px; color: var(--text-muted); }
.pb-legend span { display: flex; align-items: center; gap: 7px; }
.pb-legend i { width: 9px; height: 9px; border-radius: 3px; display: block; }
.pb-legend i.spent { background: var(--accent); }
.pb-legend i.left { background: color-mix(in srgb, var(--good) 42%, transparent); }
.pb-legend i.missing { background: color-mix(in srgb, var(--bad) 55%, transparent); }

/* the split of the charge, kept apart from the list it came from */
.receipt { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.receipt > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 10px 14px;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
}
.receipt > div:last-child { border-bottom: 0; }
.receipt span { color: var(--text-muted); }
.receipt b { font-variant-numeric: tabular-nums; }
.receipt .is-total { background: color-mix(in srgb, var(--text) 3%, transparent); }
.receipt .is-total span { color: var(--text); font-weight: 500; }
.receipt .is-total b { font-size: 15px; }

/* every order that is about to leave, with the thing itself beside it */
/* a list longer than the sheet scrolls, and says so by fading out */
.ordwrap { position: relative; }
.ordwrap.is-more::after {
  content: ""; position: absolute; left: 1px; right: 1px; bottom: 1px; height: 42px;
  border-radius: 0 0 var(--radius) var(--radius);
  background: linear-gradient(180deg, transparent, var(--surface-1));
  pointer-events: none;
}
.ordlist { max-height: 236px; overflow: auto; border: 1px solid var(--border); border-radius: var(--radius); }
.ordrow {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
}
.ordrow:last-child { border-bottom: 0; }
.ordrow .or-txt { min-width: 0; flex: 1; }
.ordrow .or-who { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ordrow .or-meta { font-size: 11px; color: var(--text-dim); margin-top: 1px; }
.ordrow .or-amt { font-variant-numeric: tabular-nums; font-size: 13px; }
.ordlist .pthumb { width: 30px; height: 30px; border-radius: 8px; }
.ordlist .pthumb svg { width: 21px; height: 21px; }

/* Two states of one product, side by side. The card stays neutral: the amber
   belongs on the field that moved, not on the whole thing. */
.cmprow {
  padding: 12px 13px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--card-hover), var(--card));
  margin-bottom: 8px;
}
.cmprow .cmphead { display: flex; align-items: center; gap: 11px; }
.cmprow .cmptitle { font-size: 13px; font-weight: 600; }
.cmprow .cmpsub { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); letter-spacing: .08em; margin-top: 2px; }
.cmprow .cmphead .vpill { margin-left: auto; }


/* --- a setting worth saying out loud ---------------------------------------- */
/* Not a banner: nothing is wrong. One line, on the merchant's own screen,
   saying that orders go out without them and what the ceiling is. */
.autoline {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--good) 6%, transparent);
}
.autoline b { font-size: 12.5px; letter-spacing: -.01em; }
.autoline .hint { margin: 0; }
.autoline .btn { margin-left: auto; }
@media (max-width: 720px) { .autoline .btn { margin-left: 0; } }

/* --- the split bar --------------------------------------------------------- */
/* One bar, cut into what it is made of, with the legend under it. The tones
   are the ones the chips already use for these states, so a parcel is the
   same colour on the bar as it is on its own row, and red is only ever the
   part somebody has to do something about. */
.viz-split { display: block; }
.vsp-bar {
  display: flex; gap: 3px; height: 14px;
  border-radius: var(--radius-pill); overflow: hidden;
}
.vsp-bar i {
  /* A floor, because the part somebody has to act on is usually the smallest
     one: two exceptions against two hundred deliveries is a hairline nobody
     sees, and the whole point of drawing it is that it is seen. The distortion
     is a few pixels on a bar a thousand wide, and the legend carries the exact
     number beside every part anyway. */
  display: block; height: 100%; min-width: 10px;
  border-radius: 3px;
  background: var(--vc, var(--text-dim));
  transform-origin: left center;
  animation: vspgrow .6s var(--ease) both;
}
.vsp-bar i[data-href] { cursor: pointer; }
.vsp-bar i[data-href]:hover { filter: brightness(1.25); }
@keyframes vspgrow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) { .vsp-bar i { animation: none; } }

.vsp-bar i.is-good, .vsp-dot.is-good { --vc: var(--good); }
.vsp-bar i.is-accent, .vsp-dot.is-accent { --vc: var(--accent); }
/* Out for delivery is the good news at the end of the journey, not an alarm.
   Red on this bar belongs to the one part somebody has to act on, and two
   reds beside each other is neither of them standing out. */
.vsp-bar i.is-info, .vsp-dot.is-info { --vc: var(--info); }
.vsp-bar i.is-warn, .vsp-dot.is-warn { --vc: var(--warn); }
.vsp-bar i.is-bad, .vsp-dot.is-bad { --vc: var(--bad); }
.vsp-bar i.is-neutral, .vsp-dot.is-neutral { --vc: color-mix(in srgb, var(--text) 34%, transparent); }
.vsp-bar i.is-none { --vc: color-mix(in srgb, var(--text) 8%, transparent); animation: none; }

.vsp-legend { display: flex; flex-wrap: wrap; gap: 6px 20px; margin-top: 14px; }
.vsp-item {
  display: flex; align-items: baseline; gap: 7px;
  color: var(--text); text-decoration: none;
  padding: 2px 0;
}
a.vsp-item:hover .vsp-l { color: var(--text); }
a.vsp-item:hover b { text-decoration: underline; text-underline-offset: 3px; }
.vsp-item.is-zero { opacity: .45; }
.vsp-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--vc, var(--text-dim)); align-self: center; }
.vsp-item b { font-size: 15px; font-weight: 600; letter-spacing: -.02em; }
.vsp-l { font-size: 12px; color: var(--text-muted); }
.vsp-p { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); }
.vsp-none { font-size: 12px; color: var(--text-dim); }
@media (max-width: 720px) { .vsp-legend { gap: 6px 16px; } }

.cmprow .cmpcols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 28px minmax(0, 1fr);
  align-items: center;
  gap: 0;
  margin-top: 11px;
}
.cmprow .cmpside {
  display: flex; flex-direction: column; gap: 2px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--text) 3%, transparent);
  min-width: 0;
}
.cmprow .cmpside.is-now {
  border-color: color-mix(in srgb, var(--warn) 30%, transparent);
  background: color-mix(in srgb, var(--warn) 7%, transparent);
}
.cmprow .cmpside .dim { font-family: var(--font-mono); font-size: 9px; letter-spacing: .11em; text-transform: uppercase; }
.cmprow .cmpside b { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; }
.cmprow .cmpside.is-now b { color: var(--warn); }
/* the arrow is drawn rather than typed, so it lines up with the two panes */
.cmprow .cmparrow { position: relative; height: 1px; background: var(--border-strong); }
.cmprow .cmparrow::after {
  content: ""; position: absolute; right: 0; top: -3px;
  width: 6px; height: 6px;
  border-top: 1px solid var(--border-strong);
  border-right: 1px solid var(--border-strong);
  transform: rotate(45deg);
}
@media (max-width: 640px) {
  .cmprow .cmpcols { grid-template-columns: 1fr; gap: 8px; }
  .cmprow .cmparrow { display: none; }
}

.pickgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.pickgrid.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pick {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  font-size: 12.5px;
  cursor: pointer;
  transition: border-color var(--fast), background var(--fast);
}
.pick:hover { border-color: var(--border-strong); }
.pick.is-on { border-color: var(--accent-line); background: var(--accent-soft); }
/* A choice this account cannot make yet: drawn, so it is known to exist, and
   not pressable. */
.pick.is-off { opacity: .55; cursor: not-allowed; }
/* Picking a country is not an action, it is an answer, and red is the colour
   this product spends on the one thing worth doing something about. Green
   says chosen without asking to be looked at, and the ones left grey stay
   plainly unchosen rather than merely quieter. */
.pickgrid.is-choice .pick.is-on {
  border-color: var(--picked-line);
  background: var(--picked-soft);
}
.pickgrid.is-choice .pick.is-on input { accent-color: var(--picked); }
.pick input { accent-color: var(--accent); margin-top: 1px; }
.pick span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pick b { font-weight: 600; }
.pick em { font-style: normal; font-size: 11.5px; color: var(--text-dim); line-height: 1.45; }
/* The listing, on the screen that prices it. A row that scrolls rather than a
   grid that wraps: how many pictures a merchant put on a product is their
   business, and twelve of them must not push the price grid off the screen on
   the one form somebody works all day. */
/* What the two answers are promised in, as two marks rather than a paragraph.
   Toned rather than neutral, because the pair is a comparison: the fast one is
   what ticking nothing gets you and the slower one is what each box costs, and
   a reader who takes only the colour has still taken the point. The ink is the
   theme-aware pair rather than the raw tone, on the rule that the ground
   decides the ink. */
.promise { display: flex; flex-wrap: wrap; gap: 6px; margin: -2px 0 10px; }
.promise .ptag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 12px; line-height: 1.3;
  white-space: nowrap;
}
.promise .ptag b { font-weight: 600; }
/* The line and the word, not a fill. Measured both ways in both themes: on
   its own tint the light green reads 4.21 against it, under AA, which is the
   failure this build already met once on the menu badge. The ink is tuned
   against the card, so the card is what it sits on, and the tone is carried
   by the border and the word. */
.promise .ptag.is-good { border-color: var(--good-soft); color: var(--good-ink); }
.promise .ptag.is-warn { border-color: var(--warn-soft); color: var(--warn-ink); }
.shots { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.shot {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 4px;
  width: 84px; text-decoration: none; color: var(--text-muted);
}
.shot img {
  width: 84px; height: 84px; object-fit: cover;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-1);
}
.shot:hover img { border-color: var(--border-strong); }
/* One picture standing for the set. Bigger than a strip tile, because it is
   now the only thing saying what the object is, and the count sits on it
   rather than beside it: a number in the corner of a photograph is read as
   "there are more of these", and the same number as a label beside it is
   read as a caption. */
/* A row of what the merchant is selling, as wide as the panel. Only what fits
   on one line is drawn, and the last of those carries how many are behind it,
   so the strip is always one line whatever the width. */
.shots.is-row { flex-wrap: wrap; gap: 8px; }
.shots.is-row .shot {
  position: relative;
  width: 108px; height: 108px;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.shots.is-row .shot img {
  width: 108px; height: 108px;
  transition: transform var(--fast), border-color var(--fast);
}
.shots.is-row .shot .shotmore {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, .52);
  color: #fff;
  font-family: var(--font-mono); font-size: 15px; letter-spacing: .02em;
  transition: background var(--fast);
}
.shots.is-row .shot:hover .shotmore { background: rgba(0, 0, 0, .38); }
.shots.is-row .shot:focus-visible img { border-color: var(--accent); }
.chiprow { display: flex; gap: 6px; flex-wrap: wrap; }

/* What the merchant wrote, on the form that answers them. It was a grey line
   under the photographs, which is where a special request goes unread until
   the price has already been typed. */
.notecard { padding: 12px 14px; margin-top: 6px; border-left: 2px solid var(--accent); }
.notecard .ctext { margin: 0; }

/* Which sizes a re-quote is actually about.

   A product comes back when the merchant adds a colour or Shopify moves a size
   underneath a price, and the sheet in front of the warehouse is then mostly
   the one they filled in last time. Naming the rows that are new is the
   difference between reading thirty sizes and reading two.

   One line that wraps rather than a table: it is a set of names, and the
   marked ones have to stand out of the rest rather than be found by scanning a
   column. A priced one is quiet on purpose, because it is the background. */
.sizetags { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
.sizetag {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 10px; font-size: 12px; color: var(--text-muted);
}
.sizetag.is-priced { opacity: .55; }
.sizetag.is-new { border-color: color-mix(in srgb, var(--good) 40%, transparent); color: var(--text); }
.sizetag.is-changed { border-color: color-mix(in srgb, var(--warn) 45%, transparent); color: var(--text); }
.badge.sm { font-size: 9.5px; padding: 1px 6px; }

/* Which colour a photograph is of. Their quote sheet carries one per
   colourway and every file used to land in a heap on the first variant, so a
   merchant looking at the navy read the picture of the black one. One row per
   file, and the name is cut rather than wrapped: a list where every row is a
   different height is a list the eye cannot walk. */
.fvlist { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.fvrow { display: flex; align-items: center; gap: 10px; }
.fvrow .fvname { flex: 1; min-width: 0; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-muted); }
.fvrow select { flex: 0 0 auto; max-width: 46%; }

/* A detail that is rows and columns. `size_table` was type `file`, so a size
   chart reached the merchant as a photograph: nothing they could copy a
   measurement out of, translate, or put in their own listing. It is a table
   now, on both sides, and it gets the width of the card rather than the value
   column of a definition row.

   The first column is the size and the rest are numbers, which is what every
   size chart is, so the first is left and the rest are right and monospaced:
   a column of figures that do not line up is a column nobody can compare. */
.dtblock { padding: 12px 16px 16px; border-top: 1px solid var(--border); }
.dtblock .eyebrow { margin-bottom: 8px; }
.dtable { width: 100%; }
.dtable th, .dtable td { padding: 6px 10px; }
.dtable th:first-child, .dtable td:first-child { padding-left: 0; }

/* The preview under the paste box on the price form. It is the point of that
   control rather than decoration: a column that landed in the wrong place is
   invisible in a textarea and obvious in a table. */
.tprev { margin-top: 8px; }
.tprev:empty { display: none; }

/* Adding a detail nobody asked for: a category, and what is left in it. */
.addfields { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.addrow { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin: 6px 0 8px; }
.addrow .field { margin: 0; min-width: 200px; }
.addrow .chiprow { flex: 1; min-width: 220px; }
.cmpgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
@media (max-width: 720px) {
  .pickgrid, .pickgrid.two, .cmpgrid { grid-template-columns: 1fr; }
}


/* a parcel's line sits between its header and its carriers */
.parcel .prail { margin: 18px 2px 4px; }

/* --- instruments ---------------------------------------------------------- */
/* Charts share one ink: the series is drawn in the text colour at low opacity
   and red is kept for the one mark that is worth looking at. */
.viz-spark, .viz-area { color: var(--text); }
/* The hard guarantee, whatever the numbers turn out to be. An instrument
   drawing outside its own box is the one failure that makes a whole screen
   look broken, and no amount of care about scales is a substitute for the
   box actually holding. */
.viz-spark, .viz-area, .viz-dial { overflow: hidden; }
/* And the width has to come from the card rather than from the viewBox. An
   svg with a viewBox, a set height and no width takes its width from the
   aspect ratio, which is a fixed number of pixels: wider than its card it
   spills over the one beside it, narrower it stops short of the edge. */
.viz-spark, .viz-area { display: block; width: 100%; }
.vline { stroke: color-mix(in srgb, currentColor 72%, transparent); }
.vline.is-accent { stroke: var(--accent); }
.vdot { fill: currentColor; }
.vdot.is-faint { fill: color-mix(in srgb, currentColor 34%, transparent); }
.vdot.is-accent { fill: var(--accent); }
.vdot.is-peak { fill: var(--text); stroke: var(--accent); stroke-width: 1.5; }
.vring { fill: none; stroke: color-mix(in srgb, var(--accent) 48%, transparent); stroke-width: 1; }
.vhatch { stroke: color-mix(in srgb, currentColor 16%, transparent); }
.vgrid { stroke: color-mix(in srgb, currentColor 7%, transparent); stroke-width: 1; }
.vaxis { fill: var(--text-dim); font-size: 10.5px; font-family: var(--font-display); }

/* figure with its own shape behind it */
.viz-tile {
  display: flex; flex-direction: column;
  padding: 16px 16px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--card-hover), var(--card));
}
.viz-tile.is-link { cursor: pointer; transition: border-color var(--fast), background var(--fast); }
.viz-tile.is-link:hover { border-color: var(--border-strong); }

.vt-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.vt-label { font-family: var(--font-mono); font-size: 10px; letter-spacing: .13em; text-transform: uppercase; color: var(--text-dim); }
.vt-num { font-size: 30px; font-weight: 600; letter-spacing: -.035em; line-height: 1.08; margin-top: 10px; }
.vt-unit { font-size: 14px; font-weight: 400; color: var(--text-muted); letter-spacing: 0; margin-left: 5px; }
.vt-spark { margin-top: 12px; }
.vt-foot { display: flex; justify-content: space-between; gap: 10px; margin-top: 10px; font-size: 11.5px; color: var(--text-muted); }
.vt-foot span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vt-foot span:last-child { color: var(--text-dim); flex: none; }
.vt-num { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vpill {
  font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: var(--radius-pill);
  border: 1px solid color-mix(in srgb, var(--vc, var(--text-muted)) 24%, transparent);
  background: color-mix(in srgb, var(--vc, var(--text-muted)) 12%, transparent);
  /* The mark is the tone and the word is the ink, which are the same colour
     in the dark theme and not on white. */
  color: var(--vc-ink, var(--vc, var(--text-muted)));
  white-space: nowrap;
}
.vpill.up { --vc: var(--good); --vc-ink: var(--good-ink); }
.vpill.down { --vc: var(--bad); --vc-ink: var(--bad-ink); }
.vpill.warn { --vc: var(--warn); --vc-ink: var(--warn-ink); }
.vpill.hot { --vc: var(--accent); --vc-ink: var(--accent-ink-on-soft); }

/* period chart */
.viz-plot { position: relative; }
.viz-note {
  position: absolute;
  display: flex; flex-direction: column; gap: 1px;
  padding: 8px 11px;
  border-radius: 13px;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  background: color-mix(in srgb, var(--card) 74%, transparent);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: inset 0 1px 0 var(--glass-lip), 0 10px 26px -16px #000;
  pointer-events: none;
}
.viz-note b { font-size: 14.5px; font-weight: 600; letter-spacing: -.02em; }
.viz-note span { font-size: 10.5px; color: var(--text-dim); }

/* Reading one day off the chart. The peak note is marked without being
   asked; this is the answer to a question somebody put, so it only exists
   while they are asking it. */
.viz-plot { position: relative; }
.viz-lens { opacity: 0; transition: opacity .12s var(--ease); pointer-events: none; }
.viz-plot.is-reading .viz-lens { opacity: 1; }
.vrule { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 2 4; }
.vdot.is-live { fill: var(--accent); stroke: var(--card); stroke-width: 2; }

.viz-read {
  position: absolute; opacity: 0;
  display: flex; flex-direction: column; gap: 1px;
  padding: 7px 10px;
  border-radius: 12px;
  border: 1px solid var(--border-strong);
  background: color-mix(in srgb, var(--card) 82%, transparent);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 10px 26px -16px #000;
  pointer-events: none; white-space: nowrap;
  transition: opacity .12s var(--ease), left .1s var(--ease), top .1s var(--ease);
}
.viz-plot.is-reading .viz-read { opacity: 1; }
.viz-read b { font-size: 13.5px; font-weight: 600; letter-spacing: -.02em; }
.viz-read span { font-size: 10px; color: var(--text-dim); font-family: var(--font-mono); }
/* While a day is being read, the note about the best one is in the way. */
.viz-plot.is-reading .viz-note { opacity: 0; }
.viz-note { transition: opacity .12s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .viz-lens, .viz-read, .viz-note { transition: none; }
}

/* pipeline */
.viz-flow { display: flex; gap: 4px; }
.viz-step {
  display: flex; flex-direction: column; gap: 2px;
  position: relative; min-width: 0;
  padding: 13px 13px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  font: inherit; text-align: left; cursor: pointer;
  transition: background var(--fast), border-color var(--fast), transform var(--fast);
}
.viz-step:disabled { cursor: default; }
.viz-step:not(:disabled):hover { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-2px); }
.viz-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.viz-step::before {
  content: ""; position: absolute; left: 13px; right: 13px; top: 0; height: 2px;
  border-radius: 2px; background: color-mix(in srgb, var(--text) 16%, transparent);
}
.viz-step .vs-n { font-size: 21px; font-weight: 600; letter-spacing: -.03em; }
/* Two lines of room whether the label needs them or not. One step whose name
   wraps made its own tile taller and pushed its amount below the others, so a
   row read across at a glance had one figure out of line for no reason the
   eye could explain. */
.viz-step .vs-l {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-dim); line-height: 1.3;
  min-height: 2.6em;
}
.viz-step .vs-v { font-size: 11px; color: var(--text-dim); margin-top: 3px; }
.viz-step.tone-bad { background: color-mix(in srgb, var(--accent) 9%, var(--card)); border-color: color-mix(in srgb, var(--accent) 26%, transparent); }
.viz-step.tone-bad::before { background: var(--accent); }
.viz-step.tone-warn::before { background: var(--warn); }
.viz-step.tone-good::before { background: var(--good); }
.viz-step.is-active { border-color: var(--accent); }

/* runway */
.viz-runway { display: flex; height: 24px; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }
.vr-fill { background: linear-gradient(180deg, color-mix(in srgb, var(--good) 62%, transparent), color-mix(in srgb, var(--good) 34%, transparent)); }
.vr-short {
  background:
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--accent) 52%, transparent) 0 2px, transparent 2px 7px),
    color-mix(in srgb, var(--accent) 12%, transparent);
}
.viz-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 11px; font-size: 12px; color: var(--text-muted); }
.viz-legend span { display: flex; align-items: center; gap: 7px; }
.viz-legend i { width: 9px; height: 9px; border-radius: 3px; display: block; }
.viz-legend i.ok { background: color-mix(in srgb, var(--good) 62%, transparent); }
.viz-legend i.bad { background: color-mix(in srgb, var(--accent) 55%, transparent); }

/* dial */
.viz-dial { width: 100%; max-width: 220px; margin: 0 auto; display: block; }
.viz-dial .vtrack { stroke: color-mix(in srgb, var(--text) 8%, transparent); }
.viz-dial .vglow { stroke: color-mix(in srgb, var(--accent) 20%, transparent); }
.viz-dial .varc { stroke: var(--accent); }
/* Quiet unless there is something to look at. A gauge that is red whatever it
   reads is a gauge nobody reads. */
.viz-dial.tone-neutral .vglow { stroke: color-mix(in srgb, var(--text) 10%, transparent); }
.viz-dial.tone-neutral .varc { stroke: var(--text); }
.viz-dial.tone-good .vglow { stroke: color-mix(in srgb, var(--good) 20%, transparent); }
.viz-dial.tone-good .varc { stroke: var(--good); }
.viz-dial .vd-num { fill: var(--text); font-size: 34px; font-weight: 600; font-family: var(--font-display); letter-spacing: -1.2px; }
.viz-dial .vd-cap { fill: var(--text-dim); font-size: 9.5px; font-family: var(--font-display); letter-spacing: 1.3px; }
.vd-foot { text-align: center; font-size: 12px; color: var(--text-muted); margin-top: 2px; }

/* stall ladder */
.viz-ladder { display: flex; align-items: flex-end; gap: 5px; height: 112px; }
.vl-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 6px; height: 100%; }
.vl-bar { width: 100%; border-radius: 4px 4px 2px 2px; background: color-mix(in srgb, var(--text) 14%, transparent); animation: vrise .7s var(--ease) both; }
.vl-col.p1 .vl-bar { background: color-mix(in srgb, var(--warn) 55%, transparent); }
.vl-col.p2 .vl-bar { background: color-mix(in srgb, var(--warn) 85%, transparent); }
.vl-col.p3 .vl-bar { background: var(--accent); }
.vl-d { font-family: var(--font-mono); font-size: 9.5px; color: var(--text-dim); }
@keyframes vrise { from { transform: scaleY(0); transform-origin: bottom; } }
.viz-marks { display: flex; gap: 15px; flex-wrap: wrap; margin-top: 13px; font-size: 11.5px; color: var(--text-muted); }
.viz-marks span { display: flex; align-items: center; gap: 6px; }
.viz-marks i { width: 9px; height: 9px; border-radius: 3px; display: block; }
.viz-marks i.p1 { background: color-mix(in srgb, var(--warn) 55%, transparent); }
.viz-marks i.p2 { background: color-mix(in srgb, var(--warn) 85%, transparent); }
.viz-marks i.p3 { background: var(--accent); }

/* shipment rail */
.viz-rail { position: relative; padding-top: 2px; }
.viz-rail .vr-line { position: absolute; left: 0; right: 0; top: 7px; height: 1px; background: var(--border-strong); }
.viz-rail .vr-done { position: absolute; left: 0; top: 7px; height: 1px; background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 35%, transparent)); }
.viz-rail .vr-nodes { position: relative; display: flex; justify-content: space-between; }
.vr-node { display: flex; flex-direction: column; align-items: center; gap: 9px; flex: 1; min-width: 0; }
.vr-dot { width: 15px; height: 15px; border-radius: 50%; border: 1px solid var(--border-strong); background: var(--card); display: grid; place-items: center; }
.vr-dot i { width: 5px; height: 5px; border-radius: 50%; background: color-mix(in srgb, var(--text) 20%, transparent); display: block; }
.vr-node.is-done .vr-dot { border-color: color-mix(in srgb, var(--accent) 60%, transparent); background: color-mix(in srgb, var(--accent) 18%, transparent); }
.vr-node.is-done .vr-dot i { background: var(--accent); }
/* The one node worth looking at, and the only red on the rail. */
.vr-node.is-stuck .vr-dot { border-color: var(--bad); background: color-mix(in srgb, var(--bad) 18%, transparent); }
.vr-node.is-stuck .vr-dot i { background: var(--bad); }
.vr-node.is-stuck .vr-l { color: var(--bad); }
.vr-node.is-stuck .vr-t { color: var(--text-muted); white-space: normal; }
.vr-node.is-now .vr-dot { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 13%, transparent); }
.vr-l { font-size: 11.5px; text-align: center; color: var(--text-muted); line-height: 1.35; padding: 0 4px; }
.vr-t { display: block; margin-top: 2px; font-family: var(--font-mono); font-size: 9.5px; color: var(--text-dim); }
/* Where the parcel is now, in the one weight that is not muted. A timeline
   where every node reads the same is five labels somebody has to compare
   before finding the one that answers the question they came with. */
.vr-node.is-now .vr-l { color: var(--text); font-weight: 600; }
.vr-node.is-done .vr-l { color: var(--text-muted); }

/* --- where an order goes --------------------------------------------------- *
   The map on an order. The outline and the two points are drawn from
   assets/worldmap.js, so everything here is colour: land is a wash of the text
   colour rather than a fill of its own, because it is the ground and not the
   subject, and the only red on it is the one place this parcel is going. */
.vmap { margin: 0; display: flex; flex-direction: column; gap: 8px; }
.vmap svg {
  display: block; width: 100%; height: auto;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.vm-land { fill: color-mix(in srgb, var(--text) 14%, transparent); }
.vm-arc {
  fill: none; stroke: color-mix(in srgb, var(--accent) 50%, transparent);
  stroke-width: 1.2; stroke-linecap: round;
}
.vm-from { fill: color-mix(in srgb, var(--text) 42%, transparent); }
.vm-halo { fill: color-mix(in srgb, var(--accent) 16%, transparent); }
.vm-to { fill: var(--accent); stroke: var(--surface-1); stroke-width: 1.2; }
/* Where the parcel has got to, as one of three stands rather than a measured
   position. Green because it is the only good news on this card and the red
   is already spent on where the box is going; two reds beside each other is
   neither of them standing out. */
.vm-at { fill: var(--good); stroke: var(--surface-1); stroke-width: 1.1; }
/* Once, and then it is still. */
.vmap.is-drawing .vm-arc { animation: vmdraw 620ms var(--ease) forwards; }
@keyframes vmdraw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
  .vmap.is-drawing .vm-arc { animation: none; stroke-dashoffset: 0 !important; }
}
.vm-foot {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  font-size: 12px; color: var(--text-muted);
}
.vm-where { font-weight: 600; color: var(--text); }
.vm-km { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim); }
.vm-note { margin: 0; font-size: 10.5px; color: var(--text-dim); }
/* The globe on an order: the same world as the marketing page, close up on
   the destination. The ground is the card's own surface with a little light
   where the pin is, so it reads as a sphere rather than a hole in the card. */
.vm-globe {
  position: relative; aspect-ratio: 16 / 10; overflow: hidden;
  border: 1px solid var(--border); border-radius: 10px;
  background: radial-gradient(120% 90% at 45% 45%, color-mix(in srgb, var(--text) 5%, var(--surface-1)), var(--surface-1));
}
.vm-globe canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.vm-chip {
  position: absolute; left: 0; top: 0; display: flex; align-items: center; gap: 9px;
  max-width: calc(100% - 16px); padding: 7px 10px; border-radius: 12px;
  background: color-mix(in srgb, var(--surface-1) 92%, transparent);
  border: 1px solid var(--border-strong, var(--border));
  box-shadow: 0 12px 28px -14px rgba(0,0,0,.8);
  font-size: 12px; line-height: 1.3; opacity: 0; transition: opacity .35s var(--ease, ease); pointer-events: none;
}
.vm-chip.is-on { opacity: 1; }
.vm-chip b { display: block; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vm-chip small { display: block; color: var(--text-dim); font-size: 11px; overflow-wrap: anywhere; }
.vm-chip > span:last-child { min-width: 0; }
.vm-chip-ic { flex: none; width: 22px; height: 22px; border-radius: 7px; background: color-mix(in srgb, var(--text) 8%, transparent); position: relative; }
.vm-chip-ic::after { content: ""; position: absolute; left: 7px; top: 7px; width: 8px; height: 8px; border-radius: 50%; background: var(--text-dim); }
.vm-chip-ic.is-good { background: color-mix(in srgb, var(--good) 16%, transparent); }
.vm-chip-ic.is-good::after { background: var(--good); }
@media (prefers-reduced-motion: reduce) { .vm-chip { transition: none; } }

/* density grid */
.viz-heat { display: grid; gap: 4px; }
.vh-day { font-family: var(--font-mono); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim); display: flex; align-items: center; }
.vh-cell { aspect-ratio: 1; border-radius: 4px; }
.viz-heatfoot { display: flex; align-items: center; gap: 8px; margin-top: 13px; font-size: 11px; color: var(--text-dim); }
.vh-ramp { display: flex; gap: 3px; }
.vh-ramp i { width: 13px; height: 9px; border-radius: 3px; display: block; }
.vh-note { margin-left: auto; text-align: right; }

/* share list */
.viz-share { display: grid; grid-template-columns: 22px 1fr auto 46px; align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--border); }
.viz-share:last-child { border-bottom: 0; }
.viz-share.is-link { cursor: pointer; }
.viz-share.is-link:hover .vsh-name { color: var(--accent); }
.vsh-icon { font-size: 15px; line-height: 1; }
.vsh-body { min-width: 0; }
.vsh-name { display: block; font-size: 13px; font-weight: 500; transition: color var(--fast); }
.vsh-track { display: block; height: 5px; border-radius: 4px; background: color-mix(in srgb, var(--text) 7%, transparent); overflow: hidden; margin-top: 6px; }
.vsh-track i { display: block; height: 100%; border-radius: 4px; background: color-mix(in srgb, var(--text) 34%, transparent); transform-origin: left; animation: vgrow .8s var(--ease) both; }
.viz-share.is-top .vsh-track i { background: var(--accent); }
.viz-share.is-top .vsh-pct { color: var(--accent); }
.vsh-meta { font-size: 12.5px; color: var(--text-muted); text-align: right; white-space: nowrap; }
.vsh-pct { font-size: 13px; font-weight: 600; text-align: right; }
/* A sparkline you can ask about. It carries thirty days behind one number,
   and until now none of those days could be named. */
.viz-sparkwrap { position: relative; }
.spark-read {
  position: absolute; top: -6px; left: 0;
  opacity: 0; pointer-events: none; white-space: nowrap;
  padding: 3px 8px; border-radius: 9px;
  border: 1px solid var(--border-strong);
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  font-size: 11px; font-weight: 600; letter-spacing: -.01em;
  transition: opacity .12s var(--ease), left .1s var(--ease);
  z-index: 2;
}
.viz-sparkwrap.is-reading .spark-read { opacity: 1; }
.viz-spark .vdot.is-live { transition: opacity .12s var(--ease); }

/* Which row the eye is on, in a list where the number is at the far right. */
.viz-share { border-radius: 10px; transition: background var(--fast); }
.viz-share:hover { background: var(--surface-1); }

@keyframes vgrow { from { transform: scaleX(0); } }

@media (prefers-reduced-motion: reduce) {
  .vl-bar, .vsh-track i { animation: none; }
  .spark-read, .viz-spark .vdot.is-live, .viz-share { transition: none; }
}
@media (max-width: 720px) {
  .viz-flow { flex-wrap: wrap; }
  .viz-step { flex: 1 1 40% !important; }
}

/* --- auth pages ----------------------------------------------------------- */
.authwrap {
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
.authside {
  position: relative;
  overflow: hidden;
  border-right: 1px solid var(--border);
  background:
    radial-gradient(900px 500px at 20% 10%, var(--accent-soft), transparent 60%),
    var(--shell);
  padding: 44px;
  display: flex;
  flex-direction: column;
}
.authside > *:not(.authart) { position: relative; z-index: 1; }
.authside .brandblock { padding: 0; }
/* the line above the headline and the headline itself are set from one
   size, so they cannot drift apart as the panel narrows */
.authside .pitch { margin: auto 0; max-width: 620px; --pitch-type: clamp(23px, 2.25vw, 30px); }
/* the standfirst keeps a readable measure even though the line above it
   is allowed to run the full width of the panel */
.authside .pitch p:not(.pitchline) { max-width: 460px; }

/* The trade named back at the merchant, one word at a time. The selector
   carries the panel because .authside .pitch p sets the standfirst size, and
   this line is a paragraph too. */
.authside .pitch .pitchline {
  /* a hair under the headline at full width, and it gives way faster than the
     headline does as the panel narrows, because this line has to hold a
     country and a category on one row */
  font-size: clamp(16px, 1.92vw, 27.6px);
  font-weight: 500;
  line-height: 1.65;
  letter-spacing: -.02em;
  color: var(--text-muted);
  margin: 0 0 26px;
}
/* the promise always sits on its own line, whatever the panel is doing */
.authside .pitch .pitchline .pl { display: block; }
.authside .pitch .pitchline .fx { font-style: normal; }

/* Each changing word sits in a slot you can see: a pill with a rule under it
   that fills up to the moment it changes, so the line reads as something that
   is running rather than something that was printed. */
.authside .pitch .pitchline .tok {
  position: relative;
  display: inline-grid;
  /* the two words share one cell, and neither may stretch to it: a stretched
     word measures the slot it is already in, which is how a short word ended
     up in a pill cut for a long one */
  justify-items: start;
  overflow: hidden;
  vertical-align: middle;
  padding: .12em .28em .16em;
  margin: 0 .04em;
  border-radius: .42em;
  border: 1px solid var(--border);
  background: rgba(127, 127, 127, .06);
  color: var(--text);
  font-weight: 600;
  /* a touch of overshoot, so the slot springs to its new width instead of
     easing politely into it */
  transition: width .44s cubic-bezier(.34, 1.42, .38, 1), border-color .25s var(--ease),
    background .25s var(--ease), box-shadow .35s var(--ease);
}
.authside .pitch .pitchline .tok::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0; height: 2px; width: 0;
  background: var(--accent);
  opacity: .55;
}
.authside .pitch .pitchline .tok.is-next {
  border-color: color-mix(in srgb, var(--accent) 34%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.authside .pitch .pitchline .tok.is-next::after { animation: tokfill var(--tokspan, 2000ms) linear both; }
.authside .pitch .pitchline .tok.is-swap {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent);
}
@keyframes tokfill { from { width: 0; } to { width: 100%; } }

.authside .pitch .pitchline .tin {
  grid-area: 1 / 1;
  white-space: nowrap;
  transition: transform .42s cubic-bezier(.22, 1.2, .36, 1), opacity .26s ease, filter .26s ease;
}
.authside .pitch .pitchline .tin i {
  font-style: normal;
  margin-left: 4px;
  display: inline-block;
}
.authside .pitch .pitchline .tin:not(.is-next):not(.is-out) i { animation: tokpop .5s cubic-bezier(.22, 1.4, .36, 1) both; }
.authside .pitch .pitchline .tin.is-next { transform: translateY(120%) scale(.94); opacity: 0; }
.authside .pitch .pitchline .tin.is-out { transform: translateY(-120%) scale(.94); opacity: 0; filter: blur(2px); }
@keyframes tokpop {
  0% { transform: scale(.5) rotate(-14deg); opacity: 0; }
  60% { transform: scale(1.18) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .pitchline .tok, .authside .pitch .pitchline .tin { transition: none; }
  .authside .pitch .pitchline .tok::after, .authside .pitch .pitchline .tin i { animation: none; }
}
.authside .pitch h2 {
  font-size: var(--pitch-type, 30px);
  letter-spacing: -.03em;
  line-height: 1.15;
  margin: 0 0 14px;
}
.authside .pitch h2 em { font-family: var(--font-accent); font-style: italic; color: var(--accent); }
.authside .pitch p { font-size: 14px; color: var(--text-muted); line-height: 1.6; margin: 0 0 22px; }
.authside .points { display: flex; flex-direction: column; gap: 10px; }
.authside .point { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; color: var(--text-muted); }
.authside .point svg { width: 15px; height: 15px; color: var(--accent); flex-shrink: 0; margin-top: 2px; }
/* the temple, bottom left, running off both edges of the panel */
.authart {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 0;
  /* big enough to read as background rather than ornament: it sits on the
     bottom left corner of the panel with roughly half of it running off */
  width: clamp(460px, 82%, 820px);
  transform: translate(-27%, 21%);
  height: auto;
  fill: none;
  stroke: var(--art-ink);
  stroke-width: .7;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: var(--art-op);
  pointer-events: none;
  /* let the base dissolve before it reaches the line of type at the bottom */
  -webkit-mask-image: linear-gradient(180deg, #000 72%, transparent 99%);
  mask-image: linear-gradient(180deg, #000 72%, transparent 99%);
}
.authart .caps { stroke-width: .9; }
.authart .lantern { stroke-width: 1.15; }
@media (max-height: 760px) { .authart { width: clamp(380px, 66%, 560px); transform: translate(-38%, 30%); } }

.authside .foot { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); letter-spacing: .1em; text-transform: uppercase; }
/* The name keeps its own spelling even inside a line set in capitals. */
.authside .foot .nm { text-transform: none; letter-spacing: .02em; }

.authmain { display: grid; place-items: center; padding: 44px 24px; }
.authcard { width: min(400px, 100%); }
.authcard h1 { font-size: 24px; letter-spacing: -.02em; margin: 0 0 6px; }
.authcard .sub { font-size: 13.5px; color: var(--text-muted); margin: 0 0 24px; line-height: 1.55; }
.authcard .altrow { margin-top: 18px; font-size: 12.5px; color: var(--text-muted); text-align: center; }
.authcard .altrow a { color: var(--accent); }
.authcard .divider {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 16px;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-dim);
}
.authcard .divider::before, .authcard .divider::after {
  content: ''; flex: 1; height: 1px; background: var(--border);
}
/* on a narrow window the panel spends too much of itself on margin, which is
   what pushed the trade line onto a third row */
@media (max-width: 1040px) {
  .authside { padding: 34px 26px; }
}
/* The mark above the form, on a phone only. On a wide screen the panel
   beside it already says who this is. */
.authmark { display: none; }
@media (max-width: 880px) {
  .authwrap { grid-template-columns: 1fr; }
  .authside { display: none; }
  .authmark {
    display: flex; align-items: center; gap: 11px;
    margin-bottom: 26px; text-decoration: none; color: var(--text);
  }
  .authmark .mark { width: 30px; height: 30px; flex: none; color: var(--accent); overflow: visible; }
  .authmark span { display: block; line-height: 1.25; }
  .authmark b { display: block; font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; }
  .authmark i {
    display: block; font-style: normal;
    font-family: var(--font-accent); font-size: 9.5px; letter-spacing: .14em;
    text-transform: uppercase; color: var(--text-dim);
  }
}

/* The pull to refresh tab. Fixed above the page rather than pushing it down:
   moving the whole document under a finger is a scroll fighting a gesture. */
.pulltab {
  position: fixed; top: 0; left: 50%; z-index: 55;
  width: 40px; height: 40px; margin-top: -46px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--card); border: 1px solid var(--border-strong);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
  transform: translate(-50%, 0); opacity: 0;
  color: var(--text-dim);
  pointer-events: none;
}
.pulltab .tmark { width: 20px; height: 20px; display: block; }
/* Armed: past the point where letting go refreshes. The colour is the whole
   signal, so it does not need a word next to it. */
.pulltab.is-armed { color: var(--accent); border-color: var(--accent-line); }
.pulltab.is-springing { transition: transform .26s var(--ease), opacity .26s var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .pulltab.is-springing { transition: none; }
}

/* An order as a phone shows it: the number, what it is, what it cost, where
   it stands, and the one thing to press. Everything else is on the order
   itself. Sized so eight or nine fit on a screen, because a queue is
   something to scan and a card that fills the viewport is a page. */
.ordercard {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
}
.ordercard .oc-media { position: relative; flex: none; width: 44px; height: 44px; }
.ordercard .oc-media .thumb,
.ordercard .oc-media > * { width: 44px; height: 44px; border-radius: 9px; }
/* More than one line on the order, said on the picture of the first rather
   than by drawing three more pictures. */
.ordercard .oc-more {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px; background: rgba(0, 0, 0, .58);
  font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: #fff;
}
.ordercard .oc-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
.ordercard .oc-no { font-size: 14.5px; font-weight: 600; }
.ordercard .oc-right { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; }
.ordercard .oc-total { font-size: 14px; font-weight: 600; white-space: nowrap; }
.ordercard .oc-right .btn { padding: 4px 12px; font-size: 12.5px; }

/* The lines on an order. A list, not a table: nobody sorts three items or
   selects them, and the same shape reads at every width instead of stacking
   into unlabelled cells on a phone. */
.orderitems { display: flex; flex-direction: column; }
.oi {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 13px 16px; border-top: 1px solid var(--border);
  color: inherit; text-decoration: none;
}
.oi:first-child { border-top: 0; }
a.oi { transition: background var(--fast); }
a.oi:hover { background: var(--surface-1); }
.oi-media { flex: none; }
/* Twice what it was. This is the only thing on the row that says what was
   ordered, and at forty four pixels a lamp and a stool are the same grey
   square. */
.oi-media .thumb, .oi-media > * { width: 88px; height: 88px; border-radius: 11px; }
.oi-main { flex: 1; min-width: 8rem; display: flex; flex-direction: column; gap: 5px; }
.oi-title { font-size: 14px; font-weight: 600; line-height: 1.3; }
/* The three facts that tell one line from another: which variant, the SKU a
   merchant reads off their own packing list, and how many. One line, and each
   cut rather than wrapped, or a long variant name turns the row into a
   paragraph. */
.oi-sub {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--text-dim); min-width: 0;
}
.oi-var { color: var(--text-muted); max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.oi-sku { font-size: 10.5px; }
.oi-qty {
  padding: 1px 7px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-muted); font-variant-numeric: tabular-nums; font-size: 11px;
}
.oi-right {
  flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
  margin-left: auto; text-align: right;
}
.oi-price { font-size: 13px; white-space: nowrap; }
/* What the customer paid, and under it what this costs. Two figures, each
   with the half sentence that says which is which: a column of two amounts
   with no labels is two amounts nobody can tell apart. */
.oi-fig { display: flex; align-items: baseline; gap: 8px; white-space: nowrap; }
.oi-fig em {
  font-style: normal; font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-dim);
}
.oi-fig b { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* The one the merchant is accountable for is the one in full weight. Not the
   accent: three lines on an order would be three red numbers down a column,
   which is the accent used as a colour rather than as an instruction. */
.oi-fig:not(.is-cost) b { color: var(--text-muted); font-weight: 500; }
.oi-fig.is-cost .dim { font-size: 11.5px; font-weight: 400; }
/* The money on this card, under the lines it is about. */
.oi-costs { padding: 14px 16px 16px; border-top: 1px solid var(--border); }
@media (max-width: 520px) {
  .oi-media .thumb, .oi-media > * { width: 64px; height: 64px; }
  /* The numbers go under the name rather than squeezing it to two words. */
  .oi-right { margin-left: 78px; width: 100%; align-items: flex-start; text-align: left; }
}

/* An icon in a card head. Small, dim, and to the left of the title: it is
   there to make the card findable at a glance, not to be looked at. */
.cardhead h3 { display: flex; align-items: center; gap: 8px; }
.headicon { display: inline-flex; color: var(--text-dim); flex: none; }
.headicon svg { display: block; }

/* The span a figure is about, said above it rather than only in the picker
   across the card. A number with no period attached to it is not a number. */
.chartspan {
  font-family: var(--font-accent); font-size: 10px; letter-spacing: .11em;
  text-transform: uppercase; color: var(--text-dim);
  margin: 0 0 2px;
}

/* Three figures under a chart, all about the same span. The tick in front of
   each is what lets them sit side by side without a legend. */
.figrow { display: flex; flex-wrap: wrap; gap: 26px; margin: 2px 0 14px; }
.fig { display: flex; flex-direction: column; gap: 3px; }
.fig-head {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-muted);
}
.fig-tick { width: 2px; height: 13px; border-radius: 2px; background: var(--text-dim); flex: none; }
.fig.is-accent .fig-tick { background: var(--accent); }
.fig.is-good .fig-tick { background: var(--good); }
.fig-val { font-size: 19px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.fig-foot { font-size: 11px; color: var(--text-dim); }
@media (max-width: 520px) {
  .figrow { gap: 16px 22px; }
  .fig-val { font-size: 17px; }
}

/* --- role and store picker ------------------------------------------------ */
.rolelist { display: flex; flex-direction: column; gap: 6px; }
.rolerow {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: inherit;
  cursor: pointer;
  text-align: left;
  transition: border-color var(--fast), background var(--fast);
}
.rolerow:hover { border-color: var(--border-strong); background: var(--card-hover); }
.rolerow.is-current { border-color: var(--accent-line); background: var(--accent-soft); }
.rolerow .av {
  width: 30px; height: 30px; flex-shrink: 0;
  border-radius: 9px;
  display: grid; place-items: center;
  background: var(--surface-2);
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600;
  color: var(--text-muted);
}
.rolerow .rtxt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rolerow .rtxt b { font-size: 13px; font-weight: 500; }
.rolerow .rtxt span { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .07em; color: var(--text-dim); }
.userrow { border: none; background: none; padding: 0; cursor: pointer; width: 100%; text-align: left; color: var(--text); }
.userrow:hover .un { color: var(--accent); }
.store-id { border: 1px solid var(--border); width: 100%; text-align: left; font: inherit; color: var(--text); cursor: pointer; }

/* --- misc ----------------------------------------------------------------- */
.field textarea.is-bad, .field input.is-bad { border-color: var(--bad); }
.field.is-wide { grid-column: 1 / -1; }
.formgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 14px; }
.formgrid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.stack { display: flex; flex-direction: column; gap: 14px; }
.rowline { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rowline.end { justify-content: flex-end; }
.muted { color: var(--text-muted); }
.dim { color: var(--text-dim); }
.nowrap { white-space: nowrap; }

@media (max-width: 860px) {
  .formgrid, .formgrid.three { grid-template-columns: 1fr; }
  /* A wide one too. `.is-wide` is a ceiling for a desk, and on a phone it
     left a forty pixel sliver of the list down the side: not enough to read
     and enough to press by accident. */
  .drawer, .drawer.is-wide { width: 100vw; }
}

/* --- density: the warehouse console works in this all day ------------------ */
/* Same tokens, same black, tighter rhythm. Set html[data-density="compact"]. */
html[data-density="compact"] {
  --gutter: 22px;
  --sidebar-w: 218px;
  --topbar-h: 52px;
  --radius-lg: 14px;
  --radius: 10px;
}
html[data-density="compact"] body { font-size: 13px; }
html[data-density="compact"] .content { padding: 18px var(--gutter) 40px; max-width: 1560px; }
html[data-density="compact"] .card { padding: 14px; }
html[data-density="compact"] .cardhead { padding: 11px 14px; }
html[data-density="compact"] .kpi { padding: 13px; }
html[data-density="compact"] .kpi .knum { font-size: 26px; }
html[data-density="compact"] .pagetitle { font-size: 21px; }
html[data-density="compact"] table { font-size: 12.5px; }
html[data-density="compact"] thead th { padding: 8px 11px; }
html[data-density="compact"] td { padding: 8px 11px; }
html[data-density="compact"] .grid { gap: 10px; }
html[data-density="compact"] .btn { height: 30px; padding: 0 12px; }
html[data-density="compact"] .btn.sm { height: 26px; }
html[data-density="compact"] .navitem { padding: 7px 10px; }
html[data-density="compact"] .pagehead { margin-bottom: 14px; }

/* --- a phone is a thumb, not a pointer ------------------------------------ */
/* Every control in this app was built at the size a mouse needs, and on a
   phone that is what a merchant actually holds. Measured rather than guessed:
   the period picker that drives the whole overview was 19px tall, the buttons
   28, the tabs 28, the menu rows 30, the avatar 30. A thumb is about nine
   millimetres across and lands with a couple of millimetres of slop, which is
   why every guideline asks for something in the low forties.

   Raised under 720px and nowhere else, so nothing about the desk changes.
   After the compact rules on purpose: the console sets that density, and a
   rule with a `html[data-density]` in front of it wins over a bare class
   however late it comes, so the same selector has to say it again here.

   Heights rather than padding wherever the control has one, or a taller
   target with the same text in it is a button whose label has drifted off
   centre. */
@media (max-width: 720px) {
  .btn { height: 40px; padding: 0 18px; }
  .btn.sm { height: 36px; font-size: 12.5px; padding: 0 14px; }
  .btn.lg { height: 46px; }
  html[data-density="compact"] .btn { height: 38px; padding: 0 15px; }
  html[data-density="compact"] .btn.sm { height: 34px; }

  /* The picker for the period every figure on the screen answers. Ten pixel
     type in a nineteen pixel box is the smallest thing anybody here has to
     hit, and it is on the first screen a merchant opens. */
  .segmented button { padding: 11px 10px; font-size: 10.5px; }
  /* Seven periods across a phone is what it holds and no more, so the bar
     scrolls rather than running off the edge: at 390 with the room a thumb
     needs they only just fit, and the last one is the one somebody reaches
     for when they want a span of their own. */
  /* A line of its own and the width of the screen, so what does not fit is
     scrolled to rather than hidden past the edge. As one item among the
     buttons it kept its content width and pushed the whole row off a 320px
     screen: it is the widest thing in that head and the only one that can be
     scrolled instead of wrapped. */
  /* A flex item will not shrink past its own content unless it is told it
     may, and these two hold the widest things on a screen: the head's row of
     controls, and the column a tab bar and a filter bar sit in. Without it the
     head hung 35px off the right of a 320px screen and the balance screen went
     533px wide the moment its tabs stopped wrapping. */
  .rowline, .stack { min-width: 0; }
  .rowline > .segmented { flex: 1 1 100%; }
  .segmented { max-width: 100%; min-width: 0; overflow-x: auto; scrollbar-width: none; }
  .segmented::-webkit-scrollbar { display: none; }

  /* One line that scrolls rather than three that wrap. Eight tabs with their
     counts on them is three rows on a phone, which is eighty pixels of filter
     above a list that has not been reached yet: on the catalogue the first
     product sat below the fold on a screen a merchant opened to look at
     products. The bar keeps every tab and its count, and the one that is on is
     scrolled to. */
  .tabbar {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; scroll-behavior: smooth;
    /* It only scrolls if it is allowed to be narrower than what is in it: as
       an item in a flex column it kept its content width and took the balance
       screen 551px wide instead. */
    max-width: 100%; min-width: 0;
  }
  .tabbar::-webkit-scrollbar { display: none; }
  .tabb { padding: 10px 14px; font-size: 13px; flex: none; }
  .grpill { padding: 9px 14px; }
  .navitem { padding: 11px 11px; }
  html[data-density="compact"] .navitem { padding: 10px 10px; }

  /* Square controls: the avatar in the topbar, the one in the sidebar foot,
     and the theme switch beside it. */
  .iconbtn, .sidefoot .iconbtn { width: 38px; height: 38px; }
  /* The face itself is drawn at a size the script passes, so the room comes
     from around it rather than from the picture: the button is 38 and the
     avatar in it stays the 30 every other screen draws. The camera that
     appears over it on a press follows the picture rather than the padding. */
  .facebtn { padding: 4px; border-radius: 13px; }
  .facebtn .fcam { inset: 4px; border-radius: 11px; }

  /* Two lines of text is not a target. The name opens the seat switcher, so
     it gets the room the rows around it have. */
  .userrow .uname { padding: 7px 0; }

  /* Ticking a row. Fifteen pixels is the browser's own default and it is the
     one control here that is pressed in a run of ten, down a list, with a
     thumb. The cell already holds 38. */
  .cbcell input[type="checkbox"] { width: 21px; height: 21px; }

  /* The arrow that unfolds the sizes under a product. It is deliberately
     small so it does not shout beside the row it sits on, and it already
     carries padding for a finger: the padding is what grows, not the glyph. */
  button.gchev { padding: 9px; margin: -9px -5px -9px -9px; }

  /* Paging, and the two inline controls that look like text and are not: the
     reference somebody copies, and the link between the two halves of a
     tracking screen. Both are read at a glance and pressed rarely, so they
     get height rather than a box that shouts. */
  .pager .pg { min-width: 36px; height: 36px; }
  .copyable { padding: 7px 10px; }
  /* The one in a table has no box around it on purpose, because three hundred
     boxes down a column is a column of boxes. It still has to be pressable, so
     it takes the height and not the width: a reference that shifts sideways
     would stop lining up with the rows above it. */
  .copyable.is-plain { padding: 6px 0; }
  .jump { padding: 9px 12px; }

  /* The chips that filter a console queue by how long something has stood
     there. Nine pixel type in a nineteen pixel pill. */
  button.chip { padding: 8px 12px; font-size: 10px; }

  /* Each part of the bar that says where the parcels are is a way into that
     tab. A row of them at two pixels of padding is five links in the height
     of one. */
  a.vsp-item { padding: 8px 0; }
}

/* --- the way past the menu ------------------------------------------------- */
/* Off the screen until the keyboard reaches it, which is the first thing it
   reaches. A mouse never sees it and a screen reader reads it first, which is
   the whole point: the eleven menu rows in front of every queue are the same
   eleven on every screen. */
.skiplink {
  position: fixed;
  top: 10px; left: 10px;
  z-index: 200;
  transform: translateY(-200%);
  padding: 10px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--accent-line);
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-family: var(--font-display); font-size: 13px; font-weight: 600;
  cursor: pointer;
  transition: transform var(--fast);
}
.skiplink:focus-visible { transform: none; outline: 2px solid var(--border-focus); outline-offset: 2px; }

/* --- print --------------------------------------------------------------- */
@media print {
  .sidebar, .topbar, .toaster, .overlay { display: none !important; }
  .content { padding: 0; max-width: none; }
  body { background: #fff; color: #000; }
}

/* The ground a screen stands on, said once in the sidebar. A demonstration
   that cannot be told from the real thing is one somebody will quote back
   at you. */
.samplemark {
  margin: 0 0 10px;
  padding: 5px 10px;
  border-radius: 7px;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent-ink-on-soft);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: center;
}

/* --- connecting: the few things we need before anything works ------------
   Asked for while a merchant connects rather than left in a settings page,
   because neither the logo nor the countries is a preference. The logo goes
   on the box their customer opens; the countries are what the warehouse has
   to price before a first order can move.
   ------------------------------------------------------------------------ */

/* --- a catalogue still arriving ------------------------------------------
   Said where a missing product would be noticed, in our own colours rather
   than as a warning: nothing is wrong, something is on its way. The three
   marks breathe on the same rhythm as the card that waits for a session, so
   the two read as one idea rather than as two loading animations.
   ------------------------------------------------------------------------ */
.readnote {
  display: flex; align-items: center; gap: 14px;
  border: 1px solid rgba(226, 64, 47, .34);
  background: linear-gradient(180deg, rgba(226, 64, 47, .09), rgba(226, 64, 47, .03));
  border-radius: var(--radius); padding: 14px 16px; margin: 0 0 18px;
}
.readnote .rmark { display: flex; align-items: flex-end; gap: 4px; flex: none; height: 22px; }
.readnote .rmark i {
  display: block; width: 5px; height: 100%; border-radius: 3px;
  background: var(--accent); opacity: .45;
  animation: readnote-breathe 1500ms ease-in-out infinite;
}
.readnote .rmark i:nth-child(2) { height: 70%; animation-delay: 180ms; }
.readnote .rmark i:nth-child(3) { height: 45%; animation-delay: 360ms; }
@keyframes readnote-breathe {
  0%, 100% { opacity: .35; }
  50% { opacity: 1; }
}
.readnote .rtxt { min-width: 0; }
.readnote .rtxt b { display: block; font-size: 13.5px; letter-spacing: -.01em; }
.readnote .rtxt span { display: block; font-size: 12.5px; color: var(--text-muted); margin-top: 3px; }
/* A figure that loops is decoration to somebody who has asked for none. */
@media (prefers-reduced-motion: reduce) {
  .readnote .rmark i { animation: none; opacity: .7; }
}

.obcard { width: min(560px, 100%); }

/* --- before the server has answered --------------------------------------
   This page draws nothing until it knows which step somebody is on, and the
   answer comes from the server. That left a card with nothing in it, which
   on a service that was waking up is indistinguishable from a page that is
   broken: no heading, no rail, no way to tell waiting from stuck.

   Three bars the shape of what is about to arrive, and a line that says what
   is happening. The bars breathe rather than sweep, because a sweep says
   rows are landing one after another and here one thing is coming: the whole
   step, at once, or a failure.
   ------------------------------------------------------------------------ */
.obwait .bars { display: grid; gap: 12px; margin: 26px 0 0; }
.obwait .bars i {
  display: block; height: 46px; border-radius: var(--radius-sm);
  background: var(--surface-2);
  animation: obwait-breathe 1600ms ease-in-out infinite;
}
.obwait .bars i:first-child { height: 62px; }
.obwait .bars i:nth-child(2) { animation-delay: 160ms; }
.obwait .bars i:nth-child(3) { animation-delay: 320ms; }
@keyframes obwait-breathe {
  0%, 100% { opacity: .5; }
  50% { opacity: 1; }
}
/* A figure that loops is decoration, and somebody who has asked not to see
   movement should not be shown any. The bars stay, because what they say is
   said by being there. */
@media (prefers-reduced-motion: reduce) {
  .obwait .bars i { animation: none; opacity: .7; }
}

/* The second answer on a card that could not start, under a rule, because it
   is a different thing to do rather than more of the sentence above it: try
   again is one press, and making the connection again is a trip to Shopify. */
.obfix {
  margin: 26px 0 0; padding: 22px 0 0;
  border-top: 1px solid var(--border);
}
.obfix h2 { margin: 0 0 6px; font-size: 15px; }
.obfix .btn { margin-top: 14px; }
/* And the way to a person, which is the last line rather than a step. */
.obhelp { margin-top: 22px; font-size: 12.5px; }

.obrail {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 26px; padding: 0; list-style: none;
}
.obrail li {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--text-dim);
}
.obrail li + li::before {
  content: ""; width: 18px; height: 1px; background: var(--border-strong);
}
.obrail .dot {
  width: 20px; height: 20px; border-radius: 999px;
  border: 1px solid var(--border-strong);
  display: grid; place-items: center;
  font-size: 11px; font-variant-numeric: tabular-nums;
}
.obrail li.is-now { color: var(--text); }
.obrail li.is-now .dot { border-color: var(--accent); color: var(--accent); }
.obrail li.is-done .dot {
  background: var(--accent-fill); border-color: var(--accent-fill); color: var(--accent-ink);
}

.obchips { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 6px; }
.obchip {
  font: inherit; font-size: 13px;
  color: var(--text-muted);
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 6px 13px;
  cursor: pointer;
  transition: color .14s ease, background-color .14s ease, border-color .14s ease;
}
.obchip:hover { color: var(--text); border-color: var(--border-strong); }
.obchip[aria-pressed="true"] {
  color: var(--accent-ink); background: var(--accent-fill); border-color: var(--accent-fill);
}
.obchip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.obcount { font-size: 12.5px; color: var(--text-dim); margin: 10px 0 0; }

.obbox { display: grid; place-items: center; padding: 6px 0 14px; }
.obbox svg { width: 190px; height: 190px; overflow: visible; }

.obshop {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; margin: 0 0 18px;
  border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface-1);
}
.obshop .tick {
  width: 22px; height: 22px; border-radius: 999px; flex: 0 0 auto;
  background: var(--accent-fill); color: var(--accent-ink);
  display: grid; place-items: center; font-size: 12px;
}
.obshop .who { min-width: 0; }
.obshop .who b { display: block; font-size: 14px; font-weight: 600; }
.obshop .who span { font-size: 12.5px; color: var(--text-dim); word-break: break-all; }

/* A logo is previewed square, because that is the shape it is printed in. */
.ppsquare {
  width: 72px; height: 72px; border-radius: 12px;
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  display: grid; place-items: center;
  overflow: hidden;
}
.ppsquare img { width: 100%; height: 100%; object-fit: contain; display: block; }
.ppempty { font-size: 20px; font-weight: 600; color: var(--text-dim); letter-spacing: .02em; }

/* The mark on a sync button. It draws itself for as long as the reading
   takes, and sits there whole when nothing is happening. */
.btn.has-tmark { display: inline-flex; align-items: center; gap: 7px; }
.tmark { width: 14px; height: 14px; flex: 0 0 auto; overflow: visible; }
.btn.has-tmark .tmark { color: var(--accent); }

/* Arriving from the Shopify admin with a hand off code. The page is about to
   navigate, so the sign in form is not something to look at on the way past.
   Hidden rather than removed, because the code can fail and then the form is
   the answer. */
.is-arriving .authcard, .is-arriving .authside { visibility: hidden; }

/* A product that is gone from the merchant's own Shopify catalogue. Dimmed
   rather than struck through: it is not wrong, it is no longer for sale, and
   the price and the orders under it are still real.

   Sentence case and its own colour, because the label face beside it
   uppercases and that turned the one line worth reading into another
   attribute. */
.prodcell.is-gone .pt { color: var(--dim); }
.prodcell.is-gone .thumb, .prodcell.is-gone img { opacity: .45; }
.gonenote {
  margin-top: 2px;
  font-size: 11.5px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--warn, #d08770);
}

/* The button that unfolds the rest of the country list. A chip, because it
   sits in a row of chips, but plainly not a country: no flag, dimmed, and it
   does not toggle anything. */
.obchip.is-more, .pick.is-more {
  color: var(--dim);
  border-style: dashed;
}
.obchip.is-more:hover, .pick.is-more:hover { color: var(--text); }

/* The first-run card on the overview. It sits above everything because it is
   the only thing on that screen worth reading until a first order has gone
   out, and it removes itself once one has. */
.firstrun { border-color: var(--accent-line); }
.frsteps {
  list-style: none;
  margin: 16px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 7px;
  counter-reset: fr;
}
.frsteps li {
  counter-increment: fr;
  display: flex;
  gap: 9px;
  align-items: baseline;
  font-size: 12.5px;
  color: var(--text-dim);
}
.frsteps li::before {
  content: counter(fr);
  flex: none;
  width: 17px; height: 17px;
  border-radius: 50%;
  border: 1px solid var(--line2);
  font-size: 10px;
  display: grid; place-items: center;
  color: var(--text-dim);
}
/* Done is a tick and not a struck-through line: these are things that
   happened, not things that were cancelled. */
.frsteps li.is-done::before { content: '\2713'; border-color: var(--good); color: var(--good); }
.frsteps li.is-now { color: var(--text); font-weight: 600; }
.frsteps li.is-now::before { border-color: var(--accent); color: var(--accent); }

/* The walk round the place. The dark is one enormous shadow cast by the
   spotlight rather than a mask, because a mask clips to its own box and this
   has to darken everything outside one. */
.tour { position: fixed; inset: 0; z-index: 900; }
.tour-hole {
  position: fixed;
  border-radius: 10px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .72);
  outline: 1px solid var(--accent);
  outline-offset: 0;
  transition: top .18s ease, left .18s ease, width .18s ease, height .18s ease;
  pointer-events: none;
}
.tour-card {
  position: fixed;
  background: var(--surface-1);
  border: 1px solid var(--line2);
  border-radius: 12px;
  padding: 15px 16px 13px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
}
.tour-of {
  font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-dim); margin-bottom: 7px;
}
.tour-card h4 { margin: 0 0 5px; font-size: 14.5px; letter-spacing: -.01em; }
.tour-card p { margin: 0 0 13px; font-size: 13px; line-height: 1.5; color: var(--text-dim); }
.tour-act { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tour-nav { display: flex; gap: 7px; }
/* Nothing here about where it sits on a phone. It was pinned to the bottom,
   which put it over the one stop that cannot move: the account group hangs at
   the foot of the menu, so Balance was spotlit underneath the card explaining
   it. ui.js places it against the spotlight instead, on every width. */

/* Countries. The general picker is built for a title and a hint on two lines,
   which turns a flag, a name and a two letter code into three, and thirty six
   of those is a wall rather than a list. One line each, and four across where
   there is room. */
.pickgrid.tight { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 5px; }
@media (max-width: 1180px) { .pickgrid.tight { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .pickgrid.tight { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pick.tight {
  align-items: center;
  padding: 7px 10px;
  gap: 9px;
  font-size: 12.5px;
}
/* The general picker stacks its span, which is right for a title over a hint
   and wrong for a flag beside a name. Said out loud rather than inherited:
   overriding display without overriding the direction is how this ended up
   with the flag on its own line. */
.pick.tight > span {
  display: flex; flex-direction: row; align-items: center; gap: 7px;
  min-width: 0; flex: 1;
}
.pick.tight .ctry { display: inline-flex; flex-direction: row; gap: 7px; min-width: 0; }
.pick.tight .ctry span, .pick.tight .cname {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The code earns its place by being scannable, not by being read. Quiet, and
   still read: --text-dim is 3.08:1 on a card, and `+5` is the one thing on
   that row that says what picking it costs. */
.pick.tight em {
  margin-left: auto; flex: none;
  font-size: 10px; letter-spacing: .06em;
  color: var(--text-muted);
  font-style: normal;
}
/* The two halves of the list, said out loud rather than left to be inferred
   from where the button is. */
.picksection {
  font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-dim); margin: 16px 0 7px;
}
.picksection:first-child { margin-top: 0; }

/* The acceptance step. A card per agreement, so the tick belongs to the thing
   above it rather than to the page: one box at the bottom of everything is a
   box that was ticked to get past, and evidence that somebody ticked past a
   wall is not evidence they read the wall. */
/* The agreements themselves, in a box that has to be read to the end.

   Bounded rather than laid out down the page, because a step is a step: a
   page that scrolls for four thousand words has lost the rail, the ticks and
   the button off the bottom of the screen, and somebody has no idea how much
   is left. A panel keeps all three in sight and makes the amount visible. */
.tdoc {
  height: min(46vh, 420px); overflow-y: auto; overscroll-behavior: contain;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 22px; margin-bottom: 12px;
  font-size: 13.5px; line-height: 1.6; color: var(--text-muted);
}
.tdoc:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 2px; }
.tdoc h1 { font-size: 17px; margin: 0 0 6px; }
.tdoc h2 { font-size: 14.5px; margin: 22px 0 8px; font-weight: 600; color: var(--text); }
.tdoc h3 { font-size: 13.5px; margin: 16px 0 4px; font-weight: 600; color: var(--text); }
.tdoc p { margin: 0 0 10px; }
.tdoc .lede, .tdoc .stamp, .tdoc .also { display: none; }
.tdoc ul { padding-left: 18px; margin: 0 0 12px; }
.tdoc li { margin-bottom: 6px; }
.tdoc .num { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); margin-right: 7px; }
.tdoc .scope { border-top: 1px solid var(--border); padding-top: 12px; margin-top: 12px; }
.tdoc .card { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin: 12px 0; }
.tdoc .draft { display: none; }
.tsplit { border: 0; border-top: 1px solid var(--border-strong); margin: 26px 0; }
.tload { color: var(--text-dim); }

/* Whether the gate is passed, said rather than left to be worked out from a
   box that will not tick. */
.tgate { font-size: 12.5px; color: var(--text-dim); margin: 0 0 14px; }
.tgate.is-done { color: var(--good); }

.tick {
  display: flex; gap: 10px; align-items: flex-start;
  border-top: 1px solid var(--border); padding-top: 14px; margin-bottom: 4px;
  font-size: 13.5px; cursor: pointer;
}
.tick input:disabled { opacity: .35; }
.tick input:disabled + span { color: var(--text-dim); }
.tick input { margin-top: 3px; flex: none; width: 16px; height: 16px; accent-color: var(--accent); }
.tick span { color: var(--text); }
.fine { font-size: 12px; color: var(--text-dim); margin-top: 14px; }


/* --- two orders in one parcel ------------------------------------------- */
/* A row per order, the same height whether it can be ticked or not: the one
   that was pressed is already in the parcel and says so with a mark rather
   than with a checkbox nobody may use. */
.cbrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2);
  margin-bottom: 6px;
  cursor: pointer;
}
.cbrow.is-fixed { cursor: default; }
.cbrow .thumb { width: 34px; height: 34px; flex: 0 0 34px; }
.cbtick {
  width: 15px; height: 15px; flex: 0 0 15px;
  border-radius: 4px;
  background: var(--accent);
}
.cbtxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.cbtxt .dim { font-size: 12px; }
.cbmoney { font-variant-numeric: tabular-nums; white-space: nowrap; }
.cbsum { margin-top: 10px; }
.cbsaved {
  margin: 8px 0 0;
  color: var(--accent);
  font-weight: 600;
}

/* --- a list that is only in the way until it is wanted -------------------- */
/* The channels on the tracking upload: needed while the carrier column is
   being filled in and noise the rest of the time. */
.upchannels { margin: 10px 0 0; }
.upchannels > summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--text-muted);
  padding: 6px 0;
}
.upchannels > summary:hover { color: var(--text); }
.upchannels p { margin: 4px 0 0; line-height: 1.9; }

/* --- what this platform pays for ----------------------------------------- */
/* Nine rows where the second line under a cell is half the answer: what a
   service is for, whether its fee moves with what goes through it, and the
   day somebody last checked the usage. `.ps` is styled inside `.prodcell`
   and nowhere else, so a register drawn with bare ones was printing its
   sub-lines at body size. */
.costs td { vertical-align: top; }
.costs .pt { color: var(--text); font-weight: 500; }
.costs .ps {
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-dim);
  margin-top: 2px;
  text-transform: none;
  letter-spacing: 0;
}
.costs .svc { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.costs .svc .pthumb { width: 30px; height: 30px; border-radius: 8px; }
.costs .svc .pthumb svg { width: 19px; height: 19px; }
.costs .svc > div { min-width: 0; }
/* A sentence somebody wrote down is longer than a price, and row height is
   what decides how much of a list fits on a screen. */
.costs .usage { max-width: 320px; }
.costs tr.is-off td { opacity: .55; }
/* Two buttons that wrap make a row half as tall again, and row height is what
   decides how much of a list fits on a screen. */
.costs td.acts { white-space: nowrap; }

/* Supplier requirements retain merchant paragraphs and bullet lists. */
.sourcing-notes { overflow-wrap: anywhere; white-space: normal; }
.sourcing-notes p { margin: 0 0 10px; }
.sourcing-notes ul { list-style: disc; padding-left: 22px; margin: 10px 0; }
.sourcing-notes li { margin: 5px 0; }

/* A password field with its own show button. The button sits inside the
   field on the right, and the input leaves it room. */
.pwwrap { position: relative; }
.pwwrap > input { width: 100%; padding-right: 44px; }
.pwshow {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 32px; height: 32px; display: grid; place-items: center;
  border: 0; border-radius: 8px; background: transparent;
  color: var(--text-muted); cursor: pointer;
}
.pwshow:hover { color: var(--text); }
.pwshow:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }
/* Merchant quote coverage: compact summary, details on demand. */
.quote-coverage-count{display:grid;gap:3px;font-size:12px;line-height:1.45}
.quote-coverage-count small{color:var(--text-muted);font-size:11px}
.variant-coverage [hidden],#pdPriceCard[hidden]{display:none!important}
.variant-coverage-summary{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 20px}
.variant-coverage-summary h3{margin:0 0 4px;font:600 14px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;letter-spacing:0;text-transform:none}
.variant-coverage-summary p{margin:0;font-size:12px;line-height:1.5}
.variant-coverage-muted{color:var(--text-muted)}
.variant-coverage-muted::before{content:" · "}
.variant-coverage-summary .btn{flex-shrink:0}
.variant-coverage-controls{padding:16px 20px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;border-top:1px solid var(--border)}
.variant-coverage-controls label{display:grid;gap:6px;color:var(--text-muted);font-size:11px;letter-spacing:0;text-transform:none;font-weight:400}
.variant-coverage-controls .input{width:100%;min-width:0;height:38px;font-size:12px;border-radius:8px}
.variant-coverage-controls .variant-coverage-search{grid-column:1/-1}
.variant-coverage .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.variant-coverage-table{width:100%;border-collapse:collapse;font-size:12px;text-align:left}
.variant-coverage-table th,.variant-coverage-table td{padding:14px 20px;border-top:1px solid var(--border);vertical-align:top}
.variant-coverage-table thead th{color:var(--text-muted);font-size:10px;font-weight:500;letter-spacing:0;text-transform:none}
.variant-coverage-table tbody th{width:40%;font-weight:500;overflow-wrap:anywhere}
.variant-coverage-table tbody th .meta{display:block;margin-top:4px;font-size:10px}
.variant-coverage-table td:nth-child(2){width:28%}
.variant-quote-status{font-size:11px;line-height:1.5;color:var(--text-muted)}
.variant-quote-status.is-quoted{color:var(--good,#559b80)}
.variant-coverage-prices{display:grid;gap:4px;white-space:nowrap;font-variant-numeric:tabular-nums}
.variant-coverage-link{background:none;border:0;padding:0;margin-top:6px;font:inherit;font-size:11px;color:var(--text-muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.variant-coverage-link:hover{color:var(--text)}
.variant-coverage-reason{margin-top:6px;font-size:11px;line-height:1.5;color:var(--text-muted)}
.variant-coverage-reason summary{cursor:pointer;text-decoration:underline;text-underline-offset:3px}
.variant-coverage-reason p{margin:8px 0 0;white-space:pre-wrap;overflow-wrap:anywhere;max-width:32em}
.variant-coverage-footer{padding:12px 20px;display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--text-muted);font-size:11px;border-top:1px solid var(--border)}
.variant-coverage-footer>div{display:flex;gap:6px}
.variant-coverage-empty{padding:12px 20px}
@media(max-width:640px){
  .variant-coverage-summary{padding:16px;gap:10px;align-items:flex-start}
  .variant-coverage-muted{display:block}
  .variant-coverage-muted::before{content:""}
  .variant-coverage-controls{padding:14px 16px;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
  .variant-coverage-controls .variant-coverage-search{grid-column:auto;align-self:end}
  .variant-coverage .tablewrap{overflow:visible}
  .variant-coverage-table thead{display:none}
  .variant-coverage .variant-coverage-table tbody tr{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:5px 12px;margin:0;padding:14px 16px;border:0;border-top:1px solid var(--border);border-radius:0}
  .variant-coverage .variant-coverage-table tbody th,.variant-coverage .variant-coverage-table td,.variant-coverage .variant-coverage-table td:nth-child(2){display:block;width:auto;padding:0;border:0}
  .variant-coverage .variant-coverage-table tbody th{grid-column:1;grid-row:1}
  .variant-coverage .variant-coverage-table td:nth-child(2){grid-column:1;grid-row:2}
  .variant-coverage .variant-coverage-table td:nth-child(3){grid-column:2;grid-row:1/3;text-align:right}
  .variant-coverage .variant-coverage-table tbody td::before{display:none}
  .variant-coverage-reason p{max-width:100%}
  .variant-coverage-prices{font-size:12px}
}
