/* ===========================================================================
   showroom.css — the client-facing private showroom (D1c · D28 · D29)
   ---------------------------------------------------------------------------
   Namespace: .ps-*   |   This sheet NEVER loads app.css (D28).
   Authored fresh. No V3 value, no app.css value, crosses into this file (D1/D1b).

   THE GOVERNING IDEA
   The image is the page. Space goes into the photograph, not the furniture.
   Generous per-item space comes from a large slab image, not from padding —
   nine slabs at large image size is image-dense and whitespace-light.

   PREVENTION BY ABSENCE (D28's own trick: enforce by the file, not by restraint)
   This sheet deliberately has NO primitive for:
     - badge / chip / pill        -> the "25% OFF" chip cannot be rendered
     - strike-through / --was     -> comparison pricing cannot be rendered
                                     AMENDED: the PRODUCT VIEW alone may compare —
                                     see .ps-price__was. Everywhere else still cannot.
     - card                       -> tile chrome, which is what reads as catalog
     - search / facet / filter / sort / count / paginate  -> all browse chrome
     - uppercase tracked label    -> the operator console's voice
   If you find yourself needing one of these, the requirement is wrong, not this file.
   =========================================================================== */

@import url('fonts.css');
/* The Default room's pair (see THE THREE ROOM LAYOUTS). Cormorant Garamond is the
   display voice — the greeting only — and Jost is everything read at length. Both
   are imported here rather than linked from the page: this sheet is the whole of
   the room's styling (D28), and the page links nothing else. */
/* The four a supplier can choose between for LOT NAMES ONLY — the Product Name
   Font control on Showroom → Layout writes one of them into --ps-namefont. They
   are imported here, not linked from the page, for the same reason as the pair
   above: this sheet is the whole of the room's styling (D28). All four are
   loaded whichever is picked, because the control previews them live and a font
   that arrives on selection previews as a flash of the fallback. */

:root {
  /* --- TENANT-VARIABLE. The five values a supplier may set (D29, amended). --
     Per licensed supplier, resolved at render. All that supplier's showrooms
     share them, and they are authored on showroom-branding.html.
     Brand used to reach the header and stop there; it now also reaches the
     action colour, the ground and the ink — so the G.6.4 warn/success collision
     CAN arise, and the branding page checks for it rather than the file
     preventing it. What is still unreachable is listed under RESERVED. */
  --ps-header-bg: #1C1A17;
  --ps-header-fg: #F5F3EF;
  --ps-accent:    #1C1A17;   /* .ps-action — the one action colour */
  /* Foreground on the accent, and its hover. Both DERIVED on the branding page,
     never picked by hand — a supplier cannot be trusted to keep her own CTA
     legible, and a single authored hover value is wrong for most brands. */
  --ps-accent-fg:    #F7F5F2;
  --ps-accent-hover: #35302A;

  /* --- RESERVED. Tenant theming must NEVER reach these. --------------------
     D14's semantic wins over the brand, always (G.6.4, binding). Colour here
     is a SUPPORTING channel only — never load-bearing. See .ps-state. */
  --ps-warn:    #8A5A1F;
  --ps-warn-bg: #FBF3E8;
  --ps-success:    #2F6146;
  --ps-success-bg: #EDF4EF;
  --ps-danger:     #8E3B2E;   /* refused — brick, 6.5:1 on its own ground (0335) */
  --ps-danger-bg:  #FBEDEA;

  /* --- Ground. Warm stone paper, never pure white (no white-on-white). -----
     --ps-bg and --ps-ink are TENANT-VARIABLE too (D29, amended): the ground and
     the ink are the "background" and "text" the supplier sets. The derived
     values below them are not — surface, line and the two softened inks stay
     authored here, so a dark ground cannot be half-applied. */
  --ps-bg:        #F7F5F2;   /* tenant-variable */
  --ps-surface:   #FDFCFA;   /* warm white — pure #FFF reads clinical against stone */
  --ps-surface-2: #EFECE7;   /* the client's own chat bubble */
  --ps-line:      #E2DDD5;
  --ps-ink:       #1C1A17;   /* tenant-variable */
  --ps-ink-soft:  #6B655C;
  --ps-ink-mute:  #948C80;

  /* --- Type. 6 steps. 16px body vs the portal's 13px IS the density break. -
     Weights 400/500/600 only. No 700: bold is the console's table-header voice. */
  --ps-font: 'Plus Jakarta Sans', system-ui, sans-serif;
  --ps-text-hero:  40px;
  --ps-text-title: 28px;
  --ps-text-lede:  20px;
  --ps-text-body:  16px;
  --ps-text-meta:  14px;
  --ps-text-micro: 12px;
  --ps-text-price: 24px;

  /* --- Space. Base 8, range starts high, 7 steps. -------------------------- */
  --ps-space-1: 4px;
  --ps-space-2: 8px;
  --ps-space-3: 16px;
  --ps-space-4: 24px;
  --ps-space-5: 40px;
  --ps-space-6: 64px;
  --ps-space-7: 96px;

  --ps-radius: 3px;
  --ps-measure: 1180px;
}

/* --- Base ----------------------------------------------------------------- */
.ps-body {
  margin: 0;
  background: var(--ps-bg);
  color: var(--ps-ink);
  font-family: var(--ps-font);
  font-size: var(--ps-text-body);
  line-height: 1.6;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}
.ps-body *, .ps-body *::before, .ps-body *::after { box-sizing: border-box; }
.ps-body img { max-width: 100%; display: block; }

/* Never underline. Hover shifts colour, never decoration — to the accent, so a
   link and a button answer to the same tenant value. The header's own contact
   links keep --ps-header-fg (see .ps-contact a:hover). */
.ps-body a { color: inherit; text-decoration: none; }
.ps-body a:hover { color: var(--ps-accent); }

.ps-body :focus-visible {
  outline: 2px solid var(--ps-ink);
  outline-offset: 3px;
  border-radius: var(--ps-radius);
}

.ps-wrap { max-width: var(--ps-measure); margin: 0 auto; padding: 0 var(--ps-space-4); }

/* ===========================================================================
   HEADER — the tenant's slot, and the only place brand exists (D29)
   72px, taller and calmer than the console's 56. No nav. No search. Nothing else.
   =========================================================================== */
.ps-header {
  background: var(--ps-header-bg);
  color: var(--ps-header-fg);
  height: 72px;
  display: flex;
  align-items: center;
}
.ps-header__in {
  max-width: var(--ps-measure);
  margin: 0 auto;
  padding: 0 var(--ps-space-4);
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ps-space-4);
}

/* Constrain the slot; do not trust the asset (D29). A fixed box + contain means
   a 6:1 wordmark, a square badge and a 4000px upload all letterbox instead of
   setting the layout. The asset never gets to move anything. */
.ps-logo {
  height: 28px;
  max-width: 180px;
  width: auto;
  object-fit: contain;
  object-position: left center;
  flex: none;
}
/* The header opens with the supplier and closes with the VIP Offer mark: the goods
   are theirs, the room is ours. The contact line travels with the mark, so the two
   right-hand facts read as one group. */
.ps-header__end {
  display: flex;
  align-items: center;
  gap: var(--ps-space-4);
}
/* Same box discipline as .ps-logo — a supplier upload never sets the layout. The
   artwork is monochrome by design, so the dark header inverts it rather than
   asking for a second file. */
.ps-supplier {
  height: 26px;
  max-width: 180px;
  width: auto;
  object-fit: contain;
  object-position: left center;
  flex: none;
  filter: brightness(0) invert(1);
}
/* WHOSE ROOM THIS IS — between the supplier's mark and the product's, and the
   only thing in the middle of the bar. A private room carries no nav and no
   search: there is nothing to browse and nowhere else to go, so the space a
   showroom header spends on menus is spent naming the person instead.
   He leads, his company follows, and a hairline divides both from the supplier's
   mark — read left to right it is one sentence: Mike Johnson's showroom, inside
   Verona Stone Suppliers. The rule is a tint of the header foreground rather
   than --ps-line, which belongs to the paper, not to the dark bar. */
.ps-owner {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  margin-inline-end: auto;
  padding-inline-start: var(--ps-space-4);
  border-inline-start: 1px solid rgba(245, 243, 239, 0.22);
  color: var(--ps-header-fg);
}
.ps-owner__t {
  font-size: var(--ps-text-lede);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ps-owner__s {
  font-size: var(--ps-text-meta);
  line-height: 1.3;
  opacity: 0.72;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The product mark is two-colour and ships its own dark-background artwork, so it
   takes no filter — the header names the file it needs. */
.ps-productmark {
  height: 28px;
  width: auto;
  flex: none;
}
/* Fallback when a supplier has uploaded no logo: their name, set in brand fg. */
.ps-logo-text {
  font-size: var(--ps-text-lede);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ps-header-fg);
}

.ps-contact {
  display: flex;
  align-items: center;
  gap: var(--ps-space-3);
  font-size: var(--ps-text-meta);
  color: var(--ps-header-fg);
  opacity: 0.82;
  text-align: end;
}
.ps-contact a:hover { color: var(--ps-header-fg); opacity: 1; }

/* ---- Language — the reader's own setting, in the header that holds it -------
   Drawn out of --ps-header-fg rather than out of a colour, so the one control
   serves the four dark bars and Modern's light one without a second rule: a
   hairline box at the header's own opacity, brightening on hover the way
   .ps-contact does. The bar shows the flag and the two letters; the list under
   it shows the flag and the name, written in its own language.
   It brings its own menu because these pages carry no app.js and so have no
   dropdown to borrow — and the menu lands on the page's own surface, not on the
   bar, because it is read against the room. */
.ps-lang { position: relative; flex: none; }
.ps-lang__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--ps-space-2);
  padding: 6px 10px;
  font: inherit;
  font-size: var(--ps-text-meta);
  line-height: 1.1;
  color: var(--ps-header-fg);
  background: transparent;
  border: 1px solid color-mix(in oklab, var(--ps-header-fg) 30%, transparent);
  border-radius: var(--ps-radius);
  opacity: 0.82;
  cursor: pointer;
}
.ps-lang__btn:hover { opacity: 1; border-color: color-mix(in oklab, var(--ps-header-fg) 58%, transparent); }
.ps-lang__code { font-weight: 600; letter-spacing: 0.03em; }
.ps-lang__chev { width: 13px; height: 13px; flex: none; }
/* A drawn flag is flat colour with no edge of its own, so the pale ones need the
   hairline to end somewhere on both the dark bars and Modern's light one. */
.ps-lang__flag { width: 19px; height: 13px; flex: none; border-radius: 2px; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18); }
.ps-lang__menu {
  position: absolute;
  top: calc(100% + 10px);
  inset-inline-end: 0;
  min-width: 186px;
  max-height: calc(100vh - 160px);
  overflow-y: auto;
  padding: 6px;
  background: var(--ps-surface);
  border: 1px solid var(--ps-line);
  border-radius: var(--ps-radius);
  box-shadow: 0 14px 34px rgba(28, 26, 23, 0.24);
  z-index: 60;
  display: none;
}
.ps-lang.is-open .ps-lang__menu { display: block; }
.ps-lang__opt {
  display: flex;
  align-items: center;
  gap: var(--ps-space-2);
  width: 100%;
  padding: 7px 10px;
  font: inherit;
  font-size: var(--ps-text-meta);
  text-align: start;
  color: var(--ps-ink);
  background: transparent;
  border: 0;
  border-radius: var(--ps-radius);
  cursor: pointer;
}
.ps-lang__opt:hover { background: var(--ps-surface-2); }
.ps-lang__opt.is-on { font-weight: 600; }

/* ===========================================================================
   THE ADDRESS — she is named, and the set is a sentence, not a metric
   "Nine slabs, picked for Marbles & Granite". Never a count as a KPI (G.6.2).
   =========================================================================== */
.ps-address {
  font-size: var(--ps-text-hero);
  line-height: 1.15;
  font-weight: 500;
  letter-spacing: -0.02em;
  margin: var(--ps-space-6) 0 var(--ps-space-4);
  max-width: 20ch;
}
/* She is GREETED, and the room is the line under the greeting. The greeting is the
   variable half ("Hello Maria", "Nice seeing you again"); the room name is fixed. */
.ps-address--greeting { margin-bottom: var(--ps-space-1); max-width: 24ch; }
/* The room says what it IS before it says whose it is: eyebrow, then the client's
   name at hero size, then the greeting. A client uploads no mark — he is not the
   licensee — so his name IS the artwork here, taking the size a supplier's logo
   takes in the bar above it. */
.ps-address--client { margin: 0 0 var(--ps-space-1); max-width: 18ch; }
.ps-eyebrow {
  font-size: var(--ps-text-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ps-ink-soft);
  margin: var(--ps-space-6) 0 var(--ps-space-2);
}
.ps-address__sub {
  font-size: var(--ps-text-lede);
  color: var(--ps-ink-soft);
  margin: 0 0 var(--ps-space-4);
}

/* --- The rep. The page has an author and she can tell who (G.6.2). --------- */
.ps-rep { display: flex; align-items: center; gap: var(--ps-space-3); margin-bottom: var(--ps-space-6); }
.ps-rep__face {
  width: 56px; height: 56px;
  border-radius: 50%;              /* circular — exempt from the radius cap */
  object-fit: cover;
  background: var(--ps-surface-2);
  flex: none;
}
.ps-rep__name { font-weight: 500; }
.ps-rep__role { font-size: var(--ps-text-meta); color: var(--ps-ink-soft); line-height: 1.45; }

/* --- The rep's note. One paragraph, in her voice. -------------------------- */
.ps-lede {
  font-size: var(--ps-text-lede);
  line-height: 1.5;
  color: #3A3630;
  max-width: 62ch;
  margin: 0 0 var(--ps-space-6);
}

/* ===========================================================================
   THE SET — 2-up, NO CARDS. The image sits directly on the stone paper.
   Card chrome is precisely what makes a tile read as a catalog tile.
   No count, no sort, no pagination, no density toggle: each says "this is a query".
   =========================================================================== */
.ps-set {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--ps-space-6) var(--ps-space-5);
  margin-bottom: var(--ps-space-7);
}
@media (max-width: 860px) { .ps-set { grid-template-columns: 1fr; } }

.ps-item__img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--ps-radius);
  background: var(--ps-surface-2);
  margin-bottom: var(--ps-space-3);
  transition: filter 160ms ease;
}
.ps-item__name {
  font-size: var(--ps-text-title);
  line-height: 1.25;
  font-weight: 500;
  letter-spacing: -0.015em;
  margin: 0 0 var(--ps-space-1);
}
.ps-item__meta {
  font-size: var(--ps-text-meta);
  line-height: 1.45;
  color: var(--ps-ink-soft);
  margin: 0 0 var(--ps-space-3);
}

