/* ==========================================================================
   EC Carpets · Rugs — MINI CART (the header drawer)
   Ported from v9b css/cart.css (Round 60), which is authoritative for the
   geometry. Wave MC-1 of the plugin's docs/MINI-CART.md.

   ITS OWN FILE, not an addition to pb-shell.css or pb-header.css, for three
   reasons in the order they matter: it has to load AFTER pb-woo.css on the
   purchase path (see the enqueue in inc/woocommerce.php, which DECLARES that
   dependency rather than trusting the order); its entire selector surface is
   one prefixed component; and pb-shell.css is already 69KB.

   EVERY MEASUREMENT IS A CHROME TOKEN, not a number. All of them already exist
   in pb-tokens.css, audited before the port — including the whole z ladder,
   which is IDENTICAL between the two projects, so v9b's stacking argument
   transfers without a single number changing.

   WHAT THE SERVER OWNS. The panel is rendered by the plugin's
   PbEc\Cart\MiniCart and refreshed through WooCommerce's cart fragments, so
   empty sections and an empty foot are OMITTED rather than hidden. Only two
   things here still switch: the open state, and a foot whose fragment root has
   to stay in the DOM to be replaceable (§6).
   ========================================================================== */

/* --- 1. Progressive enhancement + the AB1 guard.
   The AB1 bug class is a display rule beating the UA's [hidden] at EQUAL
   specificity. `.cart-count { display: grid }` in pb-shell.css is (0,1,0) and
   ties, which is that bug exactly — and the badge genuinely reaches zero on
   every page now, so a zero badge that still paints is a live defect, not a
   hypothetical. One rule at (0,2,0) defuses it permanently. The .mini-cart
   entries are kept for anything a later wave decides to toggle.

   .no-js is the theme's own: purebold_javascript_detection() swaps it for `js`
   at wp_head priority 0, so this costs nothing and keeps a dead panel — which
   cannot open without its script — out of a JS-off page entirely. --- */
.no-js .mini-cart { display: none; }

.mini-cart__section[hidden],
.mini-cart__empty[hidden],
.mini-cart__foot[hidden],
.mini-cart__terms[hidden],
.cart-count[hidden] { display: none; }


/* --- 2. The panel.
   IT IS A MEMBER OF THE FLOATING CHROME, NOT A DRAWER (v9b Round 51). The
   client asked for "a right column panel, not a centred modal", "positioned
   carefully so it doesn't conflict with the menu, including the sticky menu
   aspect", and "aesthetically match the style". The chrome is a FAMILY — the
   utility strip and the header are separate bars inset --chrome-inset from the
   viewport edge, --chrome-radius cornered, --chrome-gap apart, on
   --shadow-glass — so the panel is built from the same four numbers rather
   than from approximations of them:
     top     the live bottom of the chrome, plus --chrome-gap
     right   --chrome-inset      bottom  --chrome-inset
     corners --chrome-radius     shadow  --shadow-glass
   It therefore never covers the header or the strip: the two sit in one
   vertical rhythm, strip then header then cart, with the same 7px between each.

   THE TOP TRACKS THE LIVE CHROME, NOT THE TOKEN. --chrome-bottom is the
   UNCONDENSED figure. Once the page has scrolled, the strip hides and the
   header rises to --chrome-inset, and a panel pinned to the token would float
   adrift with page content showing through the gap. The .chrome-condensed
   override below tracks it, keyed on the same class pb-shell.css uses to move
   the header itself, so the two can never disagree. pb-ec-nav.js sets that
   class on every page, so this is the chrome's behaviour and not a borrowed one.

   A SOLID GROUND, NOT GLASS — a considered break from the family. The other
   chrome elements are thin translucent bars over the quiet top of a page. This
   is a 420px column of photography, prices and hairline-separated rows standing
   over whatever the page is showing; a blur there turns the line items into a
   smear. Everything else about the surface — inset, radius, shadow, hairline
   ring — is the chrome's.

   STACKING. The scrim takes --z-popover and the panel --z-toast, so both clear
   the configurator's sticky add-to-cart bar and the mega menu. THE ONE
   DELIBERATE EXCEPTION is the chrome itself: while the cart is open, .cart-open
   lifts the strip and the header to --z-toast so they sit ABOVE the scrim
   rather than under its wash. They do not overlap the panel, so nothing fights;
   what it buys is a header that is genuinely present and genuinely clickable
   while the cart is up, which is what the client asked for. pb-ec-cart.js keeps
   the two honest by leaving the header out of the `inert` sweep as well —
   dimmed-and-dead or bright-and-live are the only self-consistent pairs, and
   the brief asked for the second.

   VISIBILITY IS SWITCHED, NOT INTERPOLATED. A transitioned visibility is still
   computing `hidden` at t=0, and .focus() on a hidden element is silently a
   no-op that leaves the reader on <body>. The panel is focused SYNCHRONOUSLY on
   open, so the visibility here is a 0s switch with a delay on the way OUT only:
   instant on open, held until the slide has finished on close. --- */

