/* The ONLY stylesheet. Every page uses these tokens — no page-local :root
   blocks, which is how the reference platform ended up with 23 divergent
   copies of the same palette and a theme file full of per-page patches. */
:root {
  --bg: #f6f7f9;
  --panel: #ffffff;
  --text: #14181f;
  --muted: #5c6674;
  --border: #dfe3e8;
  --brand: #262262;
  --brand-accent: #27aae1;
  --brand-ink: #ffffff;
  /* One step off the panel: table headers, row hover, and any other surface
     that needs to read as "same card, slightly recessed". */
  --subtle: #f9fafb;
  --selected: #eff6ff;
  --green: #157347;
  --amber: #9a6700;
  --red: #b42318;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .1);
  /* Headings only — body copy stays on the system stack for speed and
     legibility at 15px. This is the one place a page should look designed
     rather than merely functional. */
  --font-display: 'Sora', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* Theme resolution. Light is the :root default above. Dark applies two ways:
   the header toggle stamps html[data-theme] and that choice WINS over the OS;
   absent a choice, the OS decides. See the init script in lib/layout.php.

   The dark token list appears twice on purpose — once for the manual override,
   once for the OS default — because a plain selector and one inside @media
   cannot share a block. KEEP THE TWO LISTS IN SYNC. */
:root { color-scheme: light; }

/* Manual dark: wins over the OS because [data-theme] out-specifies :root. */
:root[data-theme="dark"] {
  --bg: #0d1117; --panel: #161b22; --text: #e6edf3; --muted: #9198a1;
  --border: #2a313c; --brand: #27aae1; --brand-accent: #27aae1; --brand-ink: #06101f;
  --subtle: #1b222c; --selected: #17293f;
  --green: #3fb950; --amber: #d29922; --red: #f85149;
  --shadow: none;
  color-scheme: dark;
}

/* OS dark, but only with NO manual choice: any data-theme (light or dark)
   disables this block, so a chosen light theme survives an OS set to dark. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg: #0d1117; --panel: #161b22; --text: #e6edf3; --muted: #9198a1;
    --border: #2a313c; --brand: #27aae1; --brand-accent: #27aae1; --brand-ink: #06101f;
    --subtle: #1b222c; --selected: #17293f;
    --green: #3fb950; --amber: #d29922; --red: #f85149;
    --shadow: none;
    color-scheme: dark;
  }
}

* { box-sizing: border-box; }

/* `hidden` is an attribute, not a class, and a component rule that sets
   `display` beats the user-agent stylesheet's [hidden] rule. Without this a
   hidden .btn stays on screen — which is how the admin-only Save buttons were
   visible to Marketing. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
}

a { color: var(--brand); }

h1 { font-family: var(--font-display); font-weight: 700; letter-spacing: -.01em; }

.header {
  display: flex; align-items: center; gap: 16px;
  padding: 14px 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  /* A thin brand gradient rule, the one deliberately decorative touch in an
     otherwise utilitarian chrome — enough to read as "a product", not a tool.
     border-top has to carry the width/style; border-image only supplies paint. */
  border-top: 3px solid;
  border-image: linear-gradient(90deg, var(--brand), var(--brand-accent)) 1;
}
.header .brand { display: flex; align-items: center; }
.header .brand img { display: block; height: 26px; width: auto; }
.header nav { display: flex; gap: 4px; margin-left: auto; align-items: center; }

.wrap { max-width: 1080px; margin: 24px auto; padding: 0 24px; }

.footer {
  max-width: 1080px; margin: 40px auto 24px; padding: 16px 24px 0;
  border-top: 1px solid var(--border);
  color: var(--muted); font-size: 12px; text-align: center;
}

/* Catalog hero: the one full-bleed color moment in the app, sized down from a
   marketing site's since agents come here to do a task, not to browse. */
.hero {
  background: linear-gradient(120deg, var(--brand) 0%, var(--brand-accent) 130%);
  color: #fff;
  border-radius: var(--radius);
  padding: 32px 28px;
  margin-bottom: 20px;
  box-shadow: var(--shadow);
}
.hero h1 { margin: 0 0 6px; font-size: 28px; font-weight: 800; }
.hero p { margin: 0; font-size: 14px; opacity: .92; max-width: 46em; }

.eyebrow {
  font-family: var(--font-display);
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); margin: 24px 0 12px;
}

/* The funnel's step indicator. See layout_steps() — used on configure.php and
   checkout.php only; a history view is not a step in a flow. */
.steps { display: flex; align-items: center; gap: 8px; margin: 0 0 18px; font-size: 13px; }
.steps .step { color: var(--muted); }
.steps .step.done { color: var(--text); font-weight: 600; }
.steps .step.active { color: var(--brand); font-weight: 700; }
.steps .sep { color: var(--border); }

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px 24px;
}

