/* Promotions — page-scoped only. Everything else comes from app.css primitives. */

/* Health rail: three ordered levels in the row's leading edge. Encodes
   severity by position + colour, alongside the alert line in the cell stack
   (present whenever there is something wrong), so colour is never the sole
   carrier of a problem. */
.pm-health { display: inline-block; width: 3px; align-self: stretch; border-radius: 5px; flex: none; }
.pm-health--ok   { background: var(--color-success); }
.pm-health--warn { background: var(--color-warn); }
.pm-health--bad  { background: var(--color-danger); }
.pm-namecell { display: flex; gap: 10px; align-items: stretch; }
/* Name cell, line 3 — the alert. Critical is red; a milder one keeps the
   neutral sub tone, since the rail already ranks it. */
.pm-alert--critical { color: var(--color-danger); }

/* Start date under the Type badge — its own line, at the 11px the name
   cell's sub lines use, so it reads as meta and never fights the badge. */
.pm-date { display: block; font-size: 11px; }

/* Rows whose stock has run out are de-emphasised, never hidden. */
.pm-row--dry td { opacity: .55; }

/* Action queue: a live count of failing campaigns, not an instructional banner. */
.pm-queue {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; margin-bottom: var(--page-gap);
  background: var(--color-warn-50); border: 1px solid var(--color-warn);
  border-radius: 5px; font-size: 13px;
}
.pm-queue i { width: 16px; height: 16px; color: var(--color-warn); flex: none; }
/* Here the queue is the block the tab strip hangs off, so it takes the same 25px
   standoff the KPI summary takes on every other page with attached tabs. */
.pm-queue:has(+ .tabs) { margin-bottom: 25px; }

/* The filter group that rides in the tab strip.

   Which list, then which part of it: the tabs answer the first and this answers
   the second, so it belongs on their line rather than in a band of its own at
   the top of the card. Trailing edge, so the two never read as one row of
   seven buttons; vertically centred against tabs that are taller than it.

   `flex: none` because the strip scrolls at narrow widths rather than squeezing
   its contents — a filter group that compresses would wrap its labels inside a
   pill. Only one group is ever up; each carries `data-for-tab`. */
.pm-tabfilter { margin: 0 0 0 auto; align-self: center; flex: none; }

@media (max-width: 900px) {
  .tabs[data-tabs] { overflow-x: auto; flex-wrap: nowrap; }
  .tabs[data-tabs] .tab { flex: 0 0 auto; white-space: nowrap; }
}

/* -------------------------------------------------------------------------
   Requests tab — status filter row

   The filter band and the tab preference belong on the same line: one asks
   which requests to show, the other asks where they open. Keeping the switch
   off the band's own fill stops a control from sitting on a panel colour.
   ------------------------------------------------------------------------- */
.pm-reqbar { display: flex; align-items: center; gap: 12px; margin: 0 12px 8px; }
.pm-reqbar .subtabs { margin: 0; }
/* Pushed to the trailing edge, and never wrapped mid-phrase: a two-line
   "Open in / New Tab" beside a single-row band reads as a broken control. */
.pm-reqbar__newtab { margin-left: auto; white-space: nowrap; }

@media (max-width: 760px) {
  .pm-reqbar { flex-wrap: wrap; }
  .pm-reqbar__newtab { margin-left: 0; }
}

/* -------------------------------------------------------------------------
   Requests table

   Moved here from the page's own <style> so the QTY rules below sit with the
   rules they belong to rather than in the markup.

   Account and Request are one column: the request id sits after the contact
   name on the sub line, at the sub line's size, because nobody reads a request
   id without the account it came from. It keeps the subtle grey of
   `.cell-stack .sub`, so its link affordance is a hover colour — never an
   underline.
   ------------------------------------------------------------------------- */
#pmReqTable .cell-stack .sub a { color: inherit; text-decoration: none; }
#pmReqTable .cell-stack .sub a:hover { color: var(--color-primary-700); }