/* Declared at :root, not on the panel. v9b's second consumer is a docked shop
   filter bar that does not exist here, but a component width declared once is
   still right and costs nothing. */
:root { --cart-w: min(calc(100vw - var(--chrome-inset) * 2), 420px); }

.mini-cart__scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-popover);
  background: var(--scrim);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-standard),
              visibility 0s linear var(--dur-base);
}
.mini-cart__scrim.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-base) var(--ease-standard), visibility 0s;
}

/* The chrome rides OVER the scrim while the cart is open, so the strip and the
   header keep their own colour and stay clickable instead of going dim and dead
   behind the wash. They cannot collide with the panel: it starts a --chrome-gap
   below the header's foot. Both are (0,2,0) against pb-shell.css's own
   .site-header and .u-strip at (0,1,0), so they win on SPECIFICITY and cannot
   be defeated by a load-order shuffle. */
.cart-open .u-strip,
.cart-open .site-header { z-index: var(--z-toast); }

.mini-cart__panel {
  /* Unconditioned chrome: strip and header both still showing. */
  --cart-top: calc(var(--chrome-bottom) + var(--chrome-gap));
  position: fixed;
  top: var(--cart-top);
  right: var(--chrome-inset);
  bottom: var(--chrome-inset);
  z-index: var(--z-toast);
  width: var(--cart-w);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  color: var(--text);
  border-radius: var(--chrome-radius);
  box-shadow: var(--shadow-glass),
              inset 0 0 0 var(--hairline-width) var(--hairline);
  overflow: hidden;                    /* the corners have to clip the content */
  /* Clears its own right inset as well as its width, or it would rest 10px on
     screen while shut. */
  transform: translateX(calc(100% + var(--chrome-inset)));
  visibility: hidden;
  transition: transform var(--dur-base) var(--ease-out),
              top var(--dur-slow) var(--ease-standard),
              visibility 0s linear var(--dur-base);
}
/* Scrolled: the strip has gone and the header has risen to the top inset, so
   the cart follows it up. Same --dur-slow curve as the header's own `top`, so
   if the state flips while the cart is open the two travel together rather than
   one chasing the other. */
.chrome-condensed .mini-cart__panel {
  --cart-top: calc(var(--chrome-inset) + var(--header-h) + var(--chrome-gap));
}
.mini-cart__panel.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform var(--dur-base) var(--ease-out),
              top var(--dur-slow) var(--ease-standard),
              visibility 0s;
}
/* Focused programmatically on open so the dialog's name and role are announced
   before anything inside it. It is not a control, so it gets no ring. */
.mini-cart__panel:focus { outline: none; }


/* --- 3. Head: title, a live count, and the close control. A hairline under it,
   no fill — the same rule the rest of the site separates things with. The count
   reads across both collections ("1 rug · 3 samples"), which is why it is a line
   of its own under the title rather than a figure beside it.

   The count element is a FRAGMENT ROOT and is always present, carrying no text
   when the cart is empty. An empty block box generates no line box, so it is
   zero-height then — the same outcome as omitting it, reached without removing
   the hook a fragment refresh has to match. --- */