/* --- The price. ONE resolved number (D27/D32's output). -------------------
   Her resolved price is always the large number, and it is the only one the grid
   and the sets may show — no strike-through, no "% off" out there (G.6.2). */
.ps-price {
  font-size: var(--ps-text-price);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* --- The comparison. PRODUCT VIEW ONLY. -----------------------------------
   Where the doctrine used to be "no comparison anywhere", the detail view now
   states what the piece lists at and what she is saving, once, in both dollars
   and percent — she is deciding on ONE piece there and asked for the arithmetic.
   It stays small and soft: the saving is a fact under her price, never a badge,
   never a chip, never coloured. Do not carry these classes into .ps-grid. */
.ps-pricing { margin-bottom: var(--ps-space-5); }
.ps-pricing .ps-price { margin: 0; }
.ps-price__compare {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--ps-space-2);
  margin: var(--ps-space-1) 0 0;
  font-size: var(--ps-text-meta);
  font-variant-numeric: tabular-nums;
}
.ps-price__was { color: var(--ps-ink-mute); text-decoration: line-through; }
.ps-price__save { color: var(--ps-ink-soft); }

/* ===========================================================================
   HER SETS (D36) — the room keys on the CLIENT and is permanent; promotions
   deposit into it. So her room lists the sets she holds.

   Each set is titled by THE REP, FOR HER — never by the internal campaign name.
   That is what keeps this level legal under G.6.2: no campaign name, no
   "promotion" vocabulary, no shared-artifact tells. Bands, not cards: a card
   grid of offers is V3's discarded "All My Promotions", and reads as a menu.
   Deliberately absent: item count, status dot, date range, "% off", countdown.
   =========================================================================== */
.ps-collection { display: block; margin-bottom: var(--ps-space-6); }
.ps-collection__img {
  width: 100%;
  aspect-ratio: 16 / 6;
  object-fit: cover;
  border-radius: var(--ps-radius);
  background: var(--ps-surface-2);
  margin-bottom: var(--ps-space-3);
  transition: filter 160ms ease;
}
.ps-collection:hover .ps-collection__img { filter: brightness(1.04); }
.ps-collection__title {
  font-size: var(--ps-text-title);
  line-height: 1.25;
  font-weight: 500;
  letter-spacing: -0.015em;
  margin: 0 0 var(--ps-space-1);
}
/* Time, not lifecycle. "Added in May" is a fact about her room; "Active" and
   "Expiring Soon" are campaign lifecycle and are staff-only (G.6.1). */
.ps-collection__when { font-size: var(--ps-text-meta); color: var(--ps-ink-soft); }

/* ===========================================================================
   PRODUCT GRID — showroom-style cards for her picks (D36).
   The collection BANDS key on sets; this grid keys on the pieces themselves,
   shown the way the public showroom shows stock: image, spec line, an
   availability badge, and her one resolved Marbles & Granite price. Warm ps tokens, so the
   showroom LAYOUT arrives without the showroom's cool chrome. Still one
   resolved number per piece — no strike-through, no "% off" (G.6.2).
   =========================================================================== */
/* ===========================================================================
   THE SHELVES — one room, and what arrives in it arrives on a named shelf

   Promotions deposit into the room; the shelf name is what the client reads, so
   it is a merchandising label and never a campaign name or a client type. Two
   deposits under one name land on one shelf; two names stay apart. The shelf
   named Default carries no heading, so a room fed by one plain promotion reads
   as one room rather than a form with a section title — which is why the heading
   lives in its own block instead of being a rule on the grid.
   =========================================================================== */
.ps-shelf__head { margin-bottom: var(--ps-space-3); }
.ps-shelf__name {
  font-size: var(--ps-text-title);
  font-weight: 500;
  letter-spacing: -0.015em;
  margin: 0 0 var(--ps-space-1);
}
.ps-shelf__note {
  font-size: var(--ps-text-meta);
  color: var(--ps-ink-soft);
  line-height: 1.5;
  max-width: 62ch;
  margin: 0;
}
/* Between shelves, less air than under the last one — the page ends there. */
.ps-shelf .ps-grid { margin-bottom: var(--ps-space-6); }
.ps-shelf:last-of-type .ps-grid { margin-bottom: var(--ps-space-7); }

.ps-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--ps-space-4);
  margin-bottom: var(--ps-space-7);
}
@media (max-width: 900px) { .ps-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .ps-grid { grid-template-columns: 1fr; } }

.ps-card {
  display: flex;
  flex-direction: column;
  background: var(--ps-surface);
  border: 1px solid var(--ps-line);
  border-radius: var(--ps-radius);
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow 160ms ease, transform 160ms ease;
}
.ps-card:hover { box-shadow: 0 10px 28px rgba(28, 26, 23, 0.10); transform: translateY(-2px); }