/* Date and Value are attributes of one request, not a column of figures to be
   totalled down the page — so they are one cell rather than two columns, the
   amount over the date it was asked on. Centred for the reason they always
   were; `.num` is the right-aligned variant, so its nowrap is restated here,
   and the figures keep `.tnum`. */
#pmReqTable .col-value { text-align: center; white-space: nowrap; }

/* QTY. A request is rarely one thing — two materials, three slabs — and the
   Items column can only ever name the first of them. The count carries what
   the names cannot: how much of the request is not on screen.

   The two numbers are a pair, not a sentence, so the words that tell them
   apart are said once in the header and never repeated down the rows. The
   legend drops the header's uppercase and tracking on purpose: it is a caption
   for the column, not a second heading. */
#pmReqTable .col-qty { text-align: center; }
/* The pair never breaks; the legend above it may, and below a ~840px table it
   has to — it is the only thing in the row that costs nothing to read on two
   lines. */
#pmReqTable td.col-qty { white-space: nowrap; }

/* The two figures are not equals. Slabs is what the request is FOR — the number
   an operator holds against stock — while materials only says how many names it
   spans, so it steps down to 11px and lets the slab count take the weight. */
#pmReqTable .col-qty .mat { font-size: 11px; }
#pmReqTable .col-qty .slb { font-weight: 700; }
#pmReqTable .col-qty .th-legend {
  display: block;
  margin-top: 2px;
  font-size: 10px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-text-subtle);
}

/* -------------------------------------------------------------------------
   Price groups

   A promotion prices by client type: each group names the types it covers and
   the discount they get, and any product can be given its own price inside a
   group. The products table becomes a small matrix — one column per group —
   which is the shape the catalog Pricing grid already uses, so it is a table
   the tenant can already read.
   ------------------------------------------------------------------------- */

/* A cell someone set by hand, against one that just follows the group's
   discount. Without the distinction the matrix cannot say which numbers move
   when the group rate changes and which do not. */
.pm-price .off--own { color: var(--color-primary-700); font-weight: 600; }
.pm-pricebtn .off { margin-left: 0; }
/* The whole cell is the opener: clicking a price is how you change it, and a
   price you cannot click reads as a figure somebody else owns. */
.pm-pricebtn {
  /* Stacked, not side by side: a group column is narrow, and the figure and the
     reason it is that figure must both stay whole rather than share one line. */
  display: inline-flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 3px 7px; margin: -3px -7px;
  background: transparent; border: 0; border-radius: 5px;
  font: inherit; color: inherit; cursor: pointer;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pm-pricebtn:hover { background: var(--color-primary-50); color: var(--color-primary-700); }

/* ---- The group editor (composer) ----------------------------------------
   A group is a thing, so it gets a box. Full-width rows stranded a 66px
   discount field and a handful of chips across a thousand pixels of nothing,
   and made two groups read as one long form rather than as two of the same
   object. Boxes sit side by side, so comparing the rates — the whole point of
   having more than one — is a glance instead of a scroll.

   Flex rather than grid: the trailing add tile is 110px by convention and must
   not be stretched into a full-width cell the way auto-fill would. */
.pm-grpgrid { display: flex; flex-wrap: wrap; gap: 12px; align-items: stretch; }
/* The box sits on a --color-surface panel, so it takes the control ground: a
   white box on a white panel is a hairline pretending to be a box (memory.md
   §13). Its own controls then go white — §13 the other way up. */
.pm-grpbox {
  flex: 1 1 264px; max-width: 420px; min-width: 0;
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px; border-radius: 7px;
  border: 1px solid var(--color-control-border); background: var(--color-control);
}
.pm-grpbox .input { background: var(--color-surface); }
.pm-grpbox__head { display: flex; align-items: center; gap: 8px; }
.pm-grpbox__head .input { flex: 1; min-width: 0; font-weight: 600; }
.pm-grpbox__del { flex: none; }
.pm-grpbox__lbl {
  display: block; font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--color-text-subtle);
}
/* Label left, control pinned to the box's trailing edge — the setting-row
   convention. Used for the discount only; a label and a plain VALUE are never
   spread to opposite edges, so the client count rides inside its own label. */