.btn {
  display: inline-block; padding: 8px 14px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--panel); color: var(--text);
  text-decoration: none; font-size: 14px; cursor: pointer;
  transition: transform .08s ease, box-shadow .12s ease, border-color .12s ease, background .12s ease;
}
.btn:hover { border-color: var(--brand-accent); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--brand-accent); outline-offset: 1px; }
.btn.primary { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.btn.primary:hover { box-shadow: 0 4px 12px color-mix(in srgb, var(--brand) 35%, transparent); }
.btn:disabled { opacity: .55; cursor: default; transform: none; box-shadow: none; }

/* A consistent focus ring on every control, not only the ones inside a
   `.field`. Keyboard focus that lands somewhere invisible is the fastest way
   to make a form feel broken. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; border-color: var(--brand-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-accent) 25%, transparent);
}

/* --- The admin user directory -------------------------------------------
   A read-only table with one editor opening inside it. The controls used to
   sit in the cells, which needed eight columns, wrapped every name onto two
   lines and pushed the last button off the right edge. */

.queue.users tbody tr.row { cursor: pointer; }
.queue td.wrap-none { white-space: nowrap; }

/* Chips sat in a cell narrow enough to wrap their own text, which rendered
   "not signed in" as a circle. They are never allowed to wrap. */
.chip { white-space: nowrap; }

.pin { font-size: 11px; opacity: .85; }

.editor-host td { background: var(--subtle); }
.editor { display: flex; flex-direction: column; gap: 10px; padding: 4px 0 8px; }
.editor-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.editor-head .spacer { flex: 1; }
.editor-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.editor-label { min-width: 110px; font-size: 12px; color: var(--muted);
                text-transform: uppercase; letter-spacing: .04em; }
.pin-toggle { display: flex; gap: 4px; align-items: center;
              font-size: 12px; color: var(--muted); cursor: pointer; }
.editor-history { margin-top: 4px; border-top: 1px solid var(--border);
                  padding-top: 10px; overflow-x: auto; }

/* The add form: labels above their inputs, wrapping as a row rather than as a
   line of controls whose captions have drifted from them. */
.field-grid { display: flex; gap: 12px; flex-wrap: wrap; }
.field-grid label { display: flex; flex-direction: column; gap: 4px;
                    font-size: 12px; color: var(--muted); }
.field-grid input, .field-grid select { font: inherit; font-size: 14px; }

/* The admin entry in the header. Same button as its neighbours, outlined in
   the brand colour: it is the one control that leads to granting authority
   over money and over other people's data, and it should not read as just
   another tab. Only ever rendered inside `if (is_admin())`. */
.admin-link { border-color: var(--brand); color: var(--brand); }
.admin-link.primary { color: var(--brand-ink); }

/* The theme toggle in the header: a square icon button, its glyph set by JS. */
.theme-toggle {
  min-width: 38px; padding: 8px 10px;
  text-align: center; line-height: 1; font-size: 15px;
}

.chip {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; border: 1px solid var(--border);
}
.chip.full { color: var(--green); border-color: currentColor; }
.chip.half { color: var(--amber); border-color: currentColor; }
.chip.none { color: var(--muted); }

.dl { display: grid; grid-template-columns: 190px 1fr; gap: 10px 20px; margin: 0; }
.dl dt { color: var(--muted); }
.dl dd { margin: 0; }

.muted { color: var(--muted); }
.notice {
  border-left: 3px solid var(--amber); background: var(--panel);
  padding: 12px 16px; border-radius: 6px; margin-bottom: 16px;
}

/* Catalog */
.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.product {
  text-decoration: none; color: inherit; display: block;
  padding: 0; overflow: hidden;
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.product:hover {
  border-color: var(--brand-accent);
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(16, 24, 40, .1);
}

/* The artwork bleeds to the card's edges, like a product photo — the one
   place .product overrides .card's padding, pushed onto .product-body
   instead. Fixed aspect ratio so a row of cards never jumps as images load. */
.product-thumb {
  aspect-ratio: 4 / 3;
  background: var(--subtle);
  display: flex; align-items: center; justify-content: center;
  border-bottom: 1px solid var(--border);
}
.product-thumb img { width: 100%; height: 100%; object-fit: contain; padding: 14px; }
.product-thumb .placeholder-icon { font-size: 26px; opacity: .3; }

.product-body { padding: 16px 20px 20px; }
.product-name { font-weight: 600; margin-bottom: 4px; }
.product-desc { font-size: 13px; min-height: 2.6em; }
.product-price { margin-top: 12px; font-weight: 600; color: var(--brand); }
.product-price .muted { font-weight: 400; font-size: 13px; color: var(--muted); }
.product-price .from { font-size: 12px; }
.product-price s { margin-left: 4px; }
.subsidy-badge {
  margin-top: 8px; font-size: 12px; font-weight: 600; color: var(--green);
  border: 1px solid currentColor; border-radius: 999px;
  padding: 2px 8px; display: inline-block;
}

/* Configure: form beside the proof, stacking on narrow screens. */
.configure { display: grid; gap: 20px; grid-template-columns: minmax(320px, 1fr) minmax(320px, 1.2fr); align-items: start; }
@media (max-width: 820px) { .configure { grid-template-columns: 1fr; } }

/* The proof stays in view while a long field list scrolls — mirrors a
   product page's sticky gallery. Only above the stacking breakpoint: once
   the columns stack, "sticky" would pin the proof over the fields below it. */
@media (min-width: 821px) {
  .preview-card { position: sticky; top: 16px; }
}

.field { display: block; margin-bottom: 16px; }

/* A required field, and a required field somebody tried to skip.
   The asterisk is decoration for a screen reader -- the input carries
   aria-required, which is the part assistive technology acts on -- so it is
   hidden from the accessibility tree rather than read out as "star". */
.req { color: var(--red); margin-left: 2px; }
.field.invalid input,
.field.invalid select,
.field.invalid textarea {
  border-color: var(--red);
  background: color-mix(in srgb, var(--red) 7%, var(--bg));
}
.field.invalid .field-label { color: var(--red); }
.field-error { color: var(--red); font-size: 12px; margin-top: 4px; }
/* A sentence explaining a control, under the control. Beside the label it
   competes with it for one line and reads as part of the name of the thing. */
.field-help {
  display: block; font-size: 12px; color: var(--muted);
  margin: 4px 0 0; line-height: 1.4;
}
.check + .field-help { margin: 2px 0 10px 26px; }
.field-label {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 550; color: var(--text); margin-bottom: 6px;
}

/* One control treatment for the whole app, by element rather than by class.
   `.field input` covered an input inside a `.field` and nothing else, so a
   select was a raw browser dropdown next to a styled text box and the form
   read as half-finished. */
.field input, .field select, .qty-row select, select, textarea {
  width: 100%; padding: 9px 11px; font: inherit;
  border: 1px solid var(--border); border-radius: 8px;
  /* background-COLOR, not the shorthand. The shorthand resets every background
     sub-property, and `.field select` out-specifies the bare `select` rule
     that paints the chevron below — so a select inside a field lost its
     `no-repeat` and tiled the arrow right across the control. */
  background-color: var(--panel); color: var(--text);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.field input:hover, .field select:hover, textarea:hover { border-color: var(--muted); }

/* A native select paints its own arrow in a system style that matches nothing
   else here, so the control is reset and given one. `padding-right` reserves
   the space the chevron sits in; without it a long option runs underneath. */
select {
  appearance: none;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%235c6674' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 12px;
  cursor: pointer;
}
:root[data-theme="dark"] select,
:root:not([data-theme="light"]) select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%239198a1' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* The browser draws these in its own accent otherwise, which on a blue-branded
   form reads as a control from a different application. */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--brand-accent);
  width: 16px; height: 16px; cursor: pointer;
}

/* A date input's spinner and clear button are Chrome's, and they sit in the
   padding rather than beside the text. The picker icon is the only part worth
   keeping. */
input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .6; }
input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }

input:disabled, select:disabled, textarea:disabled {
  background: var(--subtle); color: var(--muted); cursor: not-allowed;
}
::placeholder { color: var(--muted); opacity: .75; }

/* Scoped to the element rather than to `.field`, because two of the four
   textareas are not inside one and so went unstyled entirely.

   `resize` is off: a dragged size is an inline style no layout rule can
   contain, which is what let these stretch out past their card. autosize() in
   app.js grows the box to fit instead, up to max-height, past which it scrolls
   rather than pushing the submit button off the screen. */
textarea {
  display: block;
  width: 100%; max-width: 100%; min-height: 88px; max-height: 40vh;
  padding: 8px 10px; font: inherit;
  border: 1px solid var(--border); border-radius: 8px;
  background-color: var(--panel); color: var(--text);
  resize: none; overflow-y: auto;
}
.qty-row { display: block; margin-top: 20px; }
.qty-row span { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; }

/* Which language's artwork an order prints on. Sits directly above Quantity
   and borrows its label treatment, because the two are the same kind of
   question — a property of the order rather than a value on the card. Hidden
   entirely on a single-language product, which is most of the catalogue. */
.lang-row { display: block; margin-top: 20px; }
.lang-row .field-label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; }
.lang-row .seg { flex-wrap: wrap; }

/* The languages a catalog card comes in. Deliberately quiet — it is a fact
   about the product, not a call to action, and it sits under the name where a
   second line of description would. */
.product-langs { font-size: 12px; color: var(--muted); margin-top: 2px; }

.price-box { margin-top: 20px; border-top: 1px solid var(--border); padding-top: 12px; }
.price-row { display: flex; justify-content: space-between; padding: 3px 0; font-size: 14px; }
.price-row.total { font-weight: 650; font-size: 16px; border-top: 1px solid var(--border); margin-top: 6px; padding-top: 8px; }

.preview-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.preview-frame {
  background: repeating-conic-gradient(var(--border) 0% 25%, transparent 0% 50%) 50%/16px 16px;
  border: 1px solid var(--border); border-radius: 8px;
  min-height: 200px; display: flex; align-items: center; justify-content: center; padding: 16px;
}
.preview-frame img { max-width: 100%; height: auto; box-shadow: 0 2px 12px rgba(0,0,0,.25); }

.msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-top: 10px; }
.msg.error { border-left: 3px solid var(--red); background: color-mix(in srgb, var(--red) 8%, transparent); }
.msg.warn  { border-left: 3px solid var(--amber); background: color-mix(in srgb, var(--amber) 10%, transparent); }

/* Orders */
.order-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  text-decoration: none; color: inherit; margin-bottom: 12px;
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.order-row:hover {
  border-color: var(--brand-accent);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(16, 24, 40, .08);
}

/* A fixed frame whatever the product is: a business card and a 33 x 80in
   banner are the same row in this list, and letting the proof set the row
   height makes it unscannable. */
.order-thumb {
  flex-shrink: 0; width: 140px; height: 84px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg);
  overflow: hidden; display: flex; align-items: center; justify-content: center;
}
.order-thumb img { width: 100%; height: 100%; object-fit: contain; }
.order-thumb.empty { font-size: 22px; color: var(--muted); }
.order-thumb.pending { font-size: 11px; }

.order-title {
  font-weight: 600; font-size: 15px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.order-title .muted { font-weight: 400; }

@media (max-width: 560px) {
  .order-thumb { width: 88px; height: 53px; }
}

/* Skeleton placeholders while the catalog/orders list fetches, instead of a
   blank grid. Same .card shell so nothing shifts when the real content
   swaps in. */
@keyframes skeleton-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
.skeleton { pointer-events: none; }
.skeleton-block {
  background: var(--subtle); border-radius: 6px;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}

/* A styled empty state reads as "there is nothing here yet", not "this broke". */
.empty-state {
  text-align: center; padding: 48px 24px; color: var(--muted);
}
.empty-state .empty-icon {
  width: 40px; height: 40px; margin: 0 auto 12px; border-radius: 50%;
  background: var(--subtle); display: flex; align-items: center; justify-content: center;
  font-size: 18px;
}

/* --- Approval queue ------------------------------------------------------
   A dense table rather than a grid of cards. The failure mode this guards
   against is documented in docs/PLAN.md: a dashboard that forces a click into
   every order stops being read, and the queue becomes a rubber stamp. Every
   value a reviewer needs to accept or reject a routine order is on the row. */
.queue { width: 100%; border-collapse: collapse; font-size: 14px; }
.queue th, .queue td { padding: 10px 12px; text-align: left; vertical-align: top;
                       border-bottom: 1px solid var(--border); }
.queue th { font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
            color: var(--muted); font-weight: 600; background: var(--subtle);
            position: sticky; top: 0; z-index: 1; }
.queue tbody tr[data-id] { cursor: pointer; }
.queue tbody tr:hover { background: var(--subtle); }
.queue tr.selected { background: var(--selected); }
.queue td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Chips and durations: a column of short centred tokens reads as a column.
   Left-aligned they drift against the wide cell beside them. */
.queue th.mid, .queue td.mid { text-align: center; }
/* The order's heading and what it needs, on one line: the action belongs beside
   the thing it acts on, not two screens below it. */
.order-head { display: flex; align-items: flex-start; justify-content: space-between;
              gap: 16px; flex-wrap: wrap; margin: 0 0 16px; }
.order-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* The work on the left, the conversation on the right. An order is read as
   both — what it looks like, and what has been said about it — and putting the
   second below the first meant scrolling past the artwork to find out why an
   order had sat for nine days. */
.order-grid { display: grid; gap: 28px; align-items: start;
              grid-template-columns: minmax(0, 1.65fr) minmax(300px, 1fr); }
@media (max-width: 940px) { .order-grid { grid-template-columns: 1fr; } }
.order-main { display: flex; flex-direction: column; gap: 20px; }
.order-side { border-left: 1px solid var(--border); padding-left: 24px; }
@media (max-width: 940px) {
  .order-side { border-left: none; border-top: 1px solid var(--border);
                padding: 20px 0 0; }
}
.side-title { margin: 0 0 12px; font-size: 12px; text-transform: uppercase;
              letter-spacing: .05em; color: var(--muted); }

/* Reference rather than working material: there when somebody dispatches the
   parcel or asks what it cost, folded away the rest of the time. Space and a
   rule rather than another card — everything on this page was a box, and a
   page of boxes has no hierarchy at all. */
.fold { border-top: 1px solid var(--border); margin-top: 32px; padding-top: 4px; }
.fold > summary { cursor: pointer; padding: 12px 0; font-weight: 600; font-size: 14px;
                  color: var(--muted); }
.fold[open] > summary { color: inherit; }

/* A chat log, because that is what it is. An EVENT is what the system did, so
   it reads as a quiet line with a dot; a COMMENT is a person talking, so it
   gets a name and a body. Same distinction any chat draws between a message
   and "so-and-so joined". */
.tl { display: flex; flex-direction: column; gap: 14px; }
.tl-event { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
            font-size: 13px; color: var(--muted); }
.tl-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--border);
          flex: 0 0 auto; }