.ps-card__img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: var(--ps-surface-2);
}
.ps-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-1);
  padding: var(--ps-space-3);
  flex: 1;
}
.ps-card__name { font-size: var(--ps-text-body); font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
.ps-card__meta { font-size: var(--ps-text-meta); color: var(--ps-ink-soft); }
.ps-card__desc {
  font-size: var(--ps-text-meta);
  line-height: 1.5;
  color: var(--ps-ink-mute);
  margin: var(--ps-space-1) 0 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ps-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ps-space-2);
  margin-top: auto;
  padding-top: var(--ps-space-3);
}
.ps-card__price { font-size: var(--ps-text-body); font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

/* Availability badge — the grid's one badge primitive. Colour is a support
   channel only (as with .ps-state); the dot + word carry the meaning. */
.ps-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--ps-space-1);
  padding: 3px 10px;
  border: 1px solid;
  border-radius: var(--ps-radius);
  font-size: var(--ps-text-micro);
  font-weight: 500;
  white-space: nowrap;
}
.ps-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.ps-badge--success { background: var(--ps-success-bg); color: var(--ps-success); border-color: #CBE3D5; }
.ps-badge--warn    { background: var(--ps-warn-bg);    color: var(--ps-warn);    border-color: #E9D8BE; }
.ps-badge--info    { background: #EEF1F5;              color: #45566B;           border-color: #D6DEE8; }

/* ===========================================================================
   AVAILABILITY (D13) + STATE MESSAGES (D14)
   Structural first: saturation + position + copy carry the meaning.
   Colour is support only, so no brand and no colour-blindness can break it.
   =========================================================================== */

/* Taken: the pick STAYS VISIBLE, desaturated, and not requestable (D13).
   Desaturation is a non-hue channel — it survives monochrome, and it makes a
   sold slab read as gone at a glance from across the set. */
.ps-item.is-unavailable .ps-item__img { filter: grayscale(0.72) opacity(0.72); }
.ps-item.is-unavailable .ps-item__name,
.ps-item.is-unavailable .ps-price { color: var(--ps-ink-soft); }
/* The action is REMOVED, not disabled-looking (D13: "not requestable"). */
.ps-item.is-unavailable .ps-action { display: none; }

/* Bands, never badges. D14: "explicit, not a subtle badge" — and there is no
   badge class here anyway. Fixed position directly under the image. */
.ps-state {
  display: flex;
  align-items: flex-start;
  gap: var(--ps-space-2);
  padding: var(--ps-space-2) var(--ps-space-3);
  border-radius: var(--ps-radius);
  border-inline-start: 3px solid;
  font-size: var(--ps-text-meta);
  line-height: 1.45;
  margin-bottom: var(--ps-space-3);
}
.ps-state__icon { flex: none; width: 16px; height: 16px; margin-top: 2px; }

/* Taken — another client took it. The buyer's EXISTENCE is disclosed, their
   identity is not (D14: confidentiality is settled by the copy). */
.ps-state--warn { background: var(--ps-warn-bg); border-inline-start-color: var(--ps-warn); color: var(--ps-warn); }
/* Returned — available again. The module's strongest trigger (D14). This is the
   page's own truth and does not depend on D15's email having gone out. */
.ps-state--success { background: var(--ps-success-bg); border-inline-start-color: var(--ps-success); color: var(--ps-success); }
/* Refused — the server said no (a sign-in code, a login request, a send), in
   its own words. Never the warn amber: a refusal and a caution must not read
   alike, and the app shows a "confirm before filing" warn band beside it. */
.ps-state--error { background: var(--ps-danger-bg); border-inline-start-color: var(--ps-danger); color: var(--ps-danger); }

/* ===========================================================================
   ACTIONS — ask / request / sample / visit. Never ordering.
   Labels are [OPEN] (a copy decision); the primitive is not.
   Ink, not brand: brand is header-only, so the CTA is brand-independent and
   therefore safe under every supplier's palette.
   =========================================================================== */
.ps-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ps-space-2);
  background: var(--ps-accent);
  color: var(--ps-accent-fg);
  border: 0;
  border-radius: var(--ps-radius);
  padding: 11px var(--ps-space-4);
  font-family: inherit;
  font-size: var(--ps-text-body);
  font-weight: 500;
  cursor: pointer;
  transition: background 140ms ease;
}
.ps-action:hover { background: var(--ps-accent-hover); }
.ps-action--quiet { background: transparent; color: var(--ps-ink); border: 1px solid var(--ps-line); }
.ps-action--quiet:hover { background: var(--ps-surface-2); }
.ps-action--gold { background: var(--ps-gold-lift, #D9B25F); color: #241C09; }
.ps-action--gold:hover { background: var(--ps-gold, #B8862B); color: #FFF6E3; }

/* ===========================================================================
   PRODUCT VIEW — the same language, one image bigger (Q-A15 form is [OPEN])
   =========================================================================== */
/* The piece's name, at the size the room gives its greeting — this is the one place
   it is the biggest thing on the page. It was three inline declarations on the
   element; it lives here now because an inline font-size outranks every modifier,
   so a layout could not have restyled it where it was. */
.ps-product__name { font-size: var(--ps-text-hero); line-height: 1.15; letter-spacing: -0.02em; }
.ps-product { display: grid; grid-template-columns: 1.5fr 1fr; gap: var(--ps-space-6); padding: var(--ps-space-6) 0; }
@media (max-width: 860px) { .ps-product { grid-template-columns: 1fr; } }
.ps-product__img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--ps-radius); }
.ps-spec { display: grid; grid-template-columns: auto 1fr; gap: var(--ps-space-2) var(--ps-space-4); font-size: var(--ps-text-meta); }
.ps-spec dt { color: var(--ps-ink-mute); }        /* sentence case — never tracked caps */
.ps-spec dd { margin: 0; color: var(--ps-ink); font-variant-numeric: tabular-nums; }

/* ===========================================================================
   CHAT — the THIRD chat surface. Not the rep's cockpit, not support.html.

   THE ADVISOR PANEL, TRANSCRIBED BACK (2026-09-10). This is the chat the VIP
   Showroom pages carried before the 2026-09-07 rename dropped it, restored from
   that build's own values: a navy mast, a gold tile with the advisor's initials
   where a stock photograph used to be, and a gold-armed composer.

   AUTHORSHIP IS POSITION AND GROUND, and now also CORNER — each bubble squares
   off the corner nearest its own side, which says who wrote it before a word is
   read. Hue does the same work here it always did, so G.5 axis 9 still holds:
   the tenant's brand cannot tint either bubble.

   D34 IS THE ONE THING THIS RESTORATION REVERSES, and it needs an owner's
   ruling. The old design puts a green dot and WITH YOU NOW on the mast; D34
   says presence fails to offline, always, and never renders online on stale
   data. The two cannot both be on screen, so the line that read "Elena is
   offline right now" came out with the transcription. Put it back and the
   mast contradicts it; keep the mast and the room claims a presence the
   prototype cannot know.

   ITS OWN PALETTE, NOT THE ROOM'S TOKENS. The panel is the advisor's identity
   rather than the tenant's, and it kept its own navy and gold when it lived
   here before — a room that rebrands does not rebrand her.
   =========================================================================== */
.ps-chat {
  background: #FDFCF9;
  border: 1px solid rgba(22, 35, 63, 0.12);
  border-radius: 7px;
  display: flex;
  flex-direction: column;
  overflow: hidden;    /* the navy mast has to meet the corner, not overhang it */
}
/* The stream sits a shade UNDER the panel's own paper, which is what lets the
   client's white bubble read as a card on it rather than as a patch of it. */
.ps-chat__log {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: #F4F1EA;
}

.ps-msg { max-width: 78%; padding: 10px 14px; font-size: 13.5px; line-height: 1.5; }
.ps-msg--them {
  align-self: flex-start;
  background: #fff; color: #3C4356;
  border: 1px solid rgba(184, 134, 43, 0.3);
  border-radius: 7px 7px 7px 3px;
}
.ps-msg--me {
  align-self: flex-end;
  background: #16233F; color: #F3EFE4;
  border-radius: 7px 7px 3px 7px;
}

/* The composer: a plain white line and one gold arm. Send is an icon rather
   than the word — the arrow is the whole of what the control says, and at this
   width a labelled button takes the room off the box beside it. */
.ps-chat__composer {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-top: 1px solid rgba(22, 35, 63, 0.1);
  background: #FDFCF9;
}
.ps-chat__input {
  flex: 1;
  min-width: 0;
  font-family: inherit;
  font-size: 13.5px;
  color: #16233F;
  background: #fff;
  border: 1px solid rgba(22, 35, 63, 0.15);
  border-radius: 7px;
  padding: 11px 16px;
  resize: none;
}
.ps-chat__input::placeholder { color: var(--ps-ink-mute); }
.ps-chat__send {
  width: 40px; height: 40px;
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0;
  border-radius: 7px;
  background: linear-gradient(180deg, #C4964A, #A87A26);
  box-shadow: 0 2px 8px rgba(168, 122, 38, 0.35);
  cursor: pointer;
  transition: filter 140ms ease;
}
.ps-chat__send:hover { filter: brightness(1.08); }
.ps-chat__send svg { width: 16px; height: 16px; display: block; }

/* --- DOCKED. The chat rides every room page, fixed bottom-right, so reaching the
   advisor never depends on how far the client has scrolled. Closed it is a small
   navy card that names her and says what she is; open it is the same .ps-chat,
   lifted off the page. --------------------------------------------------------- */
.ps-dock {
  position: fixed;
  inset-inline-end: 28px;
  bottom: 28px;
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding-block: 9px;
  padding-inline: 10px 20px;
  background: linear-gradient(180deg, #1B2A4A, #141F38);
  border: 1px solid rgba(217, 178, 95, 0.4);
  border-radius: 7px;
  font-family: inherit;
  text-align: start;
  cursor: pointer;
  box-shadow: 0 10px 28px rgba(22, 35, 63, 0.4);
  transition: filter 140ms ease;
}
.ps-dock:hover { filter: brightness(1.12); }
/* Her initials on gold, not her photograph. A face at 36px is a thumbnail of a
   person; the monogram is a mark, and it is the same mark the open panel wears
   so the button and the panel read as one thing opening. */
.ps-dock__face,
.ps-chat__face {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 7px;
  background: radial-gradient(circle at 35% 30%, #E6C477, #B8862B);
  color: #1B2A4A;
  font-size: 13px;
  font-weight: 600;
}
.ps-dock__face { width: 36px; height: 36px; }
.ps-chat__face { width: 38px; height: 38px; }
.ps-dock__id   { display: flex; flex-direction: column; }
.ps-dock__who  { font-size: 13px; font-weight: 500; color: #F3EFE4; line-height: 1.2; }
.ps-dock__role { font-size: 10px; font-weight: 600; letter-spacing: .16em; color: #D9B25F; line-height: 1.2; }

.ps-chat[hidden] { display: none; }  /* .ps-chat sets display:flex, which outranks [hidden] */
.ps-chat--docked {
  position: fixed;
  inset-inline-end: 28px;
  bottom: 28px;
  z-index: 41;
  width: 374px;
  height: 540px;
  /* The design drew it at a fixed 540; these two only stop it running off a
     small window, and on any screen tall enough they never bind. */
  max-width: calc(100vw - 56px);
  max-height: calc(100vh - 56px);
  box-shadow: 0 18px 48px rgba(22, 35, 63, 0.4);
}
.ps-chat--docked .ps-chat__log { flex: 1; min-height: 0; overflow-y: auto; }

/* The mast names the advisor and says she is on the line. */
.ps-chat__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 18px;
  background: linear-gradient(180deg, #1B2A4A, #141F38);
}
.ps-chat__id { flex: 1; min-width: 0; line-height: 1.25; }
.ps-chat__who { font-size: 14px; font-weight: 500; color: #F3EFE4; }
/* The dot is drawn rather than marked up: it is the same statement as the words
   beside it, and a screen reader reading "with you now" twice helps nobody. */
.ps-chat__sub {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10.5px;
  letter-spacing: .1em;
  color: rgba(217, 178, 95, 0.9);
}
.ps-chat__sub::before {
  content: "";
  flex: none;
  width: 6px; height: 6px;
  border-radius: 7px;
  background: #7FC98B;
}
.ps-chat__close {
  margin-inline-start: auto;
  background: none;
  border: 0;
  padding: 4px;
  font-family: inherit;
  font-size: 20px;
  line-height: 1;
  color: rgba(243, 239, 228, 0.7);
  cursor: pointer;
}
.ps-chat__close:hover { color: #fff; }

/* ===========================================================================
   THE FRONT DOOR (D11) — the showroom mints its own auth; it inherits none.
   Named and rep-signed: this is her FIRST touch of the premise, so it does the
   work an anonymous code box cannot.
   =========================================================================== */
.ps-gate { min-height: 100vh; display: grid; place-items: center; padding: var(--ps-space-5); }
.ps-gate__in { width: 100%; max-width: 440px; text-align: center; }
.ps-gate__title { font-size: var(--ps-text-title); line-height: 1.25; font-weight: 500; letter-spacing: -0.015em; margin: 0 0 var(--ps-space-2); }
.ps-gate__note { font-size: var(--ps-text-meta); color: var(--ps-ink-soft); margin: 0 0 var(--ps-space-5); }
.ps-code {
  width: 100%;
  font-family: inherit;
  font-size: var(--ps-text-title);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.18em;
  text-align: center;
  color: var(--ps-ink);
  background: var(--ps-surface);
  border: 1px solid var(--ps-line);
  border-radius: var(--ps-radius);
  padding: var(--ps-space-3);
  margin-bottom: var(--ps-space-3);
}
.ps-code.is-invalid { border-color: var(--ps-warn); }

/* ===========================================================================
   THE TWO VIEWS — the shelves, and what has been asked for
   ---------------------------------------------------------------------------
   Navigation between two views of ONE room, which is why it is two words and a
   rule rather than a bar: nothing here counts, filters or sorts, so the sheet's
   PREVENTION BY ABSENCE stands untouched.

   Sentence case, not the tracked caps this file bans — a tab strip is the one
   place a console would reach for them, and that is precisely the voice the room
   is not written in. The live tab is ink over a rule; the other is muted.
   =========================================================================== */
.ps-tabs {
  display: flex;
  gap: var(--ps-space-4);
  margin-top: var(--ps-space-4);
  border-bottom: 1px solid var(--ps-line);
}
.ps-tabs__btn {
  appearance: none;
  background: none;
  border: 0;
  /* The rule sits ON the strip's own hairline rather than under it, so the live
     tab covers the line instead of drawing a second one below it. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 0 0 10px;
  font-family: inherit;
  font-size: var(--ps-text-body);
  font-weight: 500;
  color: var(--ps-ink-mute);
  cursor: pointer;
  transition: color 140ms ease, border-color 140ms ease;
}
.ps-tabs__btn:hover { color: var(--ps-ink); }
/* Gold where the room has gold, the action colour where it does not — the
   fallback resolves for Modern and Bold, which define no --ps-gold. */
.ps-tabs__btn.is-on { color: var(--ps-ink); border-bottom-color: var(--ps-gold, var(--ps-accent)); }

/* ===========================================================================
   REQUESTS — his side of the queue
   ---------------------------------------------------------------------------
   Rows, not cards and not a table: each one is a sentence about a piece he
   asked about, and the only thing that varies across them is where it stands.
   The state and the price close the row because they are what he opened the
   view for; everything else reads left to right the way the shelves do.
   =========================================================================== */
.ps-reqs { list-style: none; margin: 0 0 var(--ps-space-6); padding: 0; }
.ps-req {
  display: flex;
  align-items: center;
  gap: var(--ps-space-4);
  padding: var(--ps-space-3) 0;
  border-bottom: 1px solid var(--ps-line);
}
.ps-req__what { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ps-req__name { font-size: var(--ps-text-body); font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; }
/* A request rarely names one piece. The second one is his, not a category, so it
   keeps the line rather than becoming a count. */
.ps-req__more { font-weight: 400; color: var(--ps-ink-soft); }
.ps-req__ask { font-size: var(--ps-text-meta); color: var(--ps-ink-soft); line-height: 1.45; }
.ps-req__end { flex: none; display: flex; align-items: center; gap: var(--ps-space-3); }
.ps-req__price { font-size: var(--ps-text-body); font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.ps-req__foot { font-size: var(--ps-text-meta); color: var(--ps-ink-soft); margin: 0 0 var(--ps-space-7); }

/* Below this the two halves of a row no longer fit side by side, and a state
   badge squeezed against a wrapping name reads as a fourth line of the name. */
@media (max-width: 560px) {
  .ps-req { flex-direction: column; align-items: flex-start; gap: var(--ps-space-2); }
}

/* ===========================================================================
   FOOTER — attribution, not presence (D29)
   .ps-footer__vava is one class, so the plan-gated-removal [OPEN] is a
   one-line change whichever way it lands.
   =========================================================================== */
.ps-footer {
  border-top: 1px solid var(--ps-line);
  padding: var(--ps-space-5) 0;
  margin-top: var(--ps-space-7);
  font-size: var(--ps-text-micro);
  color: var(--ps-ink-mute);
  display: flex;
  justify-content: space-between;
  gap: var(--ps-space-4);
  flex-wrap: wrap;
}
/* The closing statement of privacy. Same micro size as the rest of the footer —
   it is a fact about the page, not a notice demanding to be read. */
.ps-footer__private { color: var(--ps-ink-soft); }
.ps-footer__vava { color: var(--ps-ink-mute); }

/* ===========================================================================
   THE THREE ROOM LAYOUTS — Default, Modern, Bold
   ---------------------------------------------------------------------------
   A supplier picks one on Promotions -> Layout and every client room takes it.
   Modern and Bold carry THE SAME BAR — supplier mark, the client, the VIP Offer
   mark — because whose room this is does not vary with a layout choice. What varies
   between those two is the ground the bar sits on and how the pieces are set out.

   DEFAULT IS NOW THE EXCEPTION, and it is the only one: it takes the Navy Luxe bar,
   which puts the greeting IN the bar instead of under it. That was asked for
   directly. Everything unclassed in this file is the shape Modern and Bold build
   on, so Default carries .ps-body--luxe rather than restyling the base — the base
   is also what showroom-product / -set / -access render with, and none of those
   were part of the ask.
   =========================================================================== */

/* --- MODERN: a light bar and editorial pieces. -----------------------------
   The room's paper runs all the way up through the header, so the page reads as
   one sheet rather than a dark strip over a light body. The supplier's mark stops
   inverting (it is monochrome ink on light again) and the VIP mark takes its
   light-background file — the page names that file, the way offer.html names
   the dark one. */
.ps-body--modern {
  --ps-header-bg: #FDFCFA;
  --ps-header-fg: #1C1A17;
}
.ps-body--modern .ps-header { border-bottom: 1px solid var(--ps-line); }
.ps-body--modern .ps-supplier { filter: none; }
.ps-body--modern .ps-owner { border-inline-start-color: rgba(28, 26, 23, 0.18); }

/* The masthead splits: the greeting on the left, the rep and his note on the
   right, so the first screen carries the person AND the voice without either
   pushing the pieces below the fold. Bottom-aligned — two blocks of different
   height that share a baseline read as one masthead, not as two stacked cards. */
.ps-mast--split {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: var(--ps-space-5);
  align-items: end;
  margin: var(--ps-space-6) 0;
}
.ps-mast--split .ps-eyebrow { margin-top: 0; }
.ps-mast--split .ps-lede { margin-bottom: 0; }
.ps-mast--split .ps-rep { margin-bottom: var(--ps-space-3); }
@media (max-width: 900px) {
  .ps-mast--split { grid-template-columns: 1fr; align-items: start; gap: var(--ps-space-4); }
}

/* --- BOLD: a full-bleed banner, then a dense wall of stone. ----------------
   The banner is the Hero Image the branding card asks for on this layout and on
   no other. The greeting sits on it, so the ink is fixed light and a scrim
   carries it: a supplier's photograph cannot be trusted to be dark. */
.ps-hero {
  position: relative;
  background: var(--ps-header-bg) center / cover no-repeat;
  color: #F5F3EF;
  margin-bottom: var(--ps-space-5);
}
.ps-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20, 18, 16, 0.35) 0%, rgba(20, 18, 16, 0.78) 100%);
}
/* The prototype's stand-in banner. In the product this is the supplier's own Hero
   Image, written onto the element by the renderer; there is no upload to read here,
   and a Bold room with no banner at all would show a black band and teach nothing. */
.ps-body--bold .ps-hero { background-image: url('../img/products/Patagonia_Quartzite__Slab_1.jpg'); }
/* Padding on the block axis only — the element also carries .ps-wrap, and a
   shorthand here would wipe the side padding that keeps the greeting off the
   edge of a narrow window. */
.ps-hero__in { position: relative; z-index: 1; padding-top: var(--ps-space-6); padding-bottom: var(--ps-space-5); }
.ps-hero .ps-eyebrow { color: rgba(245, 243, 239, 0.78); margin-top: 0; }
.ps-hero .ps-address { color: #F5F3EF; }
.ps-hero .ps-address__sub { color: rgba(245, 243, 239, 0.82); margin-bottom: 0; }

/* Four across instead of three, and the description drops off the card: Bold
   trades the reading for the looking. Everything else about the card is
   unchanged — this is a density, not a second card design. */
.ps-grid--dense { grid-template-columns: repeat(4, 1fr); gap: var(--ps-space-3); }
.ps-grid--dense .ps-card__body { padding: var(--ps-space-3); gap: 2px; }
.ps-grid--dense .ps-card__desc { display: none; }
@media (max-width: 1100px) { .ps-grid--dense { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 800px)  { .ps-grid--dense { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px)  { .ps-grid--dense { grid-template-columns: 1fr; } }

/* --- DEFAULT · NAVY LUXE ---------------------------------------------------
   One dark bar, a gold hairline under it, and the greeting set in serif inside
   the bar rather than under it. The room below is ivory paper with navy ink.

   GOLD IS AN ORNAMENT, NEVER A BUTTON. It carries the hairline, the eyebrow, the
   client's name in the greeting, the divider and a card's hover edge — every place
   it is type or a rule. It is not --ps-accent: a solid gold button cannot hold
   legible text in either direction, so the action colour stays navy.

   The reserved semantics (warn / success) are untouched, as they must be — D14's
   meaning outranks the room's palette, so an availability badge stays green or
   amber against the navy rather than being restyled into it.
   --------------------------------------------------------------------------- */
.ps-body--luxe {
  /* Ornament only. --gold sits on paper, --gold-lift on the navy bar. */
  --ps-gold:      #B8862B;
  --ps-gold-lift: #D9B25F;
  --ps-navy:      #16233F;

  --ps-accent:       #16233F;
  --ps-accent-fg:    #F3EFE4;
  --ps-accent-hover: #1B2A4A;

  /* The canvas's own page rule, verbatim: background #efece6, Jost, gold links.
     Its one card is white with a 1px rgba(0,0,0,.08) hairline, so --ps-surface and
     --ps-line are those and not this sheet's warm stone. The ink stays navy: that is
     the colour the header options set for type on ivory (#16233F), and #1a1a1a in
     the canvas belongs to the viewer's own chrome, not to the design. */
  --ps-bg:        #EFECE6;
  --ps-surface:   #FFFFFF;
  --ps-surface-2: #E8E2D4;
  --ps-line:      rgba(0, 0, 0, 0.08);
  --ps-ink:       #16233F;
  --ps-ink-soft:  #6E7480;
  --ps-ink-mute:  #8A8F9B;

  /* Two radii, both the canvas's: 8px on a card, 5px on everything smaller.
     This is the standing exception to memory.md §2 — an exact copy was asked for. */
  --ps-radius: 5px;

  --ps-font:         'Jost', system-ui, sans-serif;
  --ps-font-display: 'Cormorant Garamond', Georgia, serif;
  /* Cormorant runs small for its size, so a shelf name needs the extra points to
     sit where 28px of Jakarta sat. */
  --ps-text-title: 32px;
}

/* --- The room below the bar. ----------------------------------------------
   The canvas governs down here too, not only in the bar. Its page rule is four
   declarations long — ground, family, link, link hover — and its one card is the
   surface treatment the shelves take. Everything else below is this room's own
   furniture, which the canvas does not draw. */
/* a{color:#b8862b} a:hover{color:#8f671f} — the canvas's, verbatim. The action
   colour does NOT follow it: gold behind a label cannot hold legible text in
   either direction, and the canvas paints no button to copy. */
.ps-body--luxe a { color: var(--ps-gold); }
.ps-body--luxe a:hover { color: #8F671F; }
/* The greeting is up in the bar, so the room opens on the line about privacy and
   needs the air the greeting used to stand in. */
.ps-body--luxe .ps-address__sub { margin-top: var(--ps-space-6); }
/* The base lede is a warm brown-black, which reads muddy beside navy ink. */
.ps-body--luxe .ps-lede { color: #2C3548; }
.ps-body--luxe .ps-shelf__name,
.ps-body--luxe .ps-product__name { font-family: var(--ps-font-display); font-weight: 600; letter-spacing: 0; }
/* This ground is warmer than the sheet's default, so a transparent control now sits
   ON the panel's own colour. The quiet actions take the surface instead — the solid
   one is already navy, from --ps-accent. */
.ps-body--luxe .ps-action--quiet { background: var(--ps-surface); }
/* The canvas's card, verbatim: white, an 8px corner, a 1px black-at-8% hairline and
   a shadow so slight it reads as a lift off the paper rather than a drop. */
.ps-body--luxe .ps-card {
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
/* The canvas draws no hover — it is a still artboard. This is the room's own, kept
   in its language: the resting shadow deepens and the hairline warms to gold. */
.ps-body--luxe .ps-card:hover {
  border-color: rgba(184, 134, 43, 0.55);
  box-shadow: 0 10px 28px rgba(22, 35, 63, 0.12);
}

/* ===========================================================================
   VIP ROOM CHROME — Classic · Hero · Sidebar
   ---------------------------------------------------------------------------
   These three replaced design-canvas exports (2026-09-07). The exports were
   800KB–1.5MB apiece with every photo baked into one generated line, so an
   image could not be swapped and the header was not markup anything could
   mirror. Authored here, the photos are img/products/*.jpg — the same files
   the catalog and stock pages use — and the header is real elements, which is
   what lets the Showroom page's Top Bar Preview render THE SAME MARKUP rather
   than an imitation of it.

   ⭐ The chrome is one thing used three ways. Classic and Hero wear it as three
   stacked bands; Sidebar folds the same parts into a left rail. Whatever a
   layout does with them, the parts are the same parts, because whose room this
   is does not vary with a layout choice.

   ⛔ NOT .ps-mast — that name is taken by offer-modern.html for a page
   masthead, which is a different thing in a different place.

   Every colour below is a token so the branding card can drive it. A band with
   no token is a band nobody can rebrand, which is the defect this replaced.
   ========================================================================= */
/* ⭐ .ps-vip-top IS THE HEADER'S OWN HOST (2026-09-10). The header travels to
   pages that are not rooms — showroom-product.html first — and those pages keep
   their own body class and their own sheet. Putting the class on their <body>
   hands them the colours and nothing else. It stays a BODY-level host, never a
   rule on .ps-top itself: the branding card writes these tokens onto the page,
   and a declaration on the header would beat what it inherits. */
.ps-body--classic,
.ps-body--hero,
.ps-body--sidebar,
.ps-body--luxe,
.ps-body--visitor,
.ps-vip-top {
  --vip-mast-top:  #1B2A4A;
  --vip-mast-bot:  #141F38;
  --vip-mast-fg:   #F4F1EA;
  --vip-gold:      #C2A05A;   /* eyebrow, greeting highlight, active nav, price */
  --vip-rule:      #A98338;   /* the hairline under the mast */
  --vip-nav-bg:    #F0EDE6;
  --vip-nav-fg:    #16233F;
  --vip-search-bg: #FFFFFF;
  --vip-search-fg: #6B655C;
  --vip-strip-bg:  #EDE8DC;
  --vip-strip-fg:  #6B5A38;
  --vip-page-bg:   #F0EDE6;
  --vip-card-bg:   #FDFCF9;
  --vip-line:      #E2DDD5;
  /* The one the branding card's font picker writes. Named lots read as a hand
     wrote them; everything else on the page stays in the UI face. */
  --ps-namefont:   'Caveat', cursive;
}
/* The piece page (--luxe) wears the same header since 0288, so it needs the
   tokens above — but it keeps its own Navy Luxe paper, so only the three room
   bodies take the family's ground. */
.ps-body--classic,
.ps-body--hero,
.ps-body--sidebar,
.ps-body--visitor {
  background: var(--vip-page-bg);
}

/* ---- Band 1: the mast ---------------------------------------------------
   Three things across: the supplier's mark, whose room this is, the VIP mark.
   The centre column is the only one that carries a person's name, so it is the
   only one that is centred — the marks sit on the edges and hold it there. */
/* The bands carry the room's UI face themselves. On a room that is inherited
   from .ps-body anyway; on a page that wears the header over another sheet
   (showroom-product.html sits on app.css) the host's body font would otherwise
   reach in, and the same header would read differently from page to page. */
.ps-top { font-family: var(--ps-font); font-size: var(--ps-text-body); line-height: 1.6; }

.ps-top__mast {
  background: linear-gradient(180deg, var(--vip-mast-top), var(--vip-mast-bot));
  color: var(--vip-mast-fg);
  border-bottom: 2px solid var(--vip-rule);
}
.ps-top__mast-in {
  max-width: var(--ps-measure); margin: 0 auto; padding: 8px var(--ps-space-4);
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--ps-space-4);
}
.ps-top__supplier { display: flex; align-items: center; gap: 10px; }
/* The supplied mark is drawn in #1F2733 and the mast is navy, so as-is it is a
   dark logo on a dark ground — invisible, which is what it was. Flattened to
   white here rather than shipping a second asset: a tenant uploads ONE logo in
   the branding card and it has to survive whatever bar colour they pick. */
.ps-top__supplier img { height: 40px; width: auto; filter: brightness(0) invert(1); }
/* The mark is the way home — every room, the piece and the requests page point
   it at the collection the reader came from. The anchor is the image's own box
   (no baseline gap, nothing to underline) so the mast measures the same. */
.ps-top__supplier a,
.ps-rail__supplier a { display: flex; align-items: center; }
.ps-top__greet { text-align: center; }
.ps-top__eyebrow {
  display: block; font-size: 10px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--vip-gold);
}
.ps-top__hello {
  display: block; margin: 0 0 1px; line-height: 1.15;
  font-family: 'Cormorant Garamond', Georgia, serif; font-size: 20px; font-weight: 500;
}
.ps-top__hello b { font-weight: 500; color: var(--vip-gold); }
.ps-top__rep { display: block; font-size: 11px; color: rgba(244,241,234,.72); }
.ps-top__vip { justify-self: end; display: flex; align-items: center; gap: 7px; font-size: 13px; letter-spacing: .04em; }
.ps-top__vip img { height: 18px; width: auto; }
.ps-top__vip b { font-weight: 700; }
.ps-top__vip span { color: var(--vip-gold); }
.ps-rail__supplier img { filter: brightness(0) invert(1); }

/* ---- Band 2: search and nav ---------------------------------------------
   The old room chrome claimed a room has neither. It has both — this is read
   off the page it replaces — and the branding card colours them because a
   control that changes nothing visible is worse than a missing one. */
.ps-top__nav { background: var(--vip-nav-bg); color: var(--vip-nav-fg); border-bottom: 1px solid var(--vip-line); }
.ps-top__nav-in {
  max-width: var(--ps-measure); margin: 0 auto; padding: 7px var(--ps-space-4);
  display: flex; align-items: center; justify-content: space-between; gap: var(--ps-space-4);
}
.ps-top__search {
  display: flex; align-items: center; gap: 9px;
  background: var(--vip-search-bg); color: var(--vip-search-fg);
  border: 1px solid var(--vip-line); border-radius: 6px;
  padding: 5px 12px; width: 390px; max-width: 46%; font-size: 13px;
}
.ps-top__search svg { width: 15px; height: 15px; flex: none; opacity: .6; }
.ps-top__links { display: flex; flex-wrap: wrap; align-items: center; gap: 13px 26px; font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
/* A LINK NEVER BREAKS, SO ITS COUNT NEVER LEAVES IT (0336). Narrow, the label
   wrapped between its words and the count circle dropped onto a line of its
   own; a signed-in nav of four overflowed 375px outright. Now each link holds
   one line and the LINKS wrap instead — a second row, 13px down (the rail's
   own rhythm). `> *` because the app writes some of these as <button>s. */
.ps-top__links > * { white-space: nowrap; }
/* Hover previews the lit state — the room's accent is gold, not the sheet's
   near-black --ps-accent, which the base a:hover would paint here (and which
   VANISHES on the rail's navy — the bug 0288's polish pass fixed). */
.ps-top__links a:hover { color: var(--vip-gold); }
.ps-top__links a.is-on { color: var(--vip-gold); }
/* A TRUE CIRCLE WITH ITS NUMBER IN THE MIDDLE OF IT (2026-10-02). It was a
   999px pill with side padding and a min-width — which memory.md rule 2
   forbids on a container, and which left the digit off-centre two ways: the
   box only became square when min-width won over 5px of padding, and the
   count inherited the top bar's 1.6 line-height, so flex centring centred a
   17.6px line box inside a 19px one and the glyph rode wherever its baseline
   fell. Fixed square, 50% radius, line-height 1: the digit is centred by the
   box, not by arithmetic that happens to agree. `vertical-align: middle`
   seats the circle against the label's middle rather than hanging it from
   the digit's baseline. (A three-figure count would need its pill back.) */
.ps-top__count {
  display: inline-flex; align-items: center; justify-content: center;
  width: 21px; height: 21px; margin-inline-start: 7px;
  border-radius: 50%; background: var(--vip-mast-bot); color: var(--vip-mast-fg);
  font-size: 11px; line-height: 1; letter-spacing: 0; font-variant-numeric: tabular-nums;
  /* AND THE CIRCLE RIDES THE LETTERS' OWN MIDDLE. `vertical-align: middle`
     seats a box against the parent's baseline plus half its X-HEIGHT — but
     this nav is set in CAPITALS, whose optical middle is half the CAP height,
     about a pixel higher. So the circle sat low against the word beside it.
     It is lifted 2px here — the pixel the capitals ask for, and a second the
     owner asked for by eye (2026-10-02): a dark disc this size beside capitals
     carries more weight than they do, and reads level only when it sits a
     touch above the line that bisects them. Visual only; nothing in the bar
     moves. */
  vertical-align: middle; position: relative; top: -2px;
  /* THE BOX ALONE CENTRES THE FIGURE — MEASURED 2026-10-04 (changes/0334).
     A 2px top padding once spent "the descent a figure lacks"; rendered, it
     set every count 1.3px LOW (6.6px of circle above the ink, 4.0 below):
     this face's figures already sit mid-line in their own line box. 21px, not
     19: an ODD size puts the centre on a whole pixel (20 rides half a pixel
     off), and 10-99 clear the circle by 4px a side instead of 3. */
  padding: 0;
}

/* ---- Band 3: the announcement strip -------------------------------------- */
.ps-top__strip {
  background: var(--vip-strip-bg); color: var(--vip-strip-fg);
  border-bottom: 1px solid var(--vip-line); font-size: 12.5px;
}
.ps-top__strip-in { max-width: var(--ps-measure); margin: 0 auto; padding: 6px var(--ps-space-4); }
.ps-top__strip b { color: var(--vip-gold); margin-inline-end: 6px; }
.ps-top__strip em { font-style: normal; font-weight: 600; }

/* ---- The collection head + filter chips ---------------------------------- */
.ps-room { max-width: var(--ps-measure); margin: 0 auto; padding: 22px var(--ps-space-4) 60px; }
.ps-room__head { display: flex; align-items: baseline; gap: 13px; }
.ps-room__head h1 { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 30px; font-weight: 500; margin: 0; }
.ps-room__count { font-size: 11px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); }
/* The room's one standing note — the hold date the VIP section carries, and the
   line the Pending subject rewrites. Not .ps-lede: a lede's bottom margin is
   sized to open a page, and under a heading that already wears its count this
   only has to clear its own line before the collection chips. */
.ps-room__note { margin: 6px 0 0; font-size: 13px; color: var(--ps-ink-soft); }
.ps-cols { display: flex; align-items: center; gap: 10px; margin: 15px 0 20px; flex-wrap: wrap; }
.ps-cols__lbl { font-size: 10px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; color: var(--ps-ink-mute); margin-inline-end: 3px; }
.ps-chip {
  background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 6px;
  padding: 7px 15px; font-size: 13px; cursor: pointer;
}
.ps-chip.is-on { background: #F6E7BF; border-color: var(--vip-gold); color: var(--vip-nav-fg); font-weight: 600; }
/* THE ONE COLLECTION THAT IS NOT A SHELF. The other four are how Elena arranged
   the room; VIP Offer is the private offer she prepared for this client, so it
   wears the room's gold at rest rather than waiting to be lit. Lit, it goes a
   shade deeper and takes the darker rule — the only way a chip that starts
   coloured can still say it is on. */
.ps-chip--vip { background: #FBF3DE; border-color: var(--vip-gold); color: var(--vip-nav-fg); }
.ps-chip--vip.is-on { background: #F6E7BF; border-color: var(--vip-rule); font-weight: 600; }
.ps-refine {
  margin-inline-start: auto; display: inline-flex; align-items: center; gap: 8px;
  background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 6px;
  padding: 7px 15px; font-size: 11px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; cursor: pointer;
}
.ps-refine svg { width: 14px; height: 14px; }

/* ---- The lot card, shared by Classic and Hero ----------------------------
   The photo is the product; it gets the whole card width and a fixed 4:3 so a
   row of them lines up whatever each JPEG's own aspect is. */
.ps-lots { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 17px; }
.ps-lot[role="button"] { cursor: pointer; }
.ps-lot { background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 4px; overflow: hidden; display: flex; flex-direction: column; }
.ps-lot__img { aspect-ratio: 4 / 3; background: #E8E4DC; }
.ps-lot__img img { width: 100%; height: 100%; object-fit: cover; }
.ps-lot__body { padding: 13px 14px 15px; display: flex; flex-direction: column; gap: 5px; flex: 1; }
/* 10px is the floor (memory.md rule 4); it sat at 9.5px, under it. */
.ps-lot__new { font-size: 10px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; color: var(--vip-gold); }
.ps-lot__name { font-family: var(--ps-namefont); font-size: 23px; line-height: 1.15; color: var(--vip-nav-fg); }
.ps-lot__meta { font-size: 11.5px; color: var(--ps-ink-soft); line-height: 1.5; }
.ps-lot__meta b { font-weight: 600; letter-spacing: .1em; text-transform: uppercase; font-size: 10px; color: var(--ps-ink-mute); }
.ps-lot__foot { margin-top: auto; padding-top: 11px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ps-lot__price { font-size: 15px; font-weight: 700; color: var(--vip-gold); }
.ps-lot__price span { font-weight: 400; font-size: 12px; color: var(--ps-ink-soft); }
.ps-hold {
  background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 5px;
  padding: 6px 12px; font-size: 10.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--vip-nav-fg); cursor: pointer;
}
.ps-hold:hover { border-color: var(--vip-gold); color: var(--vip-gold); }

/* ---- Hero only: the first-look lot runs full width ----------------------- */
.ps-feature { display: grid; grid-template-columns: 1.15fr 1fr; background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 4px; overflow: hidden; margin-bottom: 34px; }
.ps-feature__img { min-height: 380px; background: #E8E4DC; }
.ps-feature__img img { width: 100%; height: 100%; object-fit: cover; }
.ps-feature__body { padding: 34px 38px; display: flex; flex-direction: column; justify-content: center; gap: 11px; }
.ps-feature__name { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 42px; font-weight: 500; line-height: 1.05; margin: 0; }
.ps-feature__lede { font-size: 14px; color: var(--ps-ink-soft); line-height: 1.65; max-width: 46ch; }
.ps-feature__acts { display: flex; align-items: center; gap: 13px; margin-top: 6px; }
.ps-feature__price { font-size: 19px; font-weight: 700; color: var(--vip-gold); margin-inline-end: 5px; }
.ps-feature__price span { font-weight: 400; font-size: 13px; color: var(--ps-ink-soft); }
.ps-cta {
  background: var(--vip-gold); color: #241C09; border: 1px solid var(--vip-gold); border-radius: 5px;
  padding: 10px 19px; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; cursor: pointer;
}
.ps-cta--ghost { background: transparent; color: var(--vip-nav-fg); border-color: var(--vip-line); }
.ps-shelf { margin-bottom: 30px; }
.ps-shelf__head { display: flex; align-items: baseline; gap: 11px; margin-bottom: 13px; }
.ps-shelf__head h2 { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 24px; font-weight: 500; margin: 0; }
.ps-shelf__head span { font-size: 10.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); }

/* ---- Sidebar only: the same parts folded into a left rail ---------------- */
.ps-body--sidebar { display: grid; grid-template-columns: 264px minmax(0, 1fr); min-height: 100vh; }
/* The rail stays 100% on screen while the collection scrolls (owner ruling,
   0288): viewport-tall and stuck, so the search, the nav and the advisor are
   never scrolled away. A viewport shorter than the rail scrolls the rail
   itself, not the promise. */
.ps-rail { background: linear-gradient(180deg, var(--vip-mast-top), var(--vip-mast-bot)); color: var(--vip-mast-fg); padding: 20px 20px 16px; display: flex; flex-direction: column; gap: 17px; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
.ps-rail__supplier { display: flex; align-items: center; gap: 10px; }
.ps-rail__supplier img { height: 48px; width: auto; }
.ps-rail__greet { border-bottom: 1px solid rgba(244,241,234,.14); padding-bottom: 16px; }
.ps-rail__search { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,.07); border: 1px solid rgba(244,241,234,.16); border-radius: 6px; padding: 8px 11px; font-size: 12.5px; color: rgba(244,241,234,.7); }
.ps-rail__search svg { width: 14px; height: 14px; flex: none; }
/* The rail's search learned to work the same day the band's did (0288): the
   label keeps the box's chrome, the input inside is bare. */
.ps-rail__search input { flex: 1; min-width: 0; border: 0; background: none; font: inherit; color: var(--vip-mast-fg); padding: 0; }
.ps-rail__search input::placeholder { color: rgba(244, 241, 234, 0.7); }
.ps-rail__search input:focus { outline: none; }
.ps-rail__search:focus-within { border-color: var(--vip-gold); }
.ps-rail__search input::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }
.ps-rail__links { display: flex; flex-direction: column; gap: 13px; font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; }
.ps-rail__links a { color: var(--vip-mast-fg); }
.ps-rail__links a:hover { color: var(--vip-gold); }
.ps-rail__links a.is-on { color: var(--vip-gold); }
/* The count pill is dark-on-cream in the nav band; on the rail that ground IS
   the pill's colour, so it vanished. Inverted here — same chip, readable. */
.ps-rail__links .ps-top__count { background: var(--vip-gold); color: #241C09; }
.ps-rail__foot { margin-top: auto; display: flex; flex-direction: column; gap: 13px; }
/* A real button since 0288's polish pass — its "Chat" is a promise, and it
   opens the rail's own dock. Font/ink reset because a button brings its own. */
.ps-rail__advisor { display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,.07); border: 1px solid rgba(244,241,234,.16); border-radius: 6px; padding: 9px 11px; font: inherit; color: inherit; text-align: start; width: 100%; cursor: pointer; }
.ps-rail__advisor:hover { border-color: var(--vip-gold); }
.ps-rail__advisor b { display: block; font-size: 12.5px; }
.ps-rail__advisor span { display: block; font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--vip-gold); }
.ps-avatar { width: 30px; height: 30px; border-radius: 6px; background: var(--vip-gold); color: #241C09; display: grid; place-items: center; font-size: 11px; font-weight: 700; flex: none; }

/* The row form: one lot per line, the photo small and the numbers in a column
   you can run your eye down. Named lots keep the picked font here too. */
.ps-rows { background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 4px; }
.ps-row { display: grid; grid-template-columns: 186px minmax(0, 1fr) auto; gap: 20px; align-items: center; padding: 15px 17px; border-bottom: 1px solid var(--vip-line); }
.ps-row:last-child { border-bottom: 0; }
.ps-row__img { aspect-ratio: 16 / 10; border-radius: 3px; overflow: hidden; background: #E8E4DC; }
.ps-row__img img { width: 100%; height: 100%; object-fit: cover; }
.ps-row__name { font-family: var(--ps-namefont); font-size: 22px; color: var(--vip-nav-fg); }
.ps-row__type { font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); margin-inline-start: 9px; }
.ps-row__lot { font-size: 11.5px; color: var(--ps-ink-soft); margin-top: 3px; }
.ps-row__dims { font-size: 11.5px; color: var(--ps-ink-soft); margin-top: 2px; }
.ps-row__right { text-align: end; }
.ps-row__price { font-size: 15px; font-weight: 700; color: var(--vip-gold); white-space: nowrap; }
.ps-row__price span { font-weight: 400; font-size: 12px; color: var(--ps-ink-soft); }
.ps-row__acts { display: flex; gap: 7px; margin-top: 8px; }

/* ---- Footer -------------------------------------------------------------- */
.ps-foot { border-top: 1px solid var(--vip-line); padding: 22px var(--ps-space-4) 26px; text-align: center; }
.ps-foot__who { font-size: 10.5px; font-weight: 600; letter-spacing: .17em; text-transform: uppercase; color: var(--ps-ink-mute); }
.ps-foot__by { font-size: 11px; color: var(--ps-ink-mute); margin: 5px 0 9px; }
.ps-foot__switch { font-size: 10.5px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; display: flex; gap: 9px; justify-content: center; }
.ps-foot__switch a { color: var(--vip-gold); }
.ps-foot__switch .is-on { color: var(--ps-ink-mute); }

@media (max-width: 900px) {
  .ps-feature { grid-template-columns: 1fr; }
  .ps-body--sidebar { grid-template-columns: 1fr; }
  /* Stacked full-width above the collection, a pinned viewport-tall rail
     would BE the screen — it goes back to flowing with the page. */
  .ps-rail { position: static; height: auto; overflow-y: visible; }
  .ps-row { grid-template-columns: 120px minmax(0, 1fr); }
  .ps-row__right { grid-column: 2; text-align: start; }
  .ps-top__search { max-width: none; width: 100%; }
  .ps-top__nav-in { flex-wrap: wrap; }
}

/* ---- The drawer chassis, and the request-line family ------------------------
   The chassis (.ps-scrim / .ps-request and its head/body/foot) began life as
   the Your Requests drawer; Your Requests is a PAGE now (showroom-workspace.html)
   and the chassis serves Contact. The line family further down —
   .ps-qitem — moved out with the request and is worn by the requests
   page at page width (and by the visitor page's selection drawer).

   Authored fresh (D28): app.css's .slideover never crosses into this family,
   so the drawer and its scrim are ps-* primitives of their own. The sheet's
   z ladder holds 40/41/60; the drawer takes 70/71 so nothing in the chrome
   can stand over it. Closed, it is visibility:hidden as well as off-screen —
   a hidden drawer must not sit in the tab order. */
.ps-scrim {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(20, 31, 56, .38);
  opacity: 0; pointer-events: none; transition: opacity 160ms ease;
}
.ps-scrim.is-open { opacity: 1; pointer-events: auto; }
/* No body scroll lock, on purpose. Hiding the document scrollbar makes every
   centred band flinch ~15px on classic-scrollbar platforms (and
   scrollbar-gutter on the root does nothing in Chromium), so the page keeps
   its scrollbar and the drawer contains its own: wheel and touch stop at the
   list's ends instead of chaining into the page behind the scrim. */
.ps-request {
  position: fixed; top: 0; inset-inline-end: 0; bottom: 0; z-index: 71;
  width: 430px; max-width: 100vw;
  background: var(--vip-card-bg); border-inline-start: 1px solid var(--vip-line);
  box-shadow: 0 10px 40px rgba(28, 26, 23, .20);
  display: flex; flex-direction: column;
  transform: translateX(102%); visibility: hidden;
  transition: transform 160ms ease, visibility 0s 160ms;
}
.ps-request.is-open { transform: none; visibility: visible; transition: transform 160ms ease; }
/* ⛔ THE ONE PLACE A LOGICAL INSET IS NOT ENOUGH, AND IT WOULD HAVE FAILED SILENTLY. `inset-inline-end`
   puts this drawer on the LEFT under `dir="rtl"`, but `translateX(102%)` is a PHYSICAL offset and there is
   no logical form of it — so the closed drawer would have slid 102% to the RIGHT, i.e. straight across the
   viewport it is supposed to be hidden outside of. Every other conversion in this sheet is a no-op in LTR
   and correct in RTL on its own; this pair is the exception, so it is spelled out.
   ⛔ AND `.is-open` NEEDS ITS TWIN (0338). This line used to say it did not, "it resolves to
   `transform: none` in both directions" — but `[dir="rtl"] .ps-request` and `.ps-request.is-open` are the
   SAME specificity (0,2,0) and the RTL rule comes later, so it won: in Arabic every OPEN drawer kept
   translateX(-102%) and sat wholly off-screen while the scrim dimmed the page. The twin out-ranks it. */
[dir="rtl"] .ps-request { transform: translateX(-102%); }
[dir="rtl"] .ps-request.is-open { transform: none; }

.ps-request__head { display: flex; align-items: flex-start; gap: 12px; padding: 18px 22px 15px; border-bottom: 1px solid var(--vip-line); }
.ps-request__title { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 25px; font-weight: 500; margin: 0; }
.ps-request__doc { font-size: 11px; color: var(--ps-ink-soft); margin: 3px 0 0; }
.ps-request__doc b { font-weight: 600; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }
.ps-request__close { margin-inline-start: auto; background: none; border: 0; cursor: pointer; padding: 2px 6px; font-size: 21px; line-height: 1; color: var(--ps-ink-soft); }
.ps-request__close:hover { color: var(--vip-nav-fg); }
.ps-request__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 4px 22px 18px; }
.ps-request__foot { border-top: 1px solid var(--vip-line); padding: 12px 22px 14px; font-size: 11px; color: var(--ps-ink-soft); }

/* A line is a piece: its photo, its own facts, her one resolved price
   (D27/D32). No status — Q-A7 — and the verbs are the client's two: Ask
   Elena, and Remove, because changing your mind about a request is the one
   thing that is yours to do without her. */
.ps-request__items { list-style: none; margin: 0; padding: 0; }
.ps-qitem { display: flex; align-items: center; gap: 12px; padding: 13px 0; border-bottom: 1px solid var(--vip-line); }
.ps-qitem__img { width: 54px; height: 54px; border-radius: 3px; overflow: hidden; background: #E8E4DC; flex: none; }
.ps-qitem__img img { width: 100%; height: 100%; object-fit: cover; }
.ps-qitem__what { flex: 1; min-width: 0; }
.ps-qitem__name { display: block; font-family: var(--ps-namefont); font-size: 20px; line-height: 1.1; color: var(--vip-nav-fg); }
.ps-qitem__meta { display: block; font-size: 11px; color: var(--ps-ink-soft); line-height: 1.45; margin-top: 1px; }
.ps-qitem__type { width: 13px; height: 13px; margin-inline-end: 5px; vertical-align: -2px; color: var(--ps-ink); }
.ps-qitem__meta b { font-weight: 600; letter-spacing: .1em; text-transform: uppercase; font-size: 10px; color: var(--ps-ink-mute); }
.ps-qitem__ref { font-variant-numeric: tabular-nums; }
.ps-qitem__end { flex: none; text-align: end; }
.ps-qitem__price { font-size: 13.5px; font-weight: 700; color: var(--vip-gold); white-space: nowrap; }
.ps-qitem__price span { font-weight: 400; font-size: 11px; color: var(--ps-ink-soft); }
.ps-qitem__rm { display: block; margin-inline-start: auto; margin-top: 4px; background: none; border: 0; cursor: pointer; padding: 3px 0; font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--ps-ink-mute); }
.ps-qitem__rm:hover { color: var(--vip-nav-fg); }

/* The archive's ending, in HIS words — Approved, Expired, Closed — worn
   as a badge on the finished document's head line (since 0301 the archive
   wears the open documents' own dress, so the .ps-qpast row family it
   used to need is gone). The room's reserved pairs carry the two loud
   endings (success for the yes, warn for the lapse — the palette
   deliberately holds no alarm red, D14); Closed keeps the neutral page
   ground. */
.ps-request__lbl { font-size: 10px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; color: var(--ps-ink-mute); margin: 26px 0 3px; }
.ps-req-badge { display: inline-block; margin-inline-start: 6px; padding: 2px 8px; border: 1px solid var(--vip-line); border-radius: 4px; background: var(--vip-page-bg); font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-soft); vertical-align: 1px; }
.ps-req-badge.is-ok   { background: var(--ps-success-bg); border-color: #CBE3D5; color: var(--ps-success); }
.ps-req-badge.is-gone { background: var(--ps-warn-bg); border-color: #E9D8BE; color: var(--ps-warn); }

/* The card button once its piece is on the request: still a button — pressing
   it again opens the drawer on the line — so it keeps its shape and only
   takes the gold that means "yours". The count pill hides at zero by class,
   not [hidden]: display:inline-flex would win that argument. */
.ps-hold.is-req { border-color: var(--vip-gold); color: var(--vip-gold); background: rgba(194, 160, 90, .08); }
.ps-top__count.is-none { display: none; }

/* ---- The search is a real field now ---------------------------------------
   The label wears the box (same chrome the branding preview paints); the
   input inside is bare, so nothing about the band's look changed when it
   learned to work. */
.ps-top__search input { flex: 1; min-width: 0; border: 0; background: none; font: inherit; font-size: 13px; color: var(--vip-nav-fg); padding: 0; }
.ps-top__search input::placeholder { color: var(--vip-search-fg); }
.ps-top__search input:focus { outline: none; }
.ps-top__search:focus-within { border-color: var(--vip-gold); }
/* The box already carries its own magnifier; the browser's round × would be
   a second, unstyled icon the branding preview's plain span never shows. */
.ps-top__search input::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }

/* ---- A lot goes somewhere -------------------------------------------------
   The photo and the name both open the piece's own page; the name keeps its
   hand-written face and only owns up to being a link when the pointer asks. */
.ps-lot__img a { display: block; width: 100%; height: 100%; }
.ps-lot__name a { color: inherit; text-decoration: none; }
.ps-lot__name a:hover { text-decoration: underline; text-decoration-color: var(--vip-gold); text-underline-offset: 3px; }

/* Narrowed out of the current view — display, not [hidden], because the card
   is a flex column and would win that argument. */
.ps-lot.is-off { display: none; }
/* Hero's feature and Sidebar's rows narrow the same way (0288) — both are
   display-setting elements, so [hidden] would lose the same argument. */
.ps-feature.is-off { display: none; }
.ps-row.is-off { display: none; }

/* Search can find nothing; the message and its way back stand where the grid
   was. */
.ps-room__none { margin: 6px 0 0; padding: 34px 0; text-align: center; font-size: 13px; color: var(--ps-ink-soft); }
.ps-room__none .ps-hold { margin-inline-start: 8px; }

/* ---- Filter: the room's second drawer (changes/0311) ----------------------
   Refine's floating menu became a drawer on the Contact chassis; only its
   chip rows and its foot are its own. The button keeps .ps-refine's clothes,
   and its own margin-left pushes it to the row's end. */
.ps-filter__row { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 8px; }
/* A value the other picks leave nothing under: it stays in place, faded and
   unpressable, so the rows never jump as the counts move. */
.ps-filter__row .ps-chip:disabled { opacity: .45; cursor: default; }
.ps-filter__foot { display: flex; align-items: center; gap: 10px; }
.ps-filter__foot .ps-cta { margin-inline-start: auto; }
@media (max-width: 768px) {
  .ps-filter__row .ps-chip, .ps-filter__foot .ps-hold, .ps-filter__foot .ps-cta { min-height: 44px; }
}

/* The strip's first-look line is the one clickable thing on the band —
   every page of the family backs it now (changes/0291 wired Hero and
   Sidebar; the requests page was wired in 0283). */
.ps-body--classic .ps-top__strip em,
.ps-body--hero .ps-top__strip em,
.ps-body--sidebar .ps-top__strip em,
.ps-body--luxe .ps-top__strip em,
.ps-body--visitor .ps-top__strip em { cursor: pointer; }
.ps-body--classic .ps-top__strip em:hover,
.ps-body--hero .ps-top__strip em:hover,
.ps-body--sidebar .ps-top__strip em:hover,
.ps-body--luxe .ps-top__strip em:hover,
.ps-body--visitor .ps-top__strip em:hover { text-decoration: underline; text-underline-offset: 2px; }

/* ---- Contact: the drawer's other dress ------------------------------------
   Same chassis as the request; the content is the advisor and the showroom's
   own facts — nothing that files anything, since the Plan a Visit form was
   removed with the Visits feature (owner, 2026-09-10). ⚠ The ps-visit__*
   family OUTLIVES the feature it was named for: the visitor page's sign-in
   and register panels wear __advisor / __row / __who / __form / __field /
   __again — keep them. Only __done (the visit confirmation) died with the
   form. */
.ps-visit__advisor { display: flex; align-items: center; gap: 11px; padding: 12px 0 4px; }
.ps-visit__who b { display: block; font-size: 13px; }
.ps-visit__who span { display: block; font-size: 11px; color: var(--ps-ink-soft); }
.ps-visit__row { display: flex; flex-direction: column; gap: 1px; margin: 0; padding: 9px 0; border-bottom: 1px solid var(--vip-line); }
.ps-visit__row b { font-weight: 600; font-size: 12px; color: var(--vip-nav-fg); }
.ps-visit__row span { font-size: 11.5px; color: var(--ps-ink-soft); }
.ps-visit__form { display: flex; flex-direction: column; gap: 12px; padding-top: 10px; }
.ps-visit__field { display: flex; flex-direction: column; gap: 5px; font-size: 11.5px; color: var(--ps-ink-soft); }
/* The fields sit on the drawer's cream card, so white would be a field on a
   ground one shade away from it — the control ground is the page's own
   beige, which reads as "a well" the way the nav band does. */
.ps-visit__field input, .ps-visit__field textarea { font: inherit; font-size: 12.5px; color: var(--vip-nav-fg); background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 6px; padding: 8px 11px; resize: vertical; }
.ps-visit__field input:focus, .ps-visit__field textarea:focus { outline: none; border-color: var(--vip-gold); }
.ps-visit__form .ps-cta { align-self: flex-start; }
/* A flex container's own rule beats [hidden] — without this line the form
   outlives its own submit. The dock has the same flaw one family over:
   showroom-chat.js sets toggle.hidden while the chat is open, and
   .ps-dock's inline-flex was winning that argument — an invisible,
   focusable button under the panel. Same fix .ps-chat[hidden] already got. */
.ps-visit__form[hidden] { display: none; }
.ps-dock[hidden] { display: none; }
/* The way back after a sent state: same quiet caps as the line verbs,
   inheriting the band's green so it reads as part of the confirmation.
   (Worn today by the visitor page's sign-in panel.) */
.ps-visit__again { display: block; margin-top: 7px; padding: 0; background: none; border: 0; cursor: pointer; font-size: 10px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* ---- The requests PAGE (showroom-workspace.html) ------------------------------
   The drawer grew into a page when it took on the request system's feature
   set — stone-type groups, line arithmetic, the figures bar. Its column
   runs the room's full measure (owner, 2026-10-06: a 860px cap left a
   quarter of the page empty and pushed an order's marks onto a row of
   their own). The line anatomy is the .ps-qitem family above, worn at
   page width. */
.ps-req-col { max-width: none; }
.ps-req-doc { margin: 4px 0 0; font-size: 12.5px; color: var(--ps-ink-soft); }
.ps-req-doc b { font-weight: 600; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }
/* Three figures, each a tight value-label pair. Inside the document CARD
   (0298) the bar inverts to the page ground — a box never repeats its
   panel's colour (memory.md rule 13). A flex rule beats [hidden], so the
   empty-request hide is spelled out. */
.ps-req-figs { display: flex; flex-wrap: wrap; gap: 8px 26px; margin: 15px 0 22px; padding: 13px 17px; background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 4px; }
.ps-req-figs[hidden] { display: none; }
.ps-req-fig { font-size: 11px; font-weight: 600; letter-spacing: .11em; text-transform: uppercase; color: var(--ps-ink-mute); }
.ps-req-fig b { font-size: 15px; font-weight: 700; letter-spacing: 0; text-transform: none; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; margin-inline-end: 3px; }
/* The line's own arithmetic: unit price above, what the slab comes to below. */
.ps-qitem__sum { display: block; font-size: 12px; font-weight: 600; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; margin-top: 2px; }
/* Narrowed out of a search — display, not [hidden]: both are flex. */
.ps-qitem.is-off { display: none; }
.ps-shelf.is-off { display: none; }
.ps-shelf__sub.is-off { display: none; }
.ps-shelf__subhead { margin: 16px 0 2px; font-size: 11px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-soft); }
.ps-shelf__head + .ps-shelf__sub .ps-shelf__subhead { margin-top: 0; }
.ps-req-empty { font-size: 13px; color: var(--ps-ink-soft); line-height: 1.6; margin: 18px 0 12px; }
.ps-req-add { font-size: 12px; color: var(--ps-ink-soft); margin: 6px 0 34px; }
.ps-req-empty a, .ps-req-add a { color: var(--vip-gold); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }

@media (max-width: 560px) {
  .ps-request { width: 100vw; border-inline-start: 0; }
}
@media (max-width: 768px) {
  .ps-request__close { padding: 8px 12px; }
  .ps-qitem__rm { padding: 8px 0; }
}

/* ===========================================================================
   2026-09-10 — TWO OPEN REQUESTS + THEME PARITY (changes/0291)
   The requests page renders every open request as its own document; Hero and
   Sidebar take the behaviour layer Classic already had; the piece page's
   actions wire. The two-request CAP half of 0291 is superseded (0298 —
   owner, 2026-09-11: "any count of request"); the documents render for any
   count.
   =========================================================================== */

/* One request document per open request — dressed as a CARD (0298): the
   document ground, a hairline, the record's own corner. The first sits
   close under the page head; each further document opens with more air, so
   the documents read as records rather than one long list. (`.ps-req` was
   taken — it is the older per-piece request ROW above; this is the
   DOCUMENT block.) */
.ps-req-block { display: block; margin-top: 30px; background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 4px; padding: 15px 19px 5px; }
[data-ps-reqs] > .ps-req-block:first-child { margin-top: 10px; }
/* The document's last line closes on the card's own edge. */
.ps-req-block .ps-shelf:last-of-type { margin-bottom: 0; }
.ps-req-block .ps-shelf:last-of-type .ps-qitem:last-child { border-bottom: 0; }
/* Denser lines so more of the request fits on screen: 3:2 thumbnails,
   tighter rows and group gaps, and a trash icon in place of the Remove word. */
.ps-req-block .ps-shelf { margin-bottom: 18px; }
.ps-req-block .ps-shelf__head { margin-bottom: 4px; }
.ps-req-block .ps-req-figs { margin: 12px 0 14px; padding: 9px 15px; }
.ps-req-block .ps-qitem { padding: 8px 0; }
.ps-req-block .ps-qitem__img { width: 81px; height: auto; aspect-ratio: 3 / 2; }
.ps-qitem__rm--icon { display: flex; margin: 0; padding: 6px; flex: none; }
.ps-qitem__rm--icon svg { width: 16px; height: 16px; }
/* One request at a time: the picks row under the tabs, number over its
   date and container fill. A card is display:block, so [hidden] is spelled
   out; under a live picks row the one shown document sits close. */
.ps-req-picks { display: flex; flex-wrap: wrap; gap: 8px; }
.ps-req-picks[hidden], .ps-req-block[hidden] { display: none; }
.ps-req-pick { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; min-width: 214px; padding: 7px 13px; text-align: start; font-family: inherit; color: var(--vip-nav-fg); }
.ps-req-pick b { font-weight: 600; font-variant-numeric: tabular-nums; }
.ps-req-pick__foot > span:first-child, .ps-req-pick em { font-size: 11px; font-weight: 400; font-style: normal; color: var(--ps-ink-soft); }
.ps-req-pick__head, .ps-req-pick__foot { display: flex; align-self: stretch; align-items: center; justify-content: space-between; gap: 12px; }
.ps-req-dots { display: flex; gap: 3px; }
.ps-req-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--ps-line); }
.ps-req-dots span.is-on { background: var(--ps-accent); }
.ps-req-picks:not([hidden]) ~ .ps-req-block,
.ps-req-picks:not([hidden]) ~ [data-ps-reqs] > .ps-req-block { margin-top: 12px; }

/* ---- Remove asks first (changes/0311) --------------------------------------
   A small centred dialog over the drawer's scrim: the family's own ps-*
   primitive, since app.css's modal never crosses into it (D28). Same z pair
   as the drawer; the gold Remove is the room's one strong button (the
   palette holds no alarm red, D14). */
.ps-confirm {
  position: fixed; top: 50%; left: 50%; z-index: 71; transform: translate(-50%, -50%);
  width: 380px; max-width: calc(100vw - 32px);
  background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 6px;
  box-shadow: 0 14px 40px rgba(28, 26, 23, .24);
  padding: 20px 22px 18px;
}
.ps-confirm__title { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 23px; font-weight: 500; margin: 0; color: var(--vip-nav-fg); }
.ps-confirm__text { font-size: 13px; line-height: 1.55; color: var(--ps-ink-soft); margin: 8px 0 0; }
.ps-confirm__text b { font-weight: 600; color: var(--vip-nav-fg); }
.ps-confirm__foot { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
@media (max-width: 768px) {
  .ps-confirm__foot .ps-hold, .ps-confirm__foot .ps-cta { min-height: 44px; }
}

/* The active request wears the landing tag; the other offers the move. Both
   sit inline at the end of the document's head line, one step quieter. */
.ps-req-tag { display: inline-block; margin-inline-start: 10px; padding: 2px 8px; border: 1px solid var(--vip-gold); border-radius: 4px; font-size: 10px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; color: var(--vip-gold); vertical-align: 1px; }
.ps-req-make { margin-inline-start: 10px; padding: 0; background: none; border: 0; cursor: pointer; font-size: 10px; font-weight: 700; letter-spacing: .11em; text-transform: uppercase; color: var(--ps-ink-mute); text-decoration: underline; text-underline-offset: 2px; }
.ps-req-make:hover { color: var(--vip-gold); }

/* A line priced FOR this client on this piece — SHW-501's per-item override,
   named where the number is. */
.ps-qitem__tag { display: inline-block; margin-inline-start: 8px; padding: 1px 7px; border: 1px solid var(--vip-gold); border-radius: 4px; font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--vip-gold); vertical-align: 1px; }

/* Hero's Refine row opens the page — the old inline margin-top:0, homed. */
.ps-body--hero .ps-cols { margin-top: 0; }

/* CTAs and actions that navigate are anchors now; same clothes, no underline. */
a.ps-cta, a.ps-action { text-decoration: none; }
.ps-action[disabled] { opacity: .55; cursor: default; }

/* Sidebar's row name opens the piece, in the row's own ink. */
a.ps-row__name { color: inherit; text-decoration: none; }
a.ps-row__name:hover { text-decoration: underline; text-underline-offset: 2px; }
.ps-row__img a { display: block; height: 100%; }

/* The piece page's action column — was three inline style attributes (adr-018).
   Its two-column acts-row died with the Visit the Showroom action (0298):
   Add to Request and Send Me a Sample stack full-width. */
.ps-product__acts { display: flex; flex-direction: column; gap: var(--ps-space-2); }
.ps-product__acts .ps-state { margin: var(--ps-space-2) 0 0; }
.ps-product__origin { margin-bottom: var(--ps-space-4); }
.ps-product .ps-spec { margin-bottom: var(--ps-space-5); }
.ps-product__lede { font-size: var(--ps-text-body); margin-bottom: var(--ps-space-5); }

/* ===========================================================================
   2026-09-11 — THE SIGNED-OUT SHOWROOM (changes/0298)
   showroom-visitor.html: the same room with no name on it (SHW-101/103).
   The visitor's temp cart (sessionStorage psVisitorCart) is a per-tab
   SELECTION, never the request store; its pill is [data-ps-cart-count], an
   attribute neither request painter touches.
   =========================================================================== */

/* The mast's right slot on the visitor page: where a client's VIP mark sits,
   the visitor gets the two ways in. */
.ps-top__auth { justify-self: end; display: flex; align-items: center; gap: 8px; }
/* The base ghost's navy ink vanishes on the navy mast, so the on-mast ghost
   keeps the mast's own foreground and only takes the gold on hover. */
.ps-cta--onmast { background: transparent; color: var(--vip-mast-fg); border-color: rgba(244, 241, 234, .38); }
.ps-cta--onmast:hover { border-color: var(--vip-gold); color: var(--vip-gold); }
/* Two buttons where the family's mast carries one word-mark: below 880px the
   greeting drops to its own full-width row so neither is crushed. Scoped to
   the visitor body — the client mast keeps its greeting on the first row at
   every width, and below 600px drops the supplier's mark instead (0304,
   further down). */
@media (max-width: 880px) {
  .ps-body--visitor .ps-top__mast-in { grid-template-columns: 1fr auto; }
  .ps-body--visitor .ps-top__greet { grid-column: 1 / -1; grid-row: 2; }
}

/* .ps-state is display:flex, which outranks [hidden] — without this line
   the sign-in and register sent states never hide. */
.ps-state[hidden] { display: none; }

/* The selection drawer's own furniture. */
.ps-cart__cta { display: flex; width: 100%; justify-content: center; margin-top: 14px; }
.ps-cart__note { font-size: 11.5px; color: var(--ps-ink-soft); line-height: 1.55; margin: 9px 0 0; }
.ps-cart__empty { font-size: 12.5px; color: var(--ps-ink-soft); margin: 14px 0 0; }
/* A button that reads as a link — the register/sign-in cross-references
   inside a panel's prose. */
.ps-linklike { background: none; border: 0; padding: 0; font: inherit; color: var(--vip-gold); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.ps-linklike:hover { color: var(--vip-rule); }
/* Inside a coloured band the link takes the band's own ink (0337): gold on the
   error / warn / success grounds measured 2.2:1. The underline keeps it a link,
   and hover thickens that underline instead of turning gold again. */
.ps-state .ps-linklike { color: inherit; }
.ps-state .ps-linklike:hover { text-decoration-thickness: 2px; }

/* The gate's code-less way in (offers/offer-access.html). */
.ps-gate__browse { margin: var(--ps-space-2) 0 0; font-size: var(--ps-text-meta); color: var(--ps-ink-soft); }
.ps-gate__browse a { color: var(--ps-ink); }

/* ===========================================================================
   2026-09-14 — THE REQUESTS PAGE'S TWO VIEWS (changes/0300)
   Open Requests and Earlier Requests split onto the sheet's own two-view
   tab primitive (.ps-tabs). The family's gold is --vip-gold, which the
   primitive's --ps-gold fallback cannot see, so the lit rule is pointed at
   it for the family bodies; a view is a plain block, so [hidden] needs no
   help — .ps-view only gives the list its air under the strip's rule.
   =========================================================================== */
.ps-body--classic .ps-tabs__btn.is-on,
.ps-body--hero .ps-tabs__btn.is-on,
.ps-body--sidebar .ps-tabs__btn.is-on { border-bottom-color: var(--vip-gold); }
.ps-view { margin-top: 14px; }
/* The archive's documents (0301) open with the same air the open view's
   first document gets under the strip. */
.ps-view > .ps-req-block:first-child { margin-top: 10px; }

/* ===========================================================================
   2026-09-14 — THE WAY OUT (changes/0302)
   The signed-in mast's right slot mirrors the visitor's: where the visitor
   floor carries its two ways in (.ps-top__auth), the client's mast carries
   the VIP mark and the one way out, side by side behind a hairline. Sign Out
   is a link to showroom-visitor.html and nothing more — the prototype has no
   session to end, and the signed-out floor IS what "out" means here. Not a
   fourth nav promise: the nav names places in the room, and leaving it is
   not one of them. The rail folds the same pair into its foot.
   =========================================================================== */
.ps-top__me { justify-self: end; display: flex; align-items: center; gap: 14px; }
/* Written as a.ps-top__out so it outranks the family's base link rules
   (.ps-body a / a:hover) and app.css's on the piece page — the base hover
   paints the sheet's near-black accent, which vanishes on the navy mast (the
   bug 0288's polish pass hit on the nav). The ink is the mast's own
   foreground, dimmed, and the hairline is mixed out of the same token (the
   .ps-lang__btn idiom), so a rebranded bar keeps a legible way out. */
a.ps-top__out {
  padding-block: 6px; padding-inline: 14px 0;
  border-inline-start: 1px solid color-mix(in oklab, var(--vip-mast-fg) 36%, transparent);
  font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--vip-mast-fg); opacity: .78; white-space: nowrap; text-decoration: none;
}
a.ps-top__out:hover { color: var(--vip-gold); opacity: 1; }
/* The family's focus ring is drawn in --ps-ink — near-black on the navy mast
   and rail, where it vanishes — and the link's resting opacity would dim it
   further. The keyboard gets the room's gold instead, at full strength. */
a.ps-top__out:focus-visible { outline: 2px solid var(--vip-gold); outline-offset: 3px; opacity: 1; }
/* Below 880px (the visitor mast's own collapse) the mast has no room for a
   word beside the mark: the way out drops under the VIP mark, right-aligned,
   costing the greeting no width. Scoped to the mast — the rail is 264px wide
   at every viewport and keeps the pair on one line. */
@media (max-width: 880px) {
  .ps-top__mast .ps-top__me { flex-direction: column; align-items: flex-end; gap: 2px; }
  .ps-top__mast a.ps-top__out { padding-inline-start: 0; border-inline-start: 0; }
  /* A finger's target (the responsive skill's 44px): the mast's column and
     the rail's row both have the height to spare, so the box grows and the
     mast does not. */
  a.ps-top__out { padding-top: 14px; padding-bottom: 14px; }
}
/* Below 600px the client mast has no room for the supplier's mark: the
   greeting's shortest line and the VIP pair leave its 1fr track nothing.
   The rooms had been letting it vanish on its own (the image is capped at
   100%, which is a 0 min-content under a 1fr track — gone below 480px,
   shrunk between 480 and 600), while the piece page, whose image carries no
   cap, kept it at 128px and pushed the pair 11px past the frame at 375. Said
   out loud now, for every mast that carries the pair: the mark steps out and
   the greeting takes the row beside the pair. The way home stays in the nav
   under it. The visitor floor keeps its own collapse (above). */
@media (max-width: 600px) {
  .ps-top__mast-in:has(> .ps-top__me) { grid-template-columns: 1fr auto; }
  .ps-top__mast-in:has(> .ps-top__me) > .ps-top__supplier { display: none; }
}

/* ===========================================================================
   2026-09-14 — EVERY KIND OF THING (changes/0304)
   The rooms carry all seven product types now — the portal's own seven, by
   its names and in its order (js/showroom-lots.js) — so a card says what
   KIND of thing it is above its name, a Sidebar row says it beside the
   material, and every arrangement shows the kinds as a second chip row under
   the collections: a lit kind is a lens, exactly as a lit collection is
   (owner, 2026-09-14: "different product types, same as defined on portal,
   on collection view"). Refine keeps the material.
   =========================================================================== */
/* The card's kind, above the gold eyebrow where one exists — the tag voice
   the shelf heads and the row's material already speak. */
.ps-lot__kind { display: block; font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); margin-bottom: 2px; }
/* The row form says it beside the material, a middot between. An inline-block,
   not a plain inline: the markup butts it against the material with no space
   between, and an inline run with no space in it cannot wrap — at 375px the
   name line of three rows carried the kind out past the frame and put the
   page on a sideways scroll. An atomic inline breaks on either side of
   itself, so the kind drops under the name where the line has no room. */
.ps-row__kind { display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); margin-inline-start: 6px; }
.ps-row__kind::before { content: '\00B7 '; }

/* =============================================================================
   DISPLAY STATE — what the console publishes, read on the buyer side.

   `js/showroom-display.js` stamps three attributes on <body>; everything that
   can be hidden outright is hidden HERE, declaratively, so a new card or block
   inherits the behaviour by carrying the hook rather than by being wired up.
   Only text that has to be REWRITTEN (a price with tax, a quantity reading) is
   done in script. See wiki G842 for why this reader had to exist at all.
   ============================================================================= */
body[data-sd-price="off"] [data-sd="price"],
body[data-sd-price="off"] .ps-lot__price,
body[data-sd-price="off"] .ps-row__price,
body[data-sd-price="off"] [data-sp-price-card]  { display: none !important; }
body[data-sd-price="on"]  [data-sd="noprice"],
body[data-sd-price="on"]  [data-sp-ask-price]   { display: none !important; }
body[data-sd-qty="off"]   [data-sd="qty"]       { display: none !important; }
body[data-sd-items="off"] [data-sd="items"],
body[data-sd-items="off"] .ps-qitem__ref        { display: none !important; }
body[data-sd-items="on"]  [data-sd="noitems"]   { display: none !important; }

/* Where the price is withheld the card says so, rather than leaving a gap that
   reads as a card which failed to load. Injected by the reader. */
.ps-lot__onreq { font-size: 12px; font-weight: 600; color: var(--ps-ink-mute); }

/* "In Stock / Out of Stock Only" is a STATE, so it takes the platform's badge
   shape and its success token (memory.md rule 19) — never a fresh green, and
   never colour without the word. */
.sd-badge {
  display: inline-block; padding: 1px 8px; border-radius: 7px;
  font-size: 12px; font-weight: 600;
  background: var(--color-success-50, #E8F5EE);
  color: var(--color-success-ink, #1B7A4B);
  border: 1px solid var(--color-success, #2E9E63);
}

/* ===========================================================================
   2026-10-02 — ON WATER (changes/0332)
   The workspace's third view: what the client has already bought, packed and
   afloat. An order wears the archive's document dress with three asks on its
   head line — the order sheet, the bill of lading, and the chart — in the
   slot where an open request carries its landing tag. The chart opens the
   family's own right-hand drawer (.ps-request) over the shared scrim, so
   nothing new is invented for it but its contents.
   =========================================================================== */

/* The three asks sit at the RIGHT EDGE of the order's head, opposite its
   number, on a row of their own — not inline in the sentence, where an icon
   big enough to be pressed would push the line about. Below 560px the row
   wraps and they keep the right edge under the line they belong to. */
.ps-ord-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 16px; }
.ps-ord-acts { display: inline-flex; align-items: flex-start; gap: 4px; flex: none; margin-inline-start: auto; }
/* Mark over caption: a drawn mark says WHICH file, the word under it says
   what the file IS — an order, a bill of lading, a container. The caption is
   the sheet's floor size (10px, memory.md rule 4: the least important line on
   screen), and it keeps the button's accessible name honest — each one
   contains the word under the mark. */
.ps-ord-act { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 6px 8px; background: none; border: 0; border-radius: 6px; cursor: pointer; font-family: inherit; }
.ps-ord-act img { width: 34px; height: 34px; }
.ps-ord-act__lbl { font-size: 10px; font-weight: 600; line-height: 1.2; color: var(--ps-ink-soft); white-space: nowrap; }
.ps-ord-act:hover .ps-ord-act__lbl { color: var(--vip-nav-fg); }
/* ALL THREE ARE EXPORT MARKS, one set. Two are the portal's own
   (img/ExportXLS.png, img/ExportPDF.png — the pair stock.html exports with,
   at the 34px it shows them); the third (img/ExportMAP.svg) is drawn to
   their card, palette and geometry, with a pin where the files carry a down
   arrow. Each carries its own colour, so none is painted from here — the
   press tints the ground under them instead. */
.ps-ord-act:hover { background: var(--vip-page-bg); }
/* What a press WOULD hand over, named under the head line and gone again in
   three seconds — the strip's own courtesy. */
.ps-ord-note { margin: 7px 0 0; font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--vip-gold); }
.ps-ord-note[hidden] { display: none; }

/* The chart — an OpenStreetMap embed, framed like every other box in the
   room, and fetched only when a client opens the drawer.

   ⚠ THE EMBED CARRIES ITS OWN CREDIT BAR ("Report a problem | © OpenStreetMap
   contributors | Make a Donation. Website and API terms"), and at this
   drawer's width it wraps to two lines of foreign type across the foot of the
   chart. Nothing in this sheet can restyle it — it is a cross-origin
   document. So the frame is 40px SHORTER than the iframe inside it and the
   bar scrolls out of view, while the credit it carries is re-stated under the
   map in the room's own voice (.ps-map__credit, markup). The attribution is
   required and stays visible; only OSM's layout of it is dropped. */
.ps-map { margin: 2px 0 0; height: 280px; border: 1px solid var(--vip-line); border-radius: 4px; overflow: hidden; background: #E8E4DC; }
.ps-map iframe { display: block; width: 100%; height: 320px; border: 0; }
.ps-map__credit { margin: 6px 0 10px; font-size: 10px; color: var(--ps-ink-mute); }
.ps-map__credit a { color: var(--ps-ink-mute); text-decoration: underline; text-underline-offset: 2px; }
.ps-map__credit a:hover { color: var(--vip-gold); }
/* The carrier's facts under it: label over value, read down the drawer —
   never a label and its value at opposite ends of a row (memory.md rule 6). */
.ps-voy__row { display: flex; flex-direction: column; gap: 1px; margin: 0; padding: 9px 0; border-bottom: 1px solid var(--vip-line); }
.ps-voy__row:last-child { border-bottom: 0; }
.ps-voy__row b { font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); }
.ps-voy__row span { font-size: 12.5px; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }

@media (max-width: 768px) {
  /* The asks are 46px square already — a finger's target without help. */
  .ps-map { height: 220px; }
  .ps-map iframe { height: 260px; }
}

/* ---- In the Yard: ship a box as it is ---------------------------------------
   One panel per order between its figures and its lines (showroom-workspace.html).
   The state sentence leads with its verb in bold; the booking form is the
   client's forwarder's details, laid on a 6-column grid so each field takes the
   width its content needs — a booking number or a date is narrow, a forwarder
   or a port is wide, and the empty space beside a short field is left empty
   (memory.md: size fields to their content). Controls sit on the page's beige,
   never white on the white card. */
.ps-ship { margin: 0 0 26px; padding: 16px 18px; background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 4px; }
.ps-ship__state { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; }
.ps-ship__state p { flex: 1 1 320px; margin: 0; font-size: 13px; line-height: 1.6; color: var(--ps-ink-soft); }
.ps-ship__state b { font-weight: 600; color: var(--vip-nav-fg); }
.ps-ship__form { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 15px 16px; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--vip-line); }
.ps-ship__field { grid-column: span 2; display: flex; flex-direction: column; gap: 5px; font-size: 11.5px; color: var(--ps-ink-soft); }
.ps-ship__field--wide { grid-column: span 3; }
.ps-ship__field--full { grid-column: 1 / -1; }
.ps-ship__field input, .ps-ship__field textarea { font: inherit; font-size: 12.5px; color: var(--vip-nav-fg); background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 6px; padding: 8px 11px; resize: vertical; min-width: 0; }
.ps-ship__field input:focus, .ps-ship__field textarea:focus { outline: none; border-color: var(--vip-gold); }
.ps-ship__foot { grid-column: 1 / -1; display: flex; justify-content: flex-end; }
.ps-ship__sent { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 22px; margin-top: 14px; padding-top: 6px; border-top: 1px solid var(--vip-line); }
.ps-ship__sent .ps-voy__row:last-child { border-bottom: 1px solid var(--vip-line); }
.ps-ship__again { margin: 12px 0 0; display: flex; justify-content: flex-end; }
.ps-ship__none { margin: 14px 0 0; padding-top: 14px; border-top: 1px solid var(--vip-line); font-size: 12.5px; color: var(--ps-ink-soft); display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
@media (max-width: 768px) {
  .ps-ship__form, .ps-ship__sent { grid-template-columns: minmax(0, 1fr); }
  .ps-ship__field, .ps-ship__field--wide { grid-column: 1 / -1; }
  .ps-ship .ps-cta, .ps-ship .ps-hold { min-height: 44px; }
}

/* ---- The order drawer (showroom-workspace.html, 2026-10-05) -----------------
   What the retired client page for an order carried, in the room's voice, one
   subject at a time (owner, 2026-10-06): payments with the balance, documents
   with the approvals, or claims. The tracking lives in Where It Is Now
   (.ps-voy__more). Wider than Where It Is Now because it carries forms. States are words in colour, never
   filled chips; controls sit on the page's beige, never white on white. */
.ps-od { width: 600px; }
/* The workspace's two drawers read in more contrast than the room around
   them (owner, 2026-10-06: dense facts in soft grey on ivory were hard to
   read). Secondary text 7.5:1 and labels 5.4:1 on the drawer's ground
   (were 4.0:1 and 2.8:1), hairlines that show, and the gold that writes a
   word darkened to 5.3:1 — the room's gold stays on fills and hovers. */
.ps-od, .ps-voy { --ps-ink-soft: #454B57; --ps-ink-mute: #5A606C; --vip-line: #CFC8BC; }
.ps-od .ps-od__link, .ps-od .ps-ord-note, .ps-voy .ps-ord-note { color: #7A5C22; }
.ps-od .ps-request__doc, .ps-voy .ps-request__doc { font-size: 12px; }
/* The order's four new asks wear a drawn mark the size of the export marks. */
.ps-ord-act__mark { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 6px; background: var(--vip-page-bg); border: 1px solid var(--vip-line); color: var(--vip-nav-fg); }
.ps-ord-act__mark svg { width: 18px; height: 18px; }
.ps-ord-act:hover .ps-ord-act__mark { border-color: var(--vip-gold); color: var(--vip-gold); }

.ps-od .ps-ord-note { margin: 10px 0 0; }
.ps-od__k { font-size: 10px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--ps-ink-mute); }
.ps-od__fine { margin: 8px 0 0; font-size: 12px; line-height: 1.55; color: var(--ps-ink-soft); }
.ps-od__fine b { font-weight: 600; color: var(--vip-nav-fg); }
.ps-od__held { color: var(--ps-danger); font-weight: 600; }
.ps-od__link { background: none; border: 0; padding: 0 0 0 6px; font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--vip-gold); cursor: pointer; }
.ps-od__link:hover { color: var(--vip-nav-fg); }

/* The balance: number first, the word over it, the two ways to pay beside. */
.ps-od__bal { margin: 14px 0 0; padding: 16px 18px; background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 4px; }
.ps-od__bal-top { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 12px 16px; }
.ps-od__bal-fig { display: flex; flex-direction: column; gap: 2px; }
.ps-od__due { font-family: 'Cormorant Garamond', Georgia, serif; font-size: 34px; font-weight: 500; line-height: 1.05; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }
.ps-od__when { font-size: 12px; color: var(--ps-ink-soft); }
.ps-od__bal-acts { display: flex; flex-wrap: wrap; gap: 8px; }
.ps-od__paid { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.ps-od__bar { flex: 1; height: 4px; }
.ps-od__bar rect { fill: var(--ps-success); }
.ps-od__bar .ps-od__bar-bg { fill: var(--vip-line); }
.ps-od__paid-fig { font-size: 12px; font-weight: 600; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }
.ps-od__rows { margin-top: 10px; border-top: 1px solid var(--vip-line); }
.ps-od__rows p { display: flex; justify-content: space-between; gap: 12px; margin: 0; padding: 6px 0; font-size: 12.5px; color: var(--ps-ink-soft); border-bottom: 1px solid var(--vip-line); }
.ps-od__rows b { font-weight: 600; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }
.ps-od__rows b.is-paid { color: var(--ps-success); }
.ps-od__rep { display: flex; align-items: center; gap: 10px; margin: 12px 0 0; font-size: 12px; color: var(--ps-ink-soft); }
.ps-od__rep b { font-weight: 600; color: var(--vip-nav-fg); }
.ps-od__face { display: grid; place-items: center; flex: none; width: 30px; height: 30px; border-radius: 50%; background: var(--vip-nav-fg); color: var(--vip-mast-fg); font-size: 10.5px; font-weight: 700; }
.ps-od__rep .ps-od__link { margin-inline-start: auto; }

/* The progress, in the supplier's words for its clients. */
.ps-od__path { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0; padding: 0; font-size: 12px; color: var(--ps-ink-mute); }
.ps-od__path li { display: inline-flex; align-items: center; gap: 6px; }
.ps-od__path li::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--vip-line); }
.ps-od__path .is-done { color: var(--ps-success); }
.ps-od__path .is-done::before { background: var(--ps-success); }
.ps-od__path .is-now { color: var(--ps-warn); font-weight: 600; }
.ps-od__path .is-now::before { background: var(--ps-warn); }

.ps-od__panel { padding-top: 4px; }
.ps-od__panel[hidden] { display: none; }
.ps-od__sub { margin: 12px 0 0; font-size: 12px; color: var(--ps-ink-soft); }
.ps-od__sub b { font-weight: 600; color: var(--vip-nav-fg); }
.ps-od__sub-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
.ps-od__h { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; margin: 20px 0 6px; font-size: 14px; font-weight: 700; color: var(--vip-nav-fg); }
.ps-od__box { margin-top: 14px; padding: 2px 16px 14px; background: var(--vip-card-bg); border: 1px solid var(--vip-line); border-radius: 4px; }
.ps-od__box > .ps-od__h:first-child { margin-top: 14px; }
.ps-od__ok { font-size: 11px; font-weight: 600; color: var(--ps-success); }

/* Tracking, in Where It Is Now under the carrier's facts. */
.ps-voy__more { border-top: 1px solid var(--vip-line); }
.ps-od__miles { list-style: none; margin: 0; padding: 0; }
.ps-od__miles li { position: relative; padding: 6px 0 6px 18px; font-size: 12.5px; line-height: 1.45; color: var(--vip-nav-fg); }
.ps-od__miles li::before { content: ""; position: absolute; left: 2px; top: 12px; width: 7px; height: 7px; border-radius: 50%; background: var(--vip-line); }
.ps-od__miles li.is-done::before { background: var(--ps-success); }
.ps-od__miles li.is-now::before { background: var(--ps-warn); }
.ps-od__miles li > span { display: block; font-size: 11px; color: var(--ps-ink-mute); font-variant-numeric: tabular-nums; }
.ps-od__miles em { font-style: normal; color: var(--ps-ink-mute); }
.ps-od__photos { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; }
.ps-od__photos img, .ps-od__more { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 4px; border: 1px solid var(--vip-line); }
.ps-od__more { background: var(--vip-page-bg); font: inherit; font-size: 13px; font-weight: 600; color: var(--vip-nav-fg); cursor: pointer; }
.ps-od__more:hover { border-color: var(--vip-gold); }
/* Switch on the left of its label, never boxed; the checkbox carries the state. */
.ps-od__switches { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.ps-od__sw { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--vip-nav-fg); cursor: pointer; }
.ps-od__sw input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.ps-od__sw-track { position: relative; flex: none; width: 30px; height: 17px; border-radius: 5px; background: var(--vip-line); transition: background 120ms; }
.ps-od__sw-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; border-radius: 4px; background: #fff; transition: transform 120ms; }
.ps-od__sw input:checked + .ps-od__sw-track { background: var(--vip-nav-fg); }
.ps-od__sw input:checked + .ps-od__sw-track::after { transform: translateX(13px); }
.ps-od__sw input:focus-visible + .ps-od__sw-track { outline: 2px solid var(--vip-gold); outline-offset: 2px; }

/* Approval. */
.ps-od__sign { display: flex; align-items: center; gap: 16px; margin: 12px 0 0; font-size: 11px; line-height: 1.5; color: var(--ps-ink-soft); }
.ps-od__sign b { font-weight: 600; color: var(--vip-nav-fg); }
.ps-od__hand { font-family: 'Cormorant Garamond', Georgia, serif; font-style: italic; font-size: 24px; color: var(--vip-nav-fg); }

/* Documents and invoices: the mark, the name over its line, the state as a
   word, the act last. */
.ps-od__docs, .ps-od__invs { list-style: none; margin: 10px 0 0; padding: 0; }
.ps-od__docs li, .ps-od__invs li { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; padding: 10px 0; border-bottom: 1px solid var(--vip-line); }
.ps-od__docs img { width: 26px; height: 26px; flex: none; }
.ps-od__doc { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: 2px; font-size: 11.5px; line-height: 1.45; color: var(--ps-ink-soft); }
.ps-od__doc b { font-size: 12.5px; font-weight: 600; color: var(--vip-nav-fg); }
.ps-od__doc em { font-style: normal; font-weight: 500; font-size: 10.5px; color: var(--ps-ink-mute); }
.ps-od__state { font-size: 11.5px; font-weight: 600; color: var(--ps-ink-soft); }
.ps-od__state.is-ok { color: var(--ps-success); }
.ps-od__state.is-warn { color: var(--ps-warn); }
.ps-od__state.is-held { color: var(--ps-danger); }
.ps-od__lock { font-size: 11px; color: var(--ps-ink-mute); }
/* Download is a mark, not a word on a white button over the white drawer. */
.ps-od__dl { display: grid; place-items: center; flex: none; width: 30px; height: 30px; padding: 0; background: none; border: 0; border-radius: 6px; color: var(--vip-nav-fg); cursor: pointer; }
.ps-od__dl svg { width: 18px; height: 18px; }
.ps-od__dl:hover { background: var(--vip-page-bg); color: var(--vip-gold); }
.ps-od__amt { display: flex; flex-direction: column; align-items: flex-end; font-size: 11px; color: var(--ps-ink-mute); }
.ps-od__amt b { font-size: 13px; font-weight: 600; color: var(--vip-nav-fg); font-variant-numeric: tabular-nums; }
.ps-od__inv-acts { display: inline-flex; gap: 4px; }

/* Forms reuse the booking form's grid (.ps-ship__form); selects match its inputs. */
.ps-od .ps-ship__form { margin-top: 10px; padding-top: 12px; }
.ps-od .ps-ship__field select { font: inherit; font-size: 12.5px; color: var(--vip-nav-fg); background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 6px; padding: 8px 11px; min-width: 0; }
.ps-od .ps-ship__field select:focus { outline: none; border-color: var(--vip-gold); }
.ps-od .ps-ship__field .ps-od__fine { margin: 0; font-size: 11px; }
.ps-od__foot { align-items: center; gap: 10px; flex-wrap: wrap; }
.ps-od__foot .ps-od__fine { flex: 1 1 220px; margin: 0; }
.ps-od__promise-act { grid-column: span 2; align-self: end; }
.ps-od__methods { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
.ps-od__methods .ps-hold:disabled { opacity: .45; cursor: default; }
.ps-od__methods .ps-od__fine { flex: 1 1 200px; margin: 0; }
.ps-od__file { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--ps-ink-mute); }

/* Claims. */
.ps-od__lang { display: inline-flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--ps-ink-soft); }
.ps-od__lang select { font: inherit; font-size: 12px; color: var(--vip-nav-fg); background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 6px; padding: 5px 8px; }
.ps-od__qa { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 10px 16px; font-size: 12.5px; color: var(--vip-nav-fg); }
/* A yes/no is a two-sided switch: both words, the live one dark and bold. */
.ps-yn { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; }
.ps-yn__opt { background: none; border: 0; padding: 0; font: inherit; color: var(--ps-ink-mute); cursor: pointer; }
.ps-yn[data-on="yes"] [data-yn="yes"], .ps-yn[data-on="no"] [data-yn="no"] { color: var(--vip-nav-fg); font-weight: 700; }
.ps-yn__sw { position: relative; width: 30px; height: 17px; border-radius: 5px; background: var(--vip-nav-fg); cursor: pointer; }
.ps-yn__sw::after { content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; border-radius: 4px; background: #fff; transition: transform 120ms; }
.ps-yn[data-on="no"] .ps-yn__sw::after { transform: translateX(13px); }
.ps-od__pick { display: flex; flex-direction: column; }
.ps-od__pick label { display: flex; align-items: center; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--vip-line); cursor: pointer; }
.ps-od__pick input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--vip-nav-fg); flex: none; }
.ps-od__qty { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ps-ink-soft); }
.ps-od__qty input { width: 56px; font: inherit; font-size: 12.5px; color: var(--vip-nav-fg); background: var(--vip-page-bg); border: 1px solid var(--vip-line); border-radius: 6px; padding: 6px 8px; }
.ps-od__drop { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 14px; border: 1px dashed var(--vip-line); border-radius: 4px; background: var(--vip-page-bg); font-size: 12px; color: var(--ps-ink-soft); }
.ps-od__policy { margin: 0; padding-left: 18px; font-size: 12px; line-height: 1.55; color: var(--ps-ink-soft); }
.ps-od__policy li { margin-top: 5px; }
.ps-od__policy b { font-weight: 600; color: var(--vip-nav-fg); }

/* The workspace's one Vava invitation: the pending request reads as a warning word. */
.ps-join { margin-top: 18px; }
.ps-join__note { margin-inline-start: 6px; font-weight: 600; color: var(--ps-warn); }

@media (max-width: 768px) {
  /* Six asks on ORD-0042 outrun a phone: they wrap, still right-aligned. */
  .ps-ord-acts { flex: 1 1 100%; flex-wrap: wrap; justify-content: flex-end; }
  .ps-od__photos { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ps-od__qa { grid-template-columns: minmax(0, 1fr); }
  .ps-od .ps-cta, .ps-od .ps-hold { min-height: 44px; }
  .ps-od__promise-act { grid-column: 1 / -1; }
}