.pm-grpbox__row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.pm-grpbox__n { margin-left: 6px; text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--color-text-muted); }
.pm-grpbox hr { height: 1px; background: var(--color-control-border); border: 0; margin: 0; }
/* Chips go white on the box, the way template parts do on a template card. */
.pm-grpbox .fchip { background: var(--color-surface); }
.pm-grpbox .fchip.on { background: var(--color-primary-50); }
/* A group covering nobody prices nobody. It is not an error — it is what a
   group looks like the moment it is created, and the moment its last client
   type leaves the audience — so it says what is missing rather than going red. */
.pm-grpbox__empty { font-size: 12px; color: var(--color-warn); }

/* The classical add tile, same one the warehouse grid uses. Sized here because
   in a flex row it must keep its 110px rather than share the row equally. */
/* Stretches to the boxes beside it; keeps its own height when it wraps onto a
   line of its own, where stretching would collapse it to a sliver. */
.pm-grpgrid .wh-add-tile { flex: 0 0 110px; align-self: stretch; }

/* A client type can be in one group only — a type already spoken for elsewhere
   is shown, not hidden, so the operator can see where it went and why this
   chip will not take. */
.pm-ct-taken { opacity: .45; cursor: not-allowed; }

/* A badge stacked over its caption. The badge must keep its own width — a stack
   stretches its children by default, and a full-width badge reads as a banner.
   Mirrors the rule the composer already carries for its client picker. */
.pm-typecell { align-items: flex-start; gap: 3px; }

/* -------------------------------------------------------------------------
   Numeric field with a unit affix

   A narrow number, not a text box: "3 days" is three characters of input and a
   full-width field would say otherwise. Shared by the composer and by the
   follow-up dialog on the detail page, which is why it is here rather than in
   either page's own head.
   ------------------------------------------------------------------------- */
.pm-affix { display: inline-flex; align-items: center; gap: 4px; }
.pm-affix .input { width: 50px; padding: 5px 7px; text-align: right; font-variant-numeric: tabular-nums; }
.pm-affix .input[disabled] { background: var(--color-surface-alt); color: var(--color-text-subtle); }
.pm-affix .u { font-size: 12px; color: var(--color-text-subtle); }

/* -------------------------------------------------------------------------
   Follow-up dialog

   The dialog is the only place these rules are read or set — the Overview card
   that mirrored them is gone. Only the vertical rhythm needs help: a modal body
   has its own padding, so the first row must not add its own on top of it.
   ------------------------------------------------------------------------- */
#modal-notify .modal-body { padding-top: 4px; padding-bottom: 4px; }
#modal-notify .pm-autorow:first-child { padding-top: 0; }
/* One control column, one right edge (memory.md §14). The unit takes a fixed
   track so "days", "hrs" and "items" do not each push their own number box to a
   different place — the fields line up and the switches sit level with them. */
#modal-notify .pm-affix .u { display: inline-block; width: 34px; }

/* -------------------------------------------------------------------------
   Audience tab — the tally columns

   Clients, rooms opened, requests, held and edits are counts of the same
   audience read five different ways, so they sit centred under their headings
   and read down as one block. Right alignment would be correct for figures
   that get compared digit by digit; these are one- and two-digit tallies, and
   pushing them apart to opposite edges of their columns hid that they are the
   same measure. The last column is never among them: on both tables it keeps
   the global right alignment and sits flush to the table's edge, which is what
   closes the row off.
   ------------------------------------------------------------------------- */
#pmAudTable th:nth-child(n+3):not(:last-child),
#pmAudTable td:nth-child(n+3):not(:last-child),
#pmRecipTable th:nth-child(n+4):not(:last-child),
#pmRecipTable td:nth-child(n+4):not(:last-child) { text-align: center; }