.tl-time { margin-left: auto; font-size: 12px; color: var(--muted);
           white-space: nowrap; }
.tl-note { font-size: 12px; color: var(--muted); }

.comment-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
                font-size: 13px; margin-bottom: 6px; }
.comment-body { white-space: pre-wrap; font-size: 14px; }
.tl-tag { font-size: 11px; color: var(--muted); }

/* The box you type in sits under the thing you are replying to. */
.tl-compose { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.tl-compose-row { display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
                  margin-top: 8px; }
.tl-compose-row .btn { margin-left: auto; }
/* Why a row is not simply ready, under the status it qualifies. Text rather
   than more chips: three chips in a column of chips is a row that shouts. */
.row-notes { font-size: 12px; color: var(--amber); margin-top: 4px; }

/* The variable copy, under what was ordered. Muted, and the key dimmer than
   the value: the value is what somebody is checking, the key is only there so
   they know which field it is. */
.row-values { font-size: 12px; color: var(--text); margin-top: 3px; }
.row-values .k { color: var(--muted); margin-right: 4px; }

/* The shared control rule sets width:100%, which is right for a form and wrong
   for a toolbar — it took a whole row to itself and pushed the buttons onto a
   third one. */
.queue-select {
  width: auto; flex: 0 0 auto; max-width: 200px;
  font-size: 13px; padding: 6px 30px 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background-color: var(--card); color: inherit;
}

.queue td { vertical-align: middle; }

/* The card, which is what the page is about. A fixed frame, because a business
   card and a 33 x 80in banner are the same row here. */
.queue-thumb {
  width: 96px; height: 58px; border-radius: 5px; border: 1px solid var(--border);
  background: var(--bg); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.queue-thumb img { width: 100%; height: 100%; object-fit: contain; }
.queue-thumb.empty { font-size: 18px; color: var(--muted); }

.queue-title { font-weight: 600; }
.queue-title .muted { font-weight: 400; }

/* Four days into a roughly seven-day hold, the margin is going. */
.age-old { color: var(--amber); font-weight: 600; }

/* A product's pile. Not a card and not a nested table: a labelled break in one
   list, so the rows under it are still the same rows. */
.queue tr.group-row td { background: var(--subtle); border-bottom: none;
                         padding-top: 14px; }
.queue tr.group-row:hover td { background: var(--subtle); }
.group-name { font-weight: 650; margin-right: 8px; }
.group-row .linklike { margin-left: 10px; font-size: 12px; }
.group-toggle { display: flex; align-items: center; gap: 6px; font-size: 13px;
                color: var(--muted); white-space: nowrap; }


.queue-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
             margin: 0 0 14px; }
.queue-bar .spacer { flex: 1; }

/* Title over filters, rather than one long row of controls with the heading
   lost among them. */
.queue-head { display: flex; flex-direction: column; gap: 12px; margin: 0 0 14px; }
.queue-title-row { display: flex; align-items: center; justify-content: space-between;
                   gap: 12px; min-height: 34px; }
.queue-filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.chip.warn { color: var(--amber); border-color: currentColor; }
.chip.danger { color: var(--red); border-color: currentColor; }

.reject-panel { margin-top: 12px; }

/* White in both themes on purpose: a proof is ink on paper, and showing it on
   a dark ground misrepresents what comes off the press. */
.proof { max-width: 100%; border: 1px solid var(--border); border-radius: 6px;
         background: #fff; }

/* Every page side by side, wrapping when they do not fit. Side by side rather
   than behind a page control on purpose: an approver who has to click to see
   the back will approve without clicking. */
.proof-pages { display: flex; flex-wrap: wrap; gap: 14px; margin: 0 0 12px; }
.proof-pages figure { margin: 0; flex: 1 1 260px; min-width: 0; }
.proof-pages figcaption { font-size: 12px; margin-top: 4px; }

/* The configure page's page nav. Empty on a one-page product, so the margin
   only applies when there is something to show. */
.page-nav:not(:empty) { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }


/* --- Template studio -----------------------------------------------------
   The canvas is the feature: Marketing places a field by dragging it onto the
   artwork, so nobody has to measure a box in InDesign and transcribe points
   into a form. Everything here exists to make that one interaction legible. */
.tabs { display: flex; gap: 4px; margin: 0 0 16px; border-bottom: 1px solid var(--border); }
.tab {
  padding: 8px 14px; border: 0; background: none; color: var(--muted);
  font: inherit; font-size: 14px; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab.active { color: var(--text); font-weight: 600; border-bottom-color: var(--brand); }
/* The admin area's tabs are LINKS rather than panel-switching buttons, so they
   need the two things an <a> brings and a <button> does not. */
a.tab { text-decoration: none; }
a.tab:hover { color: var(--text); }

.panel-h { font-size: 15px; margin: 0 0 12px; }

/* Editor first, then what it produces. Reading order matches the order of the
   work: you change a field on the left and watch the result on the right. */
.studio { display: grid; gap: 16px; grid-template-columns: minmax(300px, 1fr) minmax(340px, 1.35fr); align-items: start; }

/* The agent's form, shown in place of the canvas on a product with no artwork.
   Deliberately the SAME controls the order page draws rather than a sketch of
   them: a picture of a form nobody fills in would be wrong first about
   whatever Marketing had just designed around. */
/* No max-width. The panel is already a column of the studio grid, and capping
   the form inside it left the artwork empty state above filling the card while
   the form stopped short of it. The real order page lets its form fill its
   column too, so this matches what the agent actually sees. */
.form-preview-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  font-weight: 650; color: var(--muted);
  margin: 0 0 14px; padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

/* Where something belongs but is not there yet. Dashed rather than solid: a
   solid box reads as a thing, and this is the absence of one. */
.empty-state {
  border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 22px 18px; text-align: center; color: var(--muted);
  font-size: 13px; background: var(--subtle);
}
.empty-state p { margin: 0 0 4px; }
.empty-state p:last-child { margin-bottom: 0; }
.empty-state strong { color: var(--text); font-size: 14px; }
.artwork-empty { margin-bottom: 20px; }

/* The catalog card, at the top of the column that shows what the agent sees.
   It sat on a tab called Artwork, which it is not: these are screen images
   that never go to print. */
.catalog-card-panel { margin-bottom: 24px; }

/* The tile at catalog scale rather than stretched across the panel. It is a
   preview of a card in a grid, and a full-width one is a preview of nothing
   that exists. */
.catalog-preview { max-width: 280px; }
.catalog-preview .product { cursor: default; }
.catalog-preview .product:hover { transform: none; }

/* The arithmetic, said out loud on every row. Without it "from 25, each $2"
   reads as a rate rather than a volume break, and nobody can tell whether 25
   costs $2 or $50. */
/* The total, said out loud on every row. Without it a row reads as a rate and
   nobody can tell whether 25 at $2 costs $2 or $50. */
.option-each { font-size: 13px; color: var(--muted); white-space: nowrap; }
/* Four columns in a narrow editor column. The inputs were full-width, which is
   right in a form and wrong in a table cell holding a two-digit number. */
/* Both money tables live in the narrow editor column, so every cell is sized
   rather than left to the browser's guess. `fixed` is what stops a long
   heading pushing the last column off the card. */
#tierTable, #ruleTable { table-layout: fixed; width: 100%; }
#tierTable input, #ruleTable input { width: 100%; min-width: 0; padding: 6px 8px; }
#tierTable th, #tierTable td, #ruleTable th, #ruleTable td {
  padding: 6px 6px; overflow: hidden;
}
#tierTable th:first-child { width: 34%; }
#tierTable th:nth-child(2) { width: 24%; }
#tierTable th:nth-child(3) { width: 26%; }
#tierTable th:last-child   { width: 16%; }
#ruleTable th:first-child  { width: 40%; }
#ruleTable th:nth-child(2) { width: 26%; }

/* A unit that belongs to the input rather than trailing after it, which is
   where a bare "%" ends up on any column narrow enough to wrap. */
.input-suffix { display: flex; align-items: center; gap: 5px; }
.input-suffix span { color: var(--muted); font-size: 13px; flex: none; }

/* The same remove control as a gallery tile. A button reading "Remove" is
   three times the width of the number it removes. */
.row-x {
  width: 24px; height: 24px; padding: 0; line-height: 1;
  border: 1px solid var(--border); border-radius: 6px; cursor: pointer;
  background: var(--bg); color: var(--muted); font-size: 15px;
}
.row-x:hover { border-color: var(--red); color: var(--red); }

/* A heading a screen reader reads and nobody sees. The column holds a button
   with no visible label, and an empty <th> leaves it announced as nothing. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* What stops this publishing, asked when somebody reaches for Publish rather
   than on a tab they had to go and find. */
.gate-dialog {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--panel); color: var(--text);
  padding: 24px; max-width: 560px; width: calc(100% - 32px);
  box-shadow: 0 12px 40px rgba(16, 24, 40, .28);
}
.gate-dialog::backdrop { background: rgba(0, 0, 0, .45); }
.gate-dialog h2 { margin: 0 0 16px; font-size: 18px; font-family: var(--font-display); }
.gate-lead { margin: 0 0 8px; font-size: 14px; }
.gate-list { margin: 0 0 18px; padding-left: 0; list-style: none; }
.gate-list li {
  font-size: 13px; line-height: 1.5; padding: 8px 12px; margin-bottom: 6px;
  border-radius: 6px; border-left: 3px solid var(--border);
}
.gate-list .blocker {
  border-left-color: var(--red);
  background: color-mix(in srgb, var(--red) 8%, transparent);
}
.gate-list .warning {
  border-left-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 10%, transparent);
}
.gate-actions { display: flex; justify-content: flex-end; gap: 10px; }