.mini-cart__head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: var(--space-sm);
  padding: var(--space-lg) var(--space-lg) var(--space-sm);
  border-bottom: var(--rule);
}
.mini-cart__title { font-size: var(--fs-h4); line-height: var(--lh-snug); color: var(--text-strong); }
.mini-cart__count {
  grid-column: 1;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.mini-cart__close {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: start;
  display: grid;
  place-items: center;
  width: var(--tap-min);
  height: var(--tap-min);
  margin: calc(var(--space-2xs) * -1) calc(var(--space-2xs) * -1) 0 0;
  border-radius: var(--radius-circle);
  font-size: 22px;
  line-height: 1;
  color: var(--text-muted);
}
.mini-cart__close:hover { background: var(--surface-recessed); color: var(--text-strong); }

/* The panel's own live region, and — once the drawer opens on every add — the
   SINGLE announcement path for a cart change. The badge is aria-hidden and the
   count reaches assistive tech only through the header anchor's aria-label,
   which is not announced when it changes on an unfocused element. Visually it is
   the quiet line under the head; empty, it takes no space. */
.mini-cart__status {
  padding: 0 var(--space-lg);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.mini-cart__status:not(:empty) { padding-block: var(--space-2xs); }

.mini-cart__body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 var(--space-lg);
}


/* --- 4. Sections.
   Rugs and samples are two different kinds of thing and the panel says so. The
   heading is the separator; there is no fill, no card and no rule ABOVE it —
   the line items already carry a hairline BELOW each row, so a rule over the
   heading would double up against the last row of the section before it. The
   space does the separating instead.
   A section with nothing in it is not rendered at all, so a cart holding only
   samples looks like a cart holding samples rather than one that is half
   broken. --- */
.mini-cart__section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block: var(--space-md) var(--space-2xs);
}
/* Caps meta voice, the same one the page uses for a label over a group. Sized
   UNDER the panel title on purpose: this is a divider, not a second heading
   competing with "Your Cart". */
.mini-cart__section-title {
  font-size: var(--fs-xs);
  font-weight: var(--w-medium);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  line-height: var(--lh-snug);
  color: var(--text-strong);
}
.mini-cart__section-count {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
}
/* The first section starts flush under the head's own hairline; the second keeps
   its full breathing space above it. */
.mini-cart__section:first-child .mini-cart__section-head { padding-top: var(--space-sm); }


/* --- 5. Line items.
   Separated by a hairline and nothing else — no card, no fill, no radius. --- */
.mini-cart__list { margin: 0; padding: 0; list-style: none; }
.mini-cart__item {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: start;
  column-gap: var(--space-sm);
  padding-block: var(--space-sm);
  border-bottom: var(--rule);
}
.mini-cart__thumb {
  display: block;
  width: 56px;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-media-sm);
  overflow: hidden;
  background: var(--surface-recessed);
}
/* The image is WooCommerce's own, through the woocommerce_cart_item_thumbnail
   filter, so it arrives with Woo's sizing attributes and the placeholder
   fallback comes free. It is sized here rather than trusted. */
.mini-cart__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mini-cart__lines { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mini-cart__range { color: var(--text-strong); }
.mini-cart__colour { font-size: var(--fs-sm); color: var(--text-muted); }

/* THE RUG'S CONFIGURATION. A <dl>, because these are name/value pairs and a
   screen reader should read "Shape, Round" rather than two loose strings; a
   two-column grid, because the labels are short and aligning them turns five
   lines into a scannable block. The wrapper <div> around each pair exists only
   to keep dt and dd associated in the DOM — display:contents drops it out of the
   layout so both land as grid items of the <dl> itself.
   The value column wraps rather than truncating: "Combination (2 round / 2
   square)" is the longest string this can hold and it is worth two lines. A cart
   line that hides the configuration is the failure this panel exists to avoid,
   so nothing here is allowed to elide. */
.mini-cart__spec {
  margin: var(--space-3xs) 0 0;
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-2xs);
  row-gap: 1px;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
}
.mini-cart__spec > div { display: contents; }
.mini-cart__spec dt { color: var(--text-muted); }
.mini-cart__spec dd { margin: 0; color: var(--text); }

/* NEW, not in v9b: the quantity affordance. v9b never shows one because its
   samples dedupe and its rugs are singular. Our /cart/ page has a real quantity
   stepper and can produce a sample line of 3, so the drawer has to be able to
   say so. Caps meta voice, same as the price beneath it. */