/* -------------------------------------------------------------------------
   Send dialog — the message label row

   The label keeps the left edge of the field it names; Save as Preset takes the
   right, level with it, so the action reads as belonging to the box below and
   not to the dialog. The button is pulled up by its own padding: a ghost button
   sitting on a 12px label would otherwise push the textarea down a whole row.
   ------------------------------------------------------------------------- */
.pm-msghead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pm-msghead .btn { margin: -5px 0; }
/* Two actions on that right edge now, and they are a pair: one fills the box, one keeps
   what is in it. They sit tight together so the pair reads as one cluster rather than as
   two unrelated buttons drifting apart. */
.pm-msghead__acts { display: flex; align-items: center; gap: 2px; }

/* -------------------------------------------------------------------------
   Message Presets — the row as a pick

   The shelf serves two visits. Opened from the gear, a row is a record to edit or
   delete. Opened from the message box, its name and opening words become the button
   that writes it — the row already shows what picking it would do, so nothing extra
   is added to say so. The negative margin lets the hover wash reach past the text
   without the row growing when it appears.
   ------------------------------------------------------------------------- */
.pm-preset__use {
  border: 0; background: none; font: inherit; color: inherit; text-align: left;
  cursor: pointer; padding: 6px 8px; margin: -6px -8px; border-radius: 5px;
}
.pm-preset__use:hover { background: var(--color-primary-50); }
.pm-preset__use:hover .m { color: var(--color-primary-700); }

/* -------------------------------------------------------------------------
   Send dialog — when it goes

   A binary choice, so both sides are named and the chosen one is dark: the reader
   never has to work out what the quiet side means. The date and time stand beside
   the toggle rather than under it — they finish the sentence it starts — and they
   are only on screen once Schedule is the answer, so the row is one line either way.
   ------------------------------------------------------------------------- */