/* A history entry is a sentence and a timestamp. It was a five-column table in
   a 300px card, which put the note past the edge of it. */
.history-row { padding: 10px 0; border-top: 1px solid var(--border); }
.history-row:first-child { border-top: 0; padding-top: 0; }
.history-line {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline;
  font-size: 14px;
}
.history-when { font-size: 12px; margin-top: 2px; }
.history-note { font-size: 13px; margin-top: 4px; line-height: 1.45; }

/* One bar, fixed over the page, for a screen that had five Save buttons in five
   cards. Invisible with nothing to do, so the page is calm rather than carrying
   a permanently dead button. */
.save-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; align-items: center; gap: 14px;
  padding: 12px 24px; font-size: 14px;
  background: var(--panel); color: var(--text);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 12px rgba(16, 24, 40, .10);
}
.save-bar .spacer { flex: 1; }
/* Room under the last card, so the bar never covers the thing somebody is
   about to save. Unconditional: a page that reflows when the bar appears moves
   the control under the cursor. */
body { padding-bottom: 72px; }

/* Preview shows the product in the agent's own order: the catalog tile they
   click, the form they fill in, then what prints. The source order suits the
   editing mode, where the canvas is the whole point, so the reordering is done
   here rather than by moving nodes between two parents. */
.canvas-card.previewing { display: flex; flex-direction: column; }
.canvas-card.previewing .canvas-bar { order: 0; }
.canvas-card.previewing .catalog-card-panel { order: 1; }
.canvas-card.previewing .form-preview { order: 2; }
.canvas-card.previewing #printedHead { order: 3; margin-top: 24px; }
.canvas-card.previewing .canvas-wrap { order: 4; }
.canvas-card.previewing .artwork-empty { order: 4; }
/* Small, and last. It is a reminder of what comes off the press, not the thing
   somebody opened preview to look at. */