.mini-cart__qty {
  margin-top: 2px;
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* "Free sample" / "$4.95". Caps meta voice, the same one the page uses for state
   elsewhere. */
.mini-cart__price {
  margin-top: 2px;
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}
/* A rug's price is a figure the user is being asked to pay, not a state label
   like "Free sample" — it gets the body voice and the strong ink, and it sits
   under a spec block, so it needs the extra space to read as a conclusion
   rather than as one more row. */
.mini-cart__price--rug {
  margin-top: var(--space-2xs);
  font-size: var(--fs-body);
  letter-spacing: normal;
  text-transform: none;
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}
.mini-cart__remove {
  display: grid;
  place-items: center;
  width: var(--tap-min);
  height: var(--tap-min);
  margin-top: calc(var(--space-3xs) * -1);
  border-radius: var(--radius-circle);
  font-size: 18px;
  line-height: 1;
  color: var(--text-muted);
}
.mini-cart__remove:hover { background: var(--surface-recessed); color: var(--text-strong); }

/* The genuine empty state. Rendered by the server when both sections are empty,
   with no foot behind it rather than an offer to check out nothing. */
.mini-cart__empty {
  padding-block: var(--space-xl);
  color: var(--text-muted);
}


/* --- 6. Foot. --- */
.mini-cart__foot {
  flex: 0 0 auto;
  padding: var(--space-sm) var(--space-lg) var(--space-lg);
  border-top: var(--rule);
}
/* THE ONE PLACE THE SERVER CANNOT SIMPLY OMIT. The foot is a cart FRAGMENT
   ROOT, and cart-fragments.js applies a fragment with replaceWith — a root that
   is not in the document matches nothing, is never replaced back, and the foot
   would never reappear once the cart had been emptied. So it is always printed
   and always empty on an empty cart, and this takes it out of the flex column.
   (0,2,0) against the block above it, so the order of the two cannot matter.
   The renderer emits the empty foot with NO inner whitespace, because a single
   space is a text node and :empty would stop matching. */
.mini-cart__foot:empty { display: none; }

.mini-cart__total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-sm);
  color: var(--text-strong);
}
.mini-cart__total b { font-weight: var(--w-medium); font-variant-numeric: tabular-nums; }
/* The sample terms, composed by the server from the live tier ladder rather
   than quoted from the mockup's dev seed. This is the one piece of copy in the
   panel that is not a control label, and it is the reason the price column can
   say "Free sample" at all. Omitted when there are no samples to govern. */
.mini-cart__terms {
  margin-top: var(--space-3xs);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.mini-cart__checkout { width: 100%; margin-top: var(--space-sm); }
/* NEW, not in v9b: the quiet second action. The mockup has no cart page for it
   to lead to; we keep a full classic /cart/ alongside the drawer, and the foot
   is the only place the panel acknowledges it. Deliberately the existing
   link-underline treatment and not a second button — Checkout is the primary
   action and two buttons would say otherwise.
   `display: flex` with `width: fit-content` rather than `display: block`,
   because .link-underline draws its rule as an ::after at width:100% — a
   block-level anchor would stretch the underline across the whole panel. Flex
   is block-level, so the auto inline margins actually centre it, while
   fit-content keeps the rule the width of the words. Everything else (the 44px
   min-height, the caps voice, the hover ink) comes from .link-underline. */
.mini-cart__view-cart {
  display: flex;
  width: fit-content;
  margin: var(--space-sm) auto 0;
}

/* A phone gets the whole width; there is nothing useful to see behind a 420px
   drawer on a 390px screen. */
@media (max-width: 460px) {
  .mini-cart__head { padding-inline: var(--space-md); }
  .mini-cart__status,
  .mini-cart__body { padding-inline: var(--space-md); }
  .mini-cart__foot { padding-inline: var(--space-md); }
}

/* Reduced motion. pb-tokens.css already zeroes --dur-base, so the durations are
   effectively nil without this block; what this adds is the part the tokens
   cannot reach — the panel stops TRAVELLING. Both states are held at the
   identity transform, so the drawer appears where it belongs instead of crossing
   420px of screen in no time at all, which is the motion a reader asking for
   less of it is asking to be spared. Restated on .is-open too: that selector is
   one step more specific than the base, so a single .mini-cart__panel rule here
   would lose to it. */
@media (prefers-reduced-motion: reduce) {
  .mini-cart__scrim,
  .mini-cart__panel { transition: none; }
  .mini-cart__panel,
  .mini-cart__panel.is-open { transform: none; }
}