.pm-when { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pm-when__at { display: flex; align-items: center; gap: 8px; }
.pm-when__at .input { width: auto; }
.pm-two {
  display: inline-flex; align-items: stretch;
  border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden;
}
.pm-two button {
  border: 0; background: var(--color-control); cursor: pointer;
  padding: 7px 13px; font: inherit; font-size: 12px; color: var(--color-text-muted);
}
.pm-two button + button { border-left: 1px solid var(--color-border); }
.pm-two button[aria-pressed="true"] { background: var(--color-text); color: var(--color-surface); font-weight: 600; }

/* -------------------------------------------------------------------------
   Products table — Suggestion

   The column is deliberately a smaller share of the table than its longest
   badge ("Remove or Substitute") needs on one line. A badge never wraps, so
   below roughly a 1400px table it would lean into Stock Status rather than
   give ground. In this column only, it wraps instead.
   ------------------------------------------------------------------------- */
#pmProdTable .col-sugg .badge { white-space: normal; }

/* -------------------------------------------------------------------------
   Products table — the product type

   Type had a column of its own and spent it on one word. It reads as what it
   always was — the first fact about the material — so it opens the meta line
   instead, ahead of the family and the origin, and drops the chip it wore: a
   filled pill on a line of plain grey text is a button that does nothing.
   ------------------------------------------------------------------------- */
#pmProdTable .pm-type { color: var(--color-text-muted); font-weight: 600; }
#pmProdTable .pm-type svg { width: 11px; height: 11px; vertical-align: -2px; }

/* -------------------------------------------------------------------------
   Activity feed — the time leads

   On a log read top to bottom, when a thing happened is what the eye scans, so
   it takes the first column and everything else starts at one left edge. The
   column is fixed at the width of the longest form the feed uses ("Yesterday")
   — sized to the values, not to each row, or the avatars would step in and out
   as the wording changed. `space-between` has to go with it: with the time
   first, it would have thrown the whole gap between the two.
   ------------------------------------------------------------------------- */
#pmFeed .pm-feedrow { justify-content: flex-start; }
#pmFeed .pm-feedmain { flex: 1; }
#pmFeed .pm-feedtime { width: 66px; padding-top: 2px; }

/* -------------------------------------------------------------------------
   Activity feed — the kind of event, before the words

   The initials that used to sit here said who, which the sentence beside them
   already says in bold. The glyph says what KIND of thing happened — she walked
   into her room, the concierge answered her, she asked to buy — which is what a
   reader scanning a log is actually sorting by. Glyphs are the ones the rest of
   the app already uses for those things, so the concierge bell here is the same
   bell as in the sidebar.

   Three tints, matched to the filter chips above the feed, not one per glyph: a
   colour per event type would be a legend nobody was given. Blue is the client
   doing something, grey is the yard or a rep, red is the one row that wants
   answering.
   ------------------------------------------------------------------------- */
.pm-feedicon {
  width: 28px; height: 28px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 5px;
  background: var(--color-primary-50); color: var(--color-primary-700);
  border: 1px solid var(--color-border);
}
.pm-feedicon svg { width: 14px; height: 14px; }
.pm-feedrow[data-cat="stock"] .pm-feedicon,
.pm-feedrow[data-cat="admin"] .pm-feedicon { background: var(--color-surface-alt); color: var(--color-text-muted); }
.pm-feedrow[data-cat="alert"] .pm-feedicon { background: var(--color-danger-50); color: var(--color-danger); }

/* -------------------------------------------------------------------------
   Refurbish

   Every other control in this toolbar is something the operator may do; this is
   the one that says the promotion has gone stale — products out of stock, prices
   no longer worth showing. It keeps the outline button's weight and size, because
   it is not destructive and must not read like Send Now, and takes the softest
   red in the system: enough to be found without a badge shouting on the page.
   ------------------------------------------------------------------------- */
.btn[data-modal-open="modal-refurbish"] {
  background: var(--color-danger-50);
  border-color: #FECACA;
  color: #B91C1C;
}
.btn[data-modal-open="modal-refurbish"]:hover {
  background: var(--color-surface);
  border-color: var(--color-danger);
  color: #B91C1C;
}

/* Refurbish is offered twice — the toolbar on Products, and a quick action tile on
   Overview. It is the same errand, so it carries the same colour in both places;
   a tile that stays grey reads as the fourth of four equal chores. The tile keeps
   its own resting fill logic — pale red at rest, white on hover — and its reason
   line stays grey, so the urgency sits on the action and not on the sentence. */
.pm-qtile[data-modal-open="modal-refurbish"] {
  background: var(--color-danger-50);
  border-color: #FECACA;
}
.pm-qtile[data-modal-open="modal-refurbish"] .m,
.pm-qtile[data-modal-open="modal-refurbish"] .m i { color: #B91C1C; }
.pm-qtile[data-modal-open="modal-refurbish"]:hover {
  background: var(--color-surface);
  border-color: var(--color-danger);
}

/* ---- Refurbish dialog: three grounds, not one -----------------------------
   The dialog is white, so a white panel on it was a hairline and nothing else,
   and the chips inside were a third white box on top of that. Every nested box
   now steps down to the control tint and the chips step back up to white, so
   dialog → panel → option reads in one glance. Same move as `.filter-grid`.
   -------------------------------------------------------------------------- */
#modal-refurbish .pm-rpanel {
  padding: 12px;
  background: var(--color-control);
  border: 1px solid var(--color-control-border);
  border-radius: 5px;
}
#modal-refurbish .pm-rpanel__head {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 10px; flex-wrap: wrap;
}
/* Name left, everything that acts on it right — the auto margin does the pushing,
   so the bin stays beside the badge instead of at the far end of an empty row. */
#modal-refurbish .pm-rpanel__head > .cell-stack { margin-right: auto; }
#modal-refurbish .pm-rpanel__lbl {
  display: block; margin-bottom: 8px;
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-muted); font-weight: 500;
}
#modal-refurbish .pm-rpanel .fchip:not(.on) {
  background: var(--color-surface); color: var(--color-text);
  border-color: var(--color-border-strong); box-shadow: var(--shadow-sm);
}
#modal-refurbish .pm-rpanel .fchip:not(.on):hover {
  color: var(--color-primary-700); border-color: var(--color-primary);
}