.canvas-card.previewing .canvas-wrap { max-width: 300px; }

/* Two modes, not two tabs of equal standing: one surface you work on, one you
   look at. Segmented rather than a pair of buttons, because they are states of
   the same panel rather than two separate actions. */
.mode-switch {
  display: inline-flex; padding: 2px; gap: 2px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg);
}
.mode-btn {
  border: 0; background: none; cursor: pointer; font: inherit; font-size: 13px;
  padding: 4px 12px; border-radius: 6px; color: var(--muted);
}
.mode-btn:hover { color: var(--text); }
.mode-btn.active { background: var(--panel); color: var(--text); font-weight: 550; }

/* Whether a field prints. The difference decides everything else about it, so
   it is visible in the list rather than only inside the editor. */
.field-mark {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  border: 1px solid var(--muted);
}
.field-mark.placed { background: var(--brand-accent); border-color: var(--brand-accent); }

/* The left half of the studio: everything you set, stacked. It scrolls with
   the page while the preview beside it stays put, because the preview is what
   you are looking at while you work down this column. */
.editor-column { display: block; min-width: 0; }
@media (min-width: 901px) {
  .studio > .preview-column { position: sticky; top: 16px; }
}

/* Drop a file, or click it. The two-step it replaces put a button between
   deciding and doing, and that button did nothing until a hidden first step
   had been taken. */
.dropzone {
  border: 1px dashed var(--border); border-radius: var(--radius);
  background: var(--subtle); padding: 20px 16px; text-align: center;
  cursor: pointer; transition: border-color .12s ease, background .12s ease;
}
.dropzone:hover, .dropzone:focus-visible { border-color: var(--brand-accent); }
.dropzone:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-accent) 25%, transparent); }
/* A dragged file is over the box. Solid, because at this moment it IS a target
   rather than an empty space inviting one. */
.dropzone.over {
  border-style: solid; border-color: var(--brand-accent);
  background: color-mix(in srgb, var(--brand-accent) 8%, var(--subtle));
}
.dropzone-main { margin: 0; font-size: 14px; color: var(--text); }
.dropzone-link { color: var(--brand-accent); text-decoration: underline; }
.dropzone-hint { margin: 4px 0 0; font-size: 12px; color: var(--muted); }
.dropzone-status:empty { display: none; }
.dropzone-status { margin: 8px 0 0; font-size: 12px; color: var(--muted); }

/* The add tile at the end of a gallery strip. Same target, sized to stand
   beside the thumbnails instead of spanning the panel. */
.dropzone.compact {
  flex: none; width: 132px; min-height: 108px; padding: 12px 10px;
  display: flex; flex-direction: column; justify-content: center;
}
.dropzone.compact .dropzone-main { font-size: 14px; font-weight: 550; }
.dropzone.compact .dropzone-hint { font-size: 11px; line-height: 1.35; }

/* A gallery: thumbnails in a row, each with a remove button and a caption, and
   the drop target as the last tile. Stacked rows made three photographs taller
   than the form they sit beside. */
.tile-strip {
  display: flex; gap: 12px; align-items: flex-start;
  overflow-x: auto; padding-bottom: 6px;
}
.tile { flex: none; width: 132px; }
.tile-img {
  position: relative; height: 92px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--subtle);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.tile-img img { max-height: 100%; max-width: 100%; object-fit: contain; }
.tile-x {
  position: absolute; top: 4px; right: 4px;
  width: 22px; height: 22px; line-height: 1; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(0, 0, 0, .62); color: #fff; font-size: 15px;
}
.tile-x:hover { background: var(--red); }
.tile-badge {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--brand); color: var(--brand-ink);
  font-size: 10px; text-align: center; padding: 2px 0;
}
.tile .example-alt {
  width: 100%; margin-top: 6px; padding: 5px 7px;
  font: inherit; font-size: 12px; border: 1px solid var(--border);
  border-radius: 6px; background-color: var(--panel); color: var(--text);
}
.tile-move { display: flex; gap: 4px; margin-top: 6px; }
.tile-move .btn { flex: 1; padding: 2px 0; font-size: 13px; line-height: 1.2; }
@media (max-width: 900px) { .studio { grid-template-columns: 1fr; } }

.canvas-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.canvas-bar .spacer { flex: 1; }
.page-btn { padding: 4px 10px; font-size: 13px; }

/* The checkerboard reads as "this is artwork, not page background" — a white
   card on a white panel gives no edge to judge the bleed against. */
.canvas-wrap {
  position: relative; user-select: none; touch-action: none;
  background: repeating-conic-gradient(var(--border) 0% 25%, transparent 0% 50%) 50%/16px 16px;
  border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
  min-height: 180px;
}
.canvas-wrap img { display: block; width: 100%; height: auto; }
.canvas-empty { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; }

