/* Stock layout — the studio (type strip, tree rail, piece panel, flat tables) and its
   column bounds. Moved verbatim out of stock.html's two inline <style> blocks so the
   consignee page (consignment-consignee.html) can wear the same layout. Linked where
   the first block stood, so the cascade order is unchanged. */
    /* Stock hierarchy tree-tables (blocks / bundles / slabs) — page-scoped */
    .stk-first { display: flex; align-items: center; gap: 8px; min-width: 0; }
    .stk-tree tbody tr[data-level="1"] .stk-first { padding-left: 22px; }
    .stk-tree tbody tr[data-level="2"] .stk-first { padding-left: 44px; }
    .stk-tree tbody tr[data-level="3"] .stk-first { padding-left: 66px; }
    .stk-toggle { width: 18px; height: 18px; flex: none; padding: 0; border: 0; background: transparent; cursor: pointer; color: var(--color-text-subtle); border-radius: 4px; display: inline-flex; align-items: center; justify-content: center; }
    .stk-toggle:hover { background: var(--color-surface-alt); color: var(--color-text); }
    .stk-toggle svg { width: 15px; height: 15px; }
    .stk-lead { width: 18px; flex: none; }
    .stk-ico { width: 16px; height: 16px; flex: none; color: var(--color-text-subtle); }
    .stk-tree tbody tr.stk-grp > td { background: var(--color-surface-alt); }
    .stk-tree tbody tr.stk-grp:hover > td { background: #EEF2FB; }
    .stk-name { font-weight: 600; color: var(--color-text); }
    .stk-thumb { width: 30px; height: 30px; font-size: 10px; }
    .stk-chip { display: inline-flex; align-items: center; font-size: 11px; font-weight: 600; color: var(--color-text-muted); background: #F1F5F9; border: 1px solid #E2E8F0; border-radius: 5px; padding: 0 6px; }
    /* Slabs master-detail — left nav to bundle level, right slab boxes */
    .slab-split { display: grid; grid-template-columns: 300px 1fr; border-top: 1px solid var(--color-border); }
    .slab-nav { border-right: 1px solid var(--color-border); padding: 10px 8px; max-height: 600px; overflow: auto; }
    .slab-nav .tree-label { cursor: default; }
    .slab-nav .tree-row.slab-nav-bundle { cursor: pointer; }
    .slab-nav .tree-row.slab-nav-bundle:hover { background: var(--color-surface-alt); }
    .slab-nav .tree-row.slab-nav-bundle.active { background: var(--color-primary-50); }
    .slab-nav .tree-row.slab-nav-bundle.active .tree-label { color: var(--color-primary-700); font-weight: 600; }
    .slab-nav .thumb.slab-navthumb { width: 22px; height: 22px; font-size: 10px; }
    .slab-nav .tree-node.nav-hidden { display: none; }
    .slab-nav.searching .tree-children { display: flex; }
    .slab-detail { padding: 12px 14px; min-width: 0; }
    .slab-detail__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
    /* The text column takes the room the buttons do not, so the description runs to the head's
       12px gap instead of stopping at an arbitrary measure. min-width:0 lets it wrap rather
       than force the head wider; the button row is flex:none so a long description cannot
       squeeze it. */
    .slab-detail__head > div:first-child { flex: 1; min-width: 0; }
    .slab-detail__head > .row { flex: none; }
    /* THE + BELONGS BESIDE WHAT IT ADDS TO. It adds a piece of the product this pane is
       describing, so it sits at that product’s name rather than in the button cluster on the
       right — the same place the tables put theirs, in the first cell of the header. The title,
       sub and description are written by JS through textContent, so the button is their SIBLING
       and never a child of one of them. */
    .slab-detail__id { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
    .slab-detail__idtext { min-width: 0; }
    /* .add-row-btn is nudged for the 11px table header it normally shares a line with; beside a
       17px title it needs neither the lift nor the trailing gap. */
    .slab-detail__id > .add-row-btn { margin: 3px 0 0; top: 0; }
    .slab-detail__desc { margin-top: 6px; font-size: 12px; color: var(--color-text-muted); line-height: 1.5; }
    /* THE SUB AND THE DESCRIPTION RUN TO THE RIGHT EDGE. Beside the three buttons the text
       column is only what they leave — 71px in a 920px window, where BLK-100 broke in two and
       "Taj Mahal · Bidasar, India" stood five lines tall. So a piece head is a grid: the +, the
       title and the button row share the first row, and the sub and the description each take
       a row of their own across to the head's right edge. The title centres on the 30px button
       row, as the + does. The two wrappers go display:contents so the grid places their children
       directly; they stay in the markup because the JS writes the title, sub and description
       through them and the + has to remain their sibling. The flat heads (samples, remnants)
       have no piece wrapper and keep the flex layout above. */
    .slab-detail__head:has(> .slab-detail__id) { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: auto auto auto; align-items: start; gap: 0; }
    .slab-detail__head:has(> .slab-detail__id) > .slab-detail__id,
    .slab-detail__head:has(> .slab-detail__id) .slab-detail__idtext { display: contents; }
    .slab-detail__head:has(> .slab-detail__id) .add-row-btn { grid-column: 1; grid-row: 1; align-self: center; margin: 0 8px 0 0; }
    .slab-detail__head:has(> .slab-detail__id) [data-detail-title] { grid-column: 2; grid-row: 1; align-self: center; }
    .slab-detail__head:has(> .slab-detail__id) [data-detail-sub] { grid-column: 2 / -1; grid-row: 2; }
    .slab-detail__head:has(> .slab-detail__id) .slab-detail__desc { grid-column: 2 / -1; grid-row: 3; }
    .slab-detail__head:has(> .slab-detail__id) > .row { grid-column: 3; grid-row: 1; margin-left: 12px; }
    /* Flat product-type panels get a header block so Import sits top-right, like the master-detail detail head */
    .slab-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
    /* Samples list — the pane answers who has a piece, when it went out and how many, so it is
     a list rather than a grid of identical photographs of the same stone. The status filter
     finds its items by `.slab-card`, so the rows keep that class and give back the look:
     `display:table-row` is the one that matters, since the card sets `display:flex` and a flex
     <tr> stops being a table row at all. */
  .smp-list { display: block; }
  .smp-list .data-table { width: 100%; table-layout: auto; }
  /* Columns by class, never by index — the page rule. Status takes the same 180px pin every
     other table's Status column has, Pieces is a one-digit count, and Client absorbs. */
  .smp-list thead th.col-client { min-width: 200px; }
  .smp-list .col-flex           { width: 100%; }
  .smp-list thead th.col-sent   { width: 260px; min-width: 260px; }
  .smp-list thead th.col-pieces { width:  90px; min-width:  90px; }
  .smp-list thead th.col-status { width: 180px; min-width: 180px; }
  .smp-list .slab-card,
  .smp-list .slab-card:hover { display: table-row; background: none; border: 0; border-radius: 0;
     box-shadow: none; cursor: default; transform: none; }
  .slab-card { text-align: left; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: 5px; overflow: hidden; cursor: pointer; padding: 0; box-shadow: var(--shadow-sm); display: flex; flex-direction: column; transition: box-shadow 120ms ease, border-color 120ms ease; font-family: inherit; }
    .slab-card:hover { border-color: var(--color-primary); box-shadow: 0 4px 12px rgba(15,23,42,.09); }
    .slab-card__body { padding: 9px 10px 10px; display: flex; flex-direction: column; gap: 6px; }
    .slab-card__row { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
    .slab-card__id { font-weight: 600; color: var(--color-primary-700); font-size: 12px; }
    .slab-card__sub { font-size: 11px; color: var(--color-text-subtle); }
    .slab-card__stats { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 10px; font-size: 12px; color: var(--color-text-muted); }
    .slab-card__stats > span { display: inline-flex; align-items: center; gap: 4px; }
    .slab-card__stats svg { width: 13px; height: 13px; }
    .slab-card__foot { display: flex; align-items: center; justify-content: space-between; border-top: 1px solid var(--color-border); padding-top: 7px; }
    .slab-card__num { font-size: 11px; color: var(--color-text-subtle); }
    .slab-card__price { font-weight: 600; font-size: 13px; }
    @media (max-width: 900px) { .slab-split { grid-template-columns: 1fr; } .slab-nav { border-right: 0; border-bottom: 1px solid var(--color-border); max-height: 240px; } }
    /* Toolbar: import button moves to the far right next to Export. */
    .toolbar a[href^="import-"] { order: 1; }
    /* slab-nav: emphasize the sub-layers (Block / Bundle) over the master Product */
    .slab-nav .tree-ico { width: 22px; height: 22px; color: var(--color-primary); }
    .slab-nav .tree-row:has(.tree-ico) .tree-label { color: var(--color-text); font-weight: 600; font-size: 13px; }
    /* No size override here. A product row’s mark used to be 18px with 8px initials while a
       sample row’s was 22px with 9px — two sizes for one thing, and both under the 10px
       floor. The rule above now sets every mark in every tree. */
    .slab-nav .tree-row:has(.slab-navthumb):not(.slab-nav-bundle) .tree-label { color: var(--color-text-muted); font-weight: 500; font-size: 12px; }
    .slab-nav .tree-row.slab-nav-bundle.active:has(.tree-ico) { box-shadow: inset 3px 0 0 var(--color-primary); }
    .slab-nav .tree-row.slab-nav-bundle.active:has(.tree-ico) .tree-ico { color: var(--color-primary-700); }
    .slab-nav .tree-row.slab-nav-bundle.active:has(.tree-ico) .tree-label { color: var(--color-primary-700); font-weight: 700; }
    /* Product entries: name + structured meta + multi-line description */
    .slab-nav .tree-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
    .slab-nav .tree-text .tree-label { white-space: normal; line-height: 1.25; }
    /* ONE LINE, always. The attribute line is what makes a material identifiable at a glance,
       and a glance is one line — a row that grows to three is a paragraph, and a column of
       paragraphs stops being a list you can scan.

       Truncated rather than trimmed to a fixed set, because the column is not a fixed width.
       At 300px roughly 214px reaches `.tree-text` (300 − 16 nav padding − 16 row padding − 20
       toggle − 18 thumb − 2×8 gap), which holds about three attributes; below 900px the nav
       becomes the full width of the card and all five fit. Picking three by hand would show
       three at every width, including the width where five would have fitted.

       `min-width: 0` on `.tree-text` above is what lets this shrink at all — a flex item
       defaults to `min-width: auto` and refuses to go under its content, so the ellipsis never
       appears and the row overflows instead. The full string is on `title` either way. */
    .slab-nav .tree-sub {
      font-size: 11px; color: var(--color-text-muted); line-height: 1.3;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .slab-nav .tree-desc { font-size: 11px; color: var(--color-text-subtle); line-height: 1.35; }
    .slab-nav .tree-text .tree-meta { font-size: 11px; color: var(--color-text-subtle); margin: 1px 0 0; }
    /* The name and its count share the TOP line — the count is the second half of "which
       material am I looking at", not a fact underneath it. `.tree-text` is a flex COLUMN, so
       app.css's `margin-left:auto` on `.tree-meta` did nothing there and the count fell to a
       line of its own. `.tree-head` is the row that makes that margin work again. */
    .slab-nav .tree-text .tree-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
    .slab-nav .tree-text .tree-head .tree-label { min-width: 0; }
    /* Beats `.slab-nav .tree-text .tree-meta` above on specificity — 4 classes to 3 — which
       zeroes margin-left and would otherwise pin the count against the name. */
    .slab-nav .tree-text .tree-head .tree-meta { margin: 0 0 0 auto; flex: none; white-space: nowrap; }
    /* The line the count vacated carries the CATALOG ITEM's attributes — Stone Type · Origin ·
       Colour · Grade · Density, the same set in the same order as the attribute grid in the
       detail panel, so the nav and the panel cannot disagree about what the material is.
       The DESCRIPTION stays hidden: it is prose, it runs three lines wide, and the detail pane
       for the selected item already prints it. Attributes are what you pick a material BY. */
    .slab-nav .tree-desc { display: none; }
    .slab-nav .tree-row:has(.tree-text) { align-items: flex-start; padding: 8px; }
    .slab-nav .tree-row:has(.tree-text) .thumb.slab-navthumb { margin-top: 1px; }
    /* Product name cell in flat tables: a short wrapping description under the name */
    .data-table .cell-media .cell-stack .cell-desc { font-size: 11px; color: var(--color-text-subtle); line-height: 1.35; margin-top: 2px; white-space: normal; }
    /* The product-type tab row is the SHARED `.tabs` component, unmodified — the same row
       catalog.html renders. Two page-local overrides used to sit here and are gone:

         nav.tabs[…] { align-items: center; gap: 2px; }
         nav.tabs[…] > .tab { padding: 10px 10px; }

       Both flattened the base's fluid sizing. `.tabs` sizes gap, padding, font and icon with
       `clamp()`, and app.css's own comment says that scale was tuned for a SEVEN-tab row —
       "seven tabs pay it fourteen times over" — which is this row. Pinning gap to the clamp's
       floor and padding to a static 10px made Stock's tabs read tighter and smaller than
       Catalog's for no reason either page could state.

       A `@media (max-width: 900px)` block went with them. Three of its four declarations
       (`flex-wrap: nowrap`, `overflow-x: auto`, `flex: none`, `white-space: nowrap`) are what
       the base already does at every width; the fourth hid the scrollbar, which Catalog does
       not do. Restoring any of this re-splits one component into two. */
    /* Reorderable settings lists (Manage KPIs) */
    .reorder-list { display: flex; flex-direction: column; gap: 3px; }
    .reorder-item { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border: 1px solid transparent; border-radius: 5px; background: var(--color-surface); transition: background 120ms, border-color 120ms, box-shadow 120ms; }
    .reorder-item:hover { background: var(--color-primary-50); }
    .reorder-item.dragging { opacity: .45; }
    .reorder-grip { display: inline-flex; flex: none; color: var(--color-text-subtle); cursor: grab; }
    .reorder-grip svg { width: 16px; height: 16px; }
    .reorder-item:active .reorder-grip { cursor: grabbing; }
    .reorder-num { flex: none; min-width: 22px; padding: 0 4px; height: 22px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center; background: var(--color-primary-50); color: var(--color-primary-700); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
    .reorder-item .switch-field { flex: 1; }
    /* Default Product Type dialog */
    .dpt-list { display: flex; flex-direction: column; gap: 4px; }
    .dpt-opt { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border: 1px solid var(--color-border); border-radius: 6px; cursor: pointer; font-size: 13px; color: var(--color-text); transition: background 120ms, border-color 120ms; }
    .dpt-opt:hover { background: var(--color-primary-50); }
    .dpt-opt:has(input:checked) { border-color: var(--color-primary); background: var(--color-primary-50); }
    .dpt-opt input { accent-color: var(--color-primary); }
    .dpt-opt svg { width: 16px; height: 16px; color: var(--color-text-subtle); }
    /* Import sliders — inline editable entry grid (opens over Stock) */
    [id^="so-import-"] .slideover { width: min(960px, 100%); }
    table.entry-grid .input { height: 34px; padding: 6px 8px; font-size: 13px; min-width: 78px; }
    /* Alignment + edge inset come from the global .entry-grid last-column rule in app.css. */
    /* Manual-add sliders — one entity added to the currently selected product */
    [id^="so-add-"] .slideover { width: min(520px, 100%); }
    [id^="so-add-"] .slideover-body .field + .field,
    [id^="so-add-"] .slideover-body .form-row + .field,
    [id^="so-add-"] .slideover-body .field + .form-row,
    [id^="so-add-"] .slideover-body .form-row + .form-row { margin-top: 14px; }
    [id^="so-add-"] .slideover-body .form-row > .field + .field { margin-top: 0; }
    /* Edit takes the add sliders' width and rhythm because it IS their step 2, reopened on a
       piece that already exists. Its fields are built by script rather than written in markup,
       so it cannot carry the spacing itself — and a prefix selector keyed on "so-add-" was
       never going to reach it. Declared separately rather than folded into the list above: an
       id in that selector would raise its specificity for the eight add sliders too. */
    /* The closing note is the same 12px muted as a field hint. Without a rule above it, it
       runs on from the last field's hint and reads as part of it rather than as a statement
       about the form as a whole. */
      margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--color-border);
    }
    .add-context { display: flex; align-items: center; gap: 8px; padding: 9px 11px; border-radius: 5px; background: var(--color-primary-50); color: var(--color-primary-700); font-size: 12px; margin-bottom: 14px; }
    .add-context svg { width: 15px; height: 15px; flex: none; }
    /* Two steps: which Catalog Item, then what the piece is. The rail sits between the head and
       the body so it never scrolls away, and step 2 stays disabled until step 1 is answered —
       a stock item without a Catalog Item behind it is not a thing the app can store. */
    .add-steps { display: flex; align-items: center; gap: 8px; flex: none; padding: 10px 16px; border-bottom: 1px solid var(--color-border); background: var(--color-surface-alt); }
    .add-step { display: inline-flex; align-items: center; gap: 7px; padding: 6px 10px; border: 1px solid transparent; border-radius: 6px; background: none; font-family: inherit; font-size: 12px; font-weight: 500; color: var(--color-text-subtle); cursor: pointer; }
    .add-step__n { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 18px; height: 18px; border-radius: 50%; background: var(--color-border); color: var(--color-text-muted); font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; }
    .add-step:not([disabled]):hover { color: var(--color-text); }
    .add-step.is-current { border-color: color-mix(in oklab, var(--color-primary) 35%, transparent); background: var(--color-primary-50); color: var(--color-primary-700); }
    .add-step.is-current .add-step__n { background: var(--color-primary); color: #fff; }
    .add-step[disabled] { opacity: .5; cursor: not-allowed; }
    .add-steps__rule { flex: 1; height: 1px; background: var(--color-border); }
    /* Location is Warehouse › Rack › Position — three content-sized selects on one row rather
       than three near-empty rows. `width:auto` undoes the global 100% so each sizes to its own
       longest option; the rack select is the long one and takes what is left. */
    /* The label carries a way through to the warehouse itself. The link sits BESIDE the label
       rather than inside it — a <label> that wraps an anchor gives the row two click targets
       with different meanings. */
    .loc-head { display: flex; align-items: baseline; gap: 10px; }
    .loc-open { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; color: var(--color-primary-600); }
    .loc-open svg { width: 13px; height: 13px; }
    .loc-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
    .loc-pick .select { width: auto; max-width: 100%; min-width: 0; }
    .loc-pick .select[data-loc-rack] { flex: 1; min-width: 180px; }
    .field-hint { font-size: 12px; color: var(--color-text-subtle); margin-top: 4px; }
    /* The footer changes with the step, so each step brings its own pair of buttons. */
    .add-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; width: 100%; }
    .add-foot__gap { flex: 1; }
    /* The step rail is the only new tap target these sliders add, so it takes the 44px mobile
       minimum on its own — the same treatment [data-modal-tabs] .subtab gets in css/app.css. */
    @media (max-width: 768px) {
      .add-step { min-height: 44px; }
    }
    .add-dim { display: flex; align-items: center; gap: 6px; }
    .add-dim .input { min-width: 0; }
    .add-dim .x { color: var(--color-text-subtle); font-size: 12px; flex: none; }
    .add-unit { font-size: 12px; color: var(--color-text-muted); white-space: nowrap; }
    /* Tree / flat-table view toggle: .viewseg itself lives in app.css (Quotes and Orders use it too) */
    .page[data-stockview="tree"] [data-view="table"] { display:none; }
    .page[data-stockview="table"] [data-view="tree"] { display:none; }

    /* ===================== Visual stock layout (Studio) ===================== */
    /* .kpi-summary (the collapsible KPI bar) is now shared — see css/app.css */
    /* Studio: product-type tabs sit above the content */
    .stk-studio { display:block; }
    /* Search relocated into the product-nav column (above the material list) */
    .slab-nav .search { display:block; position:relative; }
    .slab-nav .search input { width:100%; padding-left:32px; }
    .slab-nav .search svg { position:absolute; left:9px; top:50%; transform:translateY(-50%); width:14px; height:14px; color:var(--color-text-subtle); }

/* ---- table column bounds — generated, see changes/ ----
   Every column a family owns is pinned to one number, so it is the same width on
   every tab; the absorber takes what is left. The few columns that belong to one
   table only stay bounded — they rest at their minimum and grow with their own
   content to their maximum, the cap living on a block INSIDE the cell, since
   max-width on a td or th is ignored under auto layout.
   The column-flex measuring overlay is off on this page — its <link> is gone. */
  /* ---- flat-table column bounds: ONE shared set of sizes ----------------------
     A column family is sized once and all eight flat tables reuse it, so switching tabs
     never resizes a column that means the same thing.

     Every family column is PINNED — `width` and `min-width` are the same number. A range
     (a `width` above the `min-width`) does not survive the trip across eight tables: what a
     column then actually renders at depends on whether its own table has a greedy
     `.col-flex { width:100% }` absorber and on how wide that table's own content is, so one
     family measured 128px on the Blocks tab and 142px on the next four. A pin is the only
     shape that is the same on every tab at every viewport.

     Blocks, Bundles and Slabs carry three columns fewer than they used to. At 1352, 1490 and
     1389 they all scrolled sideways at a 1150px content width, and a table you have to scroll
     to finish reading is a table whose last column may as well not be there. Three pairs that
     were already answering the same question were folded into one column each:

       Thickness / Finish → Dimensions   both describe the piece's size; the variant drops to
                                         the sub-line under it
       Classification     → Qty / Area   the class badge leads its yield (Slabs has no yield
                                         column, so Grade takes Classification instead)
       Price              → Status       the price leads the status badge in the last column

     That took them to 1115 / 1115 / 1028 — every tab inside the viewport. Nothing was dropped:
     each merged value kept its own line inside the surviving column. Two of the surviving
     columns were then cut on top of that — ID by a fifth, Status by 30% — and the floors are
     now 1009 / 1009 / 922. The freed width is not redistributed by hand: Product is the only
     column in these tables without a width, so under `table-layout: auto` it is the one that
     takes the slack, at every viewport, without being told a number.

     Misc went the same way twice more, 993 → 797: Info folded into the Product sub-line behind
     the SKU ("SEAL-201 / Penetrating sealant · water-based"), and Location dropped the yard and
     the city to hold the rack code alone. Both were repeating themselves — a single-yard tenant
     reads "Main Slab Yard ›" and "Miami" on every row, which is a column of width spent saying
     the same thing eight times.

       family                pin   floor   what sets the floor
       Product               320     -     absorber: min-width only, never a width
       ID                    207    ~180  blocks yard path "QC Hold Area › Staging · Verona".
                                           Blocks, Bundles, Slabs. Split off the 259 group.
       Info                  259    237    tiles and flooring only. Misc folded its Info into
                                           the Product sub-line behind the SKU and dropped the
                                           column: two text columns side by side, and the
                                           wider one had nothing to compare down it.
       Location              259     -     remnants only, still the yard path over the origin
                                           slab. Misc's is 104 — the rack code by itself.
       Dimensions            131    103    blocks "118×63×71 in". Also carries the variant on
                                           Bundles and Slabs ("3 cm · Polished", 82px at 11px)
       Qty / Area            225    159    blocks header, now "Class / Est. Yield". The header
                                           is shorter than the "Est. Yield (Qty / Area)" that
                                           set 225, so this pin is no longer header-floored and
                                           could likely come down — unmeasured, left at 225.
       Grade                 138    116    slabs only. Now Grade over Classification, two
                                           stacked badges. Header-floored again, like the
                                           Thickness / Finish it replaces at this width:
                                           "GRADE / CLASS" needs 138, the data would sit in 124.
       Price                 113     71    blocks "$14,800". Flat tables only now — Remnants,
                                           Tiles, Flooring, Misc. The other four fold it into
                                           Status, which absorbs it inside its own width.
       Status                126    180    the one pin the browser argues with. Misc's
                                           "Reserved · Below Reorder" badge floors that tab at
                                           180 and Remnants at 155 whatever is declared; the
                                           six tabs with short badges take the 126.

     Floors are measured, not estimated: each column's longest rendered line was forced to
     `white-space:nowrap; width:max-content` in Chromium and measured, plus the cell's 24px
     of horizontal padding. Every pin clears its family's floor. Measure the whole element,
     not its text: a `.badge` carries 8px of side padding and a 4px dot gap of its own, and
     leaving those out is what made Status look like a 152px column. It is not — declared at
     152 the browser overrode it back to 180 on Misc and 155 on Remnants, because a pin can
     never take a column under its own min-content.

     Alignment is a family property too, so a column keeps its alignment when you switch tabs:
       Price       centred wherever it sits before Status; RIGHT in Remnants and Misc, where it
                   is the last column and keeps the table's right inset (app.css :last-child).
       Qty / Area  left in all eight, matching Blocks' Est. Yield.
       Grade       centred. It is no longer one glyph (0118), but it is still a single badge
                   in a column of single badges, and a badge is a shape rather than a value to
                   compare down the column — centring is still what that wants. Left-aligning
                   it would make it read as a second text column beside Thickness / Finish.

     A `width` on a <th> under `table-layout: auto` is not a bound, it is a PIN: it beats
     content, so the column sits there whatever you type and the text wraps inside it. That
     is exactly what is wanted here. `max-width` on a <th> would be no help anyway — the spec
     leaves it undefined for table cells and Chromium ignores it (measured: a column declared
     max-width:207px grew to 313px, the same width it reached with no max at all).

     Columns are addressed by their `col-*` class, never by position: an index is not a stable
     name for a column — insert or reorder one and every rule after it silently re-points at
     different data. */
  #tbl-blocks, #tbl-bundles, #tbl-slabs, #tbl-remnants,
  #tbl-samples, #tbl-tiles, #tbl-flooring, #tbl-misc { table-layout: auto; }
  /* Product / Remnant / Sample — the absorber. It states a floor and no width: a preferred
     width would stop it growing, and with no floor it is the first thing the table eats when
     narrowing. */
  :is(#tbl-blocks, #tbl-bundles, #tbl-slabs, #tbl-remnants,
      #tbl-samples, #tbl-tiles, #tbl-flooring, #tbl-misc) thead th.col-prod { min-width: 320px; }
  /* Block / Bundle / Slab ID left the 259 group at a 20% cut. It was pinned with Info and
     Location only because the three measured the same; nothing makes them one family, and
     no tab shows two of them at once. 207 still clears this column's longest line — the
     yard path under the ID ("QC Hold Area › Staging · Verona") — so only slack was cut. */
  :is(#tbl-blocks, #tbl-bundles, #tbl-slabs) thead th.col-id   { width: 207px; min-width: 207px; }
  :is(#tbl-tiles, #tbl-flooring) thead th.col-info,
  #tbl-remnants thead th.col-loc                               { width: 259px; min-width: 259px; }
  /* Dimensions and Thickness / Finish are two families, so they are sized twice. They were
     briefly pinned together at 131px, which is under the 137px "THICKNESS / FINISH" needs on
     one line (113px of uppercase text at 0.55px letter-spacing, plus 24px of cell padding),
     so the header wrapped and the header row grew on Bundles and Slabs. Measured by declaring
     126 → 150 in turn and reading the header's line count: 132 gives two lines, 138 gives one.
     138 is the first integer that fits, and it is the pin for the same reason Status is 152 —
     in this column the HEADER is the widest thing, not the data ("3 cm" over "Polished"). */
  :is(#tbl-blocks, #tbl-bundles, #tbl-slabs) thead th.col-dims { width: 131px; min-width: 131px; }
  :is(#tbl-blocks, #tbl-bundles) thead th.col-avail            { width: 225px; min-width: 225px; }
  /* Tiles and Flooring left the Qty / Area family: their Available is a single centred figure
     ("6,400 sqft"), not a two-line yield, so it holds 225 → 158 (a 30% cut) with room to spare. */
  :is(#tbl-tiles, #tbl-flooring) thead th.col-avail            { width: 158px; min-width: 158px; max-width: 158px; }
  /* Grade now carries Classification under it — two stacked badges, one column. It keeps the
     header floor rather than the data floor: "GRADE / CLASS" is 13 uppercase glyphs and needs
     138 the same way Thickness / Finish did, where "Super Extra" over "Class A" would have sat
     inside 124. Slabs only — no other tab pairs the two. */
  #tbl-slabs thead th.col-grade                                { width: 138px; min-width: 138px; }
  :is(#tbl-remnants, #tbl-tiles, #tbl-flooring, #tbl-misc) thead th.col-price { width: 113px; min-width: 113px; }
  /* Status: 180 → 126, a 30% cut. This is the one pin that is not always what the column
     gets: a pin can never take a column under its own min-content, and here that floor is a
     badge, which does not wrap. 126 holds wherever the widest badge is short ("Available",
     "Reserved"); on the tabs measured earlier as 180 (Misc) and 155 (Remnants) the browser
     still floors it there. Declared once for the family so it is one number to change. */
  :is(#tbl-blocks, #tbl-bundles, #tbl-slabs, #tbl-remnants, #tbl-samples,
      #tbl-tiles, #tbl-flooring, #tbl-misc) thead th.col-status { width: 126px; min-width: 126px; }
  /* A shrink-wrapped child does not answer to the cell's text-align — that only moves inline
     content, and `.cell-stack` is a flex column. Its margins are what place it:
     `margin-left:auto` carries Dimensions to the right edge, `margin-inline:auto` centres
     Price. The cap is the pin minus the cell's 24px of horizontal padding. */
  #tbl-blocks tbody td.col-dims > *  { width: max-content; max-width: 107px; margin-left: auto; }

  /* Merged columns: a badge sharing a stack with text.
     `.cell-stack` is a flex column with no `align-items`, so a badge stretches the full cell
     and stops looking like a badge. Each merged column shrink-wraps it to its own alignment —
     left with the yield figures, centred in Grade / Class, and right in the last column, where
     app.css already turns the whole stack to flex-end. */
  :is(#tbl-blocks, #tbl-bundles) tbody td.col-avail .cell-stack { align-items: flex-start; }
  #tbl-slabs tbody td.col-grade .cell-stack { align-items: center; gap: 3px; }
  :is(#tbl-blocks, #tbl-bundles, #tbl-slabs) tbody td.col-status .cell-stack > .badge { margin-top: 3px; }

  /* Columns with no family — one table each, sized to their own content. The `.col-flex`
     absorbers stay greedy here: with every family column pinned, a 100% demand can no longer
     starve one. Nothing on the page is bounded any more — Misc's Location was the last, and
     it is a pin now too, which leaves Product the only column on that tab without a width. */
  #tbl-remnants .col-flex { width: 100%; }   /* Remnant takes the remainder */
  #tbl-remnants thead th.col-avail       { width: 221px; min-width: 221px; max-width: 221px; }   /* Available: pinned — not the Qty / Area family. Main line is the bare size (48"×32"); everything that qualifies the piece is on the secondary line, slash-separated: "1 piece / 2 cm &middot; Polished / chipped corner". That secondary line is now the widest thing in the column, so the floor is 221px, not the 176 it measured while the spec still sat on the main line. Still down from the original 274. */
  #tbl-samples .col-flex { width: 100%; }   /* Sample takes the remainder */
  #tbl-samples thead th.col-format       { width: 102px; min-width: 102px; max-width: 102px; }   /* Format: pinned */
  #tbl-samples thead th.col-avail        { width: 84px; min-width: 84px; max-width: 84px; }   /* Available: pinned — a plain count, not the Qty / Area family */
  #tbl-samples thead th.col-with-clients { width: 156px; min-width: 156px; max-width: 156px; }   /* With Clients: pinned — 104 → 156 once each recipient row carried its dispatch record under the count. Measured floor 156px ("Project: Brickell penthouse"). */

  #tbl-misc .col-flex { width: 100%; }   /* Product takes the remainder */
  #tbl-misc thead th.col-avail   { width: 134px; min-width: 134px; max-width: 134px; }   /* Available: pinned — a plain count, not the Qty / Area family */
  #tbl-misc thead th.col-loc     { width: 104px; min-width: 104px; }   /* Location: the rack code alone ("Supply A1"), so 202–260 → 104. The yard and the city that used to sit here repeated on every row of a single-yard tenant and told you nothing about where the item is. */