/* Remove leaves the option list and becomes the row's own bin — the same control
   the Products table carries, in the same place: the right edge of the row it
   removes. In here it does not fire on the spot, it marks: the dialog applies as
   a whole, and the count above tells the operator what the set is left with. */
#modal-refurbish .pm-drop {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none; padding: 0;
  background: none; border: 0; border-radius: 5px; cursor: pointer;
  color: var(--color-text-subtle);
  transition: background 120ms, color 120ms;
}
#modal-refurbish .pm-drop svg { width: 15px; height: 15px; }
#modal-refurbish .pm-drop:hover,
#modal-refurbish .pm-drop[aria-pressed="true"] {
  background: var(--color-danger-50); color: var(--color-danger);
}
#modal-refurbish .pm-atrisk.is-dropped .cell-stack .main { text-decoration: line-through; }
#modal-refurbish .pm-atrisk.is-dropped .pm-rpanel__lbl,
#modal-refurbish .pm-atrisk.is-dropped .fchips { opacity: .4; pointer-events: none; }
/* Fingertip size, keyed on the input and not on the width — same rule the Products
   table's Remove button follows. */
@media (max-width: 768px), (hover: none) {
  #modal-refurbish .pm-drop { width: 44px; height: 44px; }
}

/* -------------------------------------------------------------------------
   Collection — the name a promoted piece lands under in the client's room

   SEPARATE STORAGE, SHARED VOCABULARY: the product row carries a NAME, never a link
   to a storefront collection record. The control is app.css's own .combo, so the
   suggestion menu, the keyboard handling and the match highlight all come for free
   and nothing here re-implements them.
   ⚠ Scoped on .pm-prodbuilder / .pm-prodmatrix and never on #pmProdTable — that id
   is used by a DIFFERENT table on each of the two promotion pages.
   ------------------------------------------------------------------------- */
.pm-prodbuilder th.col-coll { width: 14%; }

/* The menu is absolutely positioned inside its .combo, so the cell is the containing
   block. Also what makes the toolbar copy of the control behave like the cell copy. */
.pm-collcell { position: relative; display: block; }
.pm-collinput { width: 100%; min-width: 0; }

/* ⛔ THE CONTAINMENT ANSWER — this is the first data-combo in a table cell anywhere in
   this prototype, and a table cell here sits inside a SCROLL CONTAINER.
   .combo-menu is position:absolute; top:calc(100% + 4px) (app.css:946-948) and
   .table-wrap is overflow-x:auto (app.css:1161-1162) — a box that scrolls on one axis
   computes the other to `auto`, so the wrap clips whatever paints outside its padding
   box. On the LAST row that clips the menu to nothing at all.
   The answer is room, given only while a Collection field has focus (pmCollRoom() on
   this page, pdCollRoom() on promotion-detail.html), plus a cap so the room needed is
   a known number rather than a guess:
     176px  six options at 28px, plus the menu's own 4px of padding
     184px  that, plus the 4px gap under the input and 4px of air
   ⚠ overflow:visible on .table-wrap is NOT the answer here, even though
   css/pages/estimates.css:423-424 does exactly that for its builder grid: that grid has
   no min-width and never scrolls sideways, while both promotion tables carry a min-width
   floor. Dropping the scroll would spill an eight-column matrix out of its card and
   reset scrollLeft to 0 on every focus. And the fix cannot live in app.css, which is
   master-only. */
.table-wrap.pm-collwrap--open { padding-bottom: 184px; }
.pm-collcell .combo-menu { max-height: 176px; }