/* The rendered proof, laid over the base artwork rather than replacing it.
   Same width, same origin, so it cannot disagree with the boxes about where
   anything is; and it can be absent or mid-reload without the panel resizing
   under the pointer. */
.proof-layer { position: absolute; left: 0; top: 0; width: 100%; }

.proof-state { font-size: 12px; padding: 2px 8px; border-radius: 999px; }
.proof-state.busy { background: var(--bg); color: var(--muted); }
.proof-state.good { background: var(--bg); color: var(--muted); }
.proof-state.bad  { background: color-mix(in srgb, var(--red) 14%, transparent);
                    color: var(--red); }
.proof-note.warn { color: var(--amber); }
.proof-note.bad  { color: var(--red); }

/* Where the sheet gets cut. Anything outside it is bleed and will not survive. */
.trim-guide { position: absolute; pointer-events: none; border: 1px dashed var(--brand); opacity: .55; }

.fbox {
  position: absolute; cursor: move;
  border: 1px solid var(--brand);
  background: color-mix(in srgb, var(--brand) 12%, transparent);
}
.fbox.selected { border-width: 2px; background: color-mix(in srgb, var(--brand) 22%, transparent); }
.fbox-label {
  position: absolute; bottom: 100%; left: -1px; white-space: nowrap;
  font-size: 11px; line-height: 1.4; padding: 0 4px;
  background: var(--brand); color: var(--brand-ink); border-radius: 3px 3px 0 0;
}
/* Where the text will actually sit. Drawn from the face's own ascent and
   descent, shipped from the server so it is the same arithmetic the renderer
   does — a box tells you the area, this tells you the line. */
.fbox-baseline {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px solid var(--brand); opacity: .8; pointer-events: none;
}
.fbox-handle {
  position: absolute; right: -5px; bottom: -5px; width: 11px; height: 11px;
  background: var(--brand); border: 1px solid var(--brand-ink); border-radius: 2px;
  cursor: nwse-resize;
}

.field-list { list-style: none; margin: 0 0 16px; padding: 0; max-height: 220px; overflow: auto; }
.field-list li {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px;
  border: 1px solid transparent; border-radius: 6px; cursor: pointer; font-size: 14px;
}
.field-list li:hover { background: var(--bg); }
.field-list li.selected { border-color: var(--brand); background: var(--bg); }
.field-list code { font-size: 12px; margin-left: auto; }

.props { border-top: 1px solid var(--border); padding-top: 14px; }
.props .field { margin-bottom: 10px; }
.props input, .props select, .props textarea {
  width: 100%; padding: 7px 9px; font: inherit; font-size: 14px;
  border: 1px solid var(--border); border-radius: 6px;
  /* background-COLOR for the same reason as the shared control rule: the
     shorthand would reset the chevron this out-specifies. */
  background-color: var(--bg); color: var(--text);
}
/* Room for the chevron. Shorter than the shared rule's because the sidebar is
   narrow and every pixel of it is a character of a value. */
.props select { padding-right: 30px; background-position: right 9px center; }
/* A checkbox is not a text box. Without this it stretches the full column and
   the label beside it wraps into a second line. */
.props input[type="checkbox"] { width: auto; padding: 0; }

.grid-2 { display: grid; gap: 0 12px; grid-template-columns: 1fr 1fr; }
.grid-3 { display: grid; gap: 0 10px; grid-template-columns: repeat(3, 1fr); }
.grid-4 { display: grid; gap: 0 10px; grid-template-columns: repeat(4, 1fr); }

/* A tickable row, not a bare box with words after it. The padding gives the
   whole row a hit area, which is what people actually aim at. */
.check {
  display: flex; align-items: flex-start; gap: 10px; font-size: 14px;
  margin: 4px 0; padding: 7px 9px; border-radius: 8px;
  border: 1px solid transparent; cursor: pointer;
}
.check:hover { background: var(--subtle); border-color: var(--border); }
.check input { margin-top: 2px; flex: none; }
.check.inline { margin: 0; padding: 0; border: 0; }
.check.inline:hover { background: none; border-color: transparent; }

.upload-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* The studio's language tabs. One product, one design, one artwork per
   language — the tab picks which file the canvas is drawing boxes onto. The
   fields below belong to the product and do not move between them. */
.lang-tabs {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 14px; padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
.lang-tabs .spacer { margin-left: auto; }
.lang-tabs .seg { flex-wrap: wrap; }
.lang-tabs .seg-btn .chip { margin-left: 6px; }

/* Example art on the ORDERING page: Marketing's photographs of the finished
   piece, under the live proof. `#examples` in `shop/configure.php` is the only
   thing that carries this class — the studio's management list is `.tile-strip`
   below, and the comment here claimed the studio for long enough that this was
   nearly given a modifier class to distinguish it from a page that does not
   use it.

   Sized to be looked at (#77). At 64px these were a coloured rectangle — you
   could tell there was a card in it and nothing else — while the browser is
   already served a 1200px derivative (#33). It matters here more than a
   thumbnail normally would: the proof above is the agent's own copy, and this
   is the only thing on the page showing the finished OBJECT, which is what
   they came to look at. One example fills the column, several share it, and a
   click opens the derivative at full size. */
.example-strip { padding-top: 12px; margin-top: 12px; border-top: 1px solid var(--border); }
.example-strip .example-shots { display: grid; gap: 10px; margin-top: 8px;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); align-items: start; }
.example-strip a { display: block; cursor: zoom-in; }
.example-strip img { height: auto; width: 100%; max-height: 460px; object-fit: contain;
  border-radius: 6px; border: 1px solid var(--border); background: var(--bg); }

/* Comments on an order. Internal and shared must be distinguishable AT A
   GLANCE — a flag people cannot see is a flag people stop trusting, and they
   go back to saying it over Teams. The left rule does that work without
   colour alone carrying it: the caption underneath says which it is in words. */
.comment { border-left: 3px solid var(--border); padding: 2px 0 2px 10px;
  white-space: pre-wrap; }
.comment.internal { border-left-color: var(--muted); border-style: dashed; }
.comment.shared { border-left-color: var(--brand); }
/* In the timeline it reads as a message rather than an aside. */
.tl .comment { border-radius: 0 8px 8px 0; padding: 10px 12px;
               background: var(--subtle); }

/* A button that reads as a link. Withdraw sits in a caption line, where a real
   button would shout louder than the comment it belongs to. */
.linklike { background: none; border: 0; padding: 0; font: inherit;
  color: var(--brand); text-decoration: underline; cursor: pointer; }
.linklike:disabled { opacity: .55; cursor: default; }
.money-actions { display: flex; gap: 8px; margin-top: 12px; }

.queue td input[type="number"] { width: 110px; padding: 6px 8px; font: inherit;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--bg); color: var(--text); }

/* One photo box on one order line, on the order page. Separated rather than
   boxed: a card can carry two of these and they are different pieces of work
   with different files. */
.artwork-field { padding: 10px 0; border-top: 1px solid var(--border); }
.artwork-field:first-child { border-top: 0; }

/* The photo work, and the copy edit, INSIDE the item card rather than beside
   it. Both are about the thing the proof above them is a picture of, and a
   panel of their own would put a card between the proof and the reason it
   looks the way it does. The inset ground is what says "this is work on the
   item" rather than another section of the page. */
.artwork-panel, .edit-panel {
  margin-top: 16px; padding: 12px 14px; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--border);
}
.artwork-panel .upload-row { margin-top: 8px; }

/* The agent's own photograph, bounded rather than laid out. It arrives from a
   phone in any proportion, and "is this good enough to print as it is" is a
   question about the picture, not about the column it is in. */
.headshot {
  max-height: 160px; max-width: 100%; width: auto; border-radius: 6px;
  border: 1px solid var(--border); background: var(--card);
  margin: 4px 0 8px; display: block;
}

/* A file input that reads as a control rather than as a browser default. The
   label carries the name of what the file IS — a cut-out and a photograph land
   two rows apart and take completely different files. */
.file-pick { display: inline-flex; align-items: center; gap: 8px; font-size: 13px;
  color: var(--muted); }
.file-pick input[type="file"] { font: inherit; font-size: 12px; max-width: 240px; }

.edit-field { display: block; margin: 8px 0; font-size: 13px; }
.edit-field span { display: block; color: var(--muted); margin-bottom: 3px; }
.edit-field input { width: 100%; padding: 7px 9px; font: inherit;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--card); color: var(--text); }

/* A segmented filter: one control, several mutually exclusive views of ONE
   list. Buttons rather than links because nothing navigates — the queue is
   already loaded, and a filter that reloaded the page would lose the count
   beside it. */
.seg { display: inline-flex; gap: 2px; }
.seg .btn { border-radius: 0; }
.seg .btn:first-child { border-top-left-radius: 6px; border-bottom-left-radius: 6px; }
.seg .btn:last-child { border-top-right-radius: 6px; border-bottom-right-radius: 6px; }

/* Clearing a chosen photo. A destructive-ish control that must not read as
   one: it removes a choice, not a file, and the agent can pick again. Sized to
   the touch target rather than to the glyph. */
.clear-photo {
  border: 1px solid var(--border); background: var(--card); color: var(--muted);
  border-radius: 6px; width: 28px; height: 28px; line-height: 1; cursor: pointer;
  font-size: 15px; padding: 0;
}
.clear-photo:hover { color: var(--red); border-color: var(--red); }


/* The field properties panel, in sections. It was one flat run of inputs
   named after database columns; the headings are what let somebody find the
   control they want without reading all of them. */
.props-h {
  font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted); font-weight: 600;
  margin: 18px 0 8px; padding-top: 12px; border-top: 1px solid var(--border);
}
.props-h:first-of-type { margin-top: 12px; }
.props-h .muted { text-transform: none; letter-spacing: 0; font-weight: 400; }
.props-hint { font-size: 12px; margin: -4px 0 12px; }
/* Colour: a swatch and the hex code beside it, two views of one value. Both
   are offered because both are how a colour arrives — picked, or pasted off a
   brand sheet — and the sentence underneath says what it becomes in CMYK,
   which is the part that gets printed. */
.ink-row { display: flex; align-items: center; gap: 8px; }
.props .ink-row input[type="color"] {
  flex: 0 0 auto; width: 44px; height: 34px; padding: 2px; cursor: pointer;
}
.props .ink-row input[type="text"] {
  flex: 1 1 auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.props-check { align-self: end; padding-bottom: 8px; }

/* The proof shows an agent's photograph before it has been retouched. The
   note saying so sits with the proof, not in the message list, because it is
   a standing fact about the picture rather than something that just went
   wrong. */
.photo-note {
  font-size: 12px; line-height: 1.5; margin: 8px 0 0;
  padding: 8px 10px; border-radius: 6px;
  background: var(--subtle); border: 1px solid var(--border);
}

/* Where an order ships. The agent picks between an assigned office and an
   address they type; before this there was no choice and no control, because
   the office on their user row was the only possible answer. */
.ship-box {
  border: 1px solid var(--border); border-radius: 8px;
  padding: 12px 14px; margin-top: 16px;
}
.ship-box legend { font-size: 13px; font-weight: 600; padding: 0 6px; }
.ship-choice {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 6px 0; cursor: pointer; font-size: 14px;
}
.ship-choice span { display: flex; flex-direction: column; gap: 1px; }
.ship-choice .muted { font-size: 12px; }
.ship-fields { margin-top: 10px; }

/* The address itself, on the one page that has to dispatch the parcel. */
.ship-block {
  margin-top: 12px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--subtle);
}

/* The line under a field whose answer is kept. Said on the form rather than
   only in a privacy page: a value stored without the person noticing is a
   value they cannot choose not to store, and the link is how they go and
   delete it. */
.saved-note { display: block; font-size: 12px; margin-top: 4px; }