/* Free text is a first-class answer on this field — the rep NAMES the shelf, they do not
   pick from a list — so the shared "No match yet" line would read as a verdict on a
   perfectly good name. The suggestions stay; the verdict goes.
   ⚠ Hiding the LINE is not enough, and this is the run's PRIMARY path, not an edge:
   app.js renders that line as the menu's only child when nothing matches (app.js:680)
   and creates the menu regardless (:690-693), so suppressing the text alone leaves an
   empty bordered, shadowed panel hanging under the field every time a rep types a NEW
   shelf name. Hide the panel whenever it holds no option. :has() is already used 42
   times across this prototype's stylesheets (15 of them in app.css), so it introduces
   no new dependency. */
.pm-collcell .combo-empty { display: none; }
.pm-collcell .combo-menu:not(:has(.combo-opt)) { display: none; }

/* The Emails branch hides the column rather than clearing it (PRM-W3: never silently
   rewrite what somebody typed). A <td> is display:table-cell, so say what hidden means
   here rather than trusting the UA sheet to win. */
.pm-prodbuilder th.col-coll[hidden],
.pm-prodbuilder td.col-coll[hidden] { display: none; }
#pmCollDefaultWrap[hidden] { display: none; }

/* The promotion-level default seeds NEW rows only and is stored nowhere. Its label is a
   sentence, not a field label, so it does not take .field label's uppercase mono. */
.pm-colldefault { display: inline-flex; align-items: center; gap: 6px; flex: 0 1 300px; min-width: 0; }
.pm-colldefault > label { font-size: 12px; font-weight: 400; color: var(--color-text-muted); text-transform: none; letter-spacing: normal; white-space: nowrap; }
.pm-colldefault .pm-collcell { flex: 1 1 auto; min-width: 0; }

/* The sentence under the table that stops a shelf name being read as a rule. */
.pm-collnote { display: block; margin-top: 6px; font-size: 12px; line-height: 1.45; color: var(--color-text-subtle); }
.pm-collnote strong { color: var(--color-text); font-weight: 600; }
.pm-collnote em { font-style: italic; }

@media (max-width: 768px) {
  /* 44px is the floor for anything tappable, and this one is typed into as well. */
  .pm-collinput { min-height: 44px; }
  .pm-colldefault { flex: 1 1 100%; }
}

/* -------------------------------------------------------------------------
   promotion-detail.html Products matrix — Collection after go-live

   Eight columns now, so the bounds are re-derived rather than inherited — and they move off the
   markup while they change, because memory.md §14 bans inline `style` and an eighth column forces
   every one of the seven inline widths to be rewritten anyway.
   Floors: Product 180 (the longest NAME on one line — its descriptor sub may wrap, the identity may
   not), Collection 150 (a 40-character name in an input), Type 84, each group 97, Available 84,
   Suggestion 130 (its widest single badge; the two are meant to stack) and Stock Status 114.
   Largest floor / its share sets 990; ceiling is 2.5x. The eight shares sum to exactly 100.
   ⚠ .pm-prodmatrix, NOT #pmProdTable — promotion-create.html uses that id for a different table. */
.pm-prodmatrix { min-width: 990px; max-width: 2475px; }
.pm-prodmatrix th.col-prod  { width: 20%; }
.pm-prodmatrix th.col-coll  { width: 12%; }
.pm-prodmatrix th.col-type  { width:  9%; }
.pm-prodmatrix th.col-grp   { width: 12%; }
.pm-prodmatrix th.col-avail { width:  9%; }
.pm-prodmatrix th.col-sugg  { width: 14%; }
.pm-prodmatrix th.col-stock { width: 12%; }

/* An emptied collection is loud here and silent in the room. Warn tone, not danger: nothing is
   broken — the pieces sold, which is the outcome the promotion wanted. */
.pm-collsub { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; font-size: 12px; color: var(--color-text-muted); }
.pm-collsub strong { color: var(--color-text); font-weight: 600; margin-right: 4px; }
.pm-collsub__zero { color: var(--color-warn); }
.pm-collsub__zero strong { color: var(--color-warn); }
