/* =========================================================================
   PXP Peptides — shop.css
   The catalogue grid, the product card, the product page, the basket drawer
   and the demo checkout.

   Loads after site.css. Buttons, pills, forms and notes all come from there —
   nothing in this file redefines a control, it only arranges them.

   index.html loads this file too: the home page renders four product cards
   from the catalogue, and a `.p-card` with no shop.css behind it is a naked
   list of names.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Search

   The field itself is site.css's .field, unchanged; this only stops it
   running the full width of the grid on a desktop, where a 1200px search box
   for a range of a dozen compounds reads as a form rather than a filter.
   Below that width it is as wide as the chips under it. The bottom margin is
   the chips' gap, so the box and the row read as one control.
   ------------------------------------------------------------------------- */

.shop-search {
  max-width: 28rem;
  margin-bottom: var(--sp-4);
}

/* -------------------------------------------------------------------------
   Filters

   Chips that wrap rather than a strip that scrolls sideways. A horizontal
   scroller hides half the research areas off the right edge of a phone with
   nothing to say they are there, and the whole point of the filter is that
   you can see what you may filter by.
   ------------------------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-6);
}

.filter {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 16px;
  border: 1px solid var(--chrome-300);
  border-radius: var(--r-pill);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(15px, var(--t-small));
  transition: background-color .16s var(--ease), border-color .16s var(--ease),
              color .16s var(--ease);
}

.filter:hover {
  background: var(--blue-100);
  border-color: var(--blue-400);
}

/* The class and the attribute together. `aria-pressed` is what the shop page
   actually sets and what a screen reader reads; the class is in the spec's
   component table. They must never be able to disagree about which chip is
   on, so they share one rule. */
/* The on state is the accent, filled, with near-black on it. A selected chip
   is the second place on the site the accent earns a solid fill — the first
   is .btn--primary on hover — because "which of these is on" is exactly the
   question an accent should answer at a glance.

   --on-accent rather than --paper for the label. The accent is the same
   colour in both themes, so a label that flips with the theme would be white
   on the accent at 2.2:1 in daylight; near-black is 8.5:1 in both. */
.filter--on,
.filter[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.filter--on:hover,
.filter[aria-pressed="true"]:hover {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
  color: var(--paper);
}

/* -------------------------------------------------------------------------
   The catalogue grid
   ------------------------------------------------------------------------- */

/* TWO UP ON A PHONE, and a smaller card to go in the column.

   It was one card per row below 560, at 335px wide and 570px tall, which
   makes nineteen products about 10,800px of scrolling — a catalogue you
   arrive at the bottom of rather than one you browse. Two up at 375 gives a
   161px column, and everything in the card is cut to suit it below. Nineteen
   cards come to roughly a quarter of the height.

   The gap comes down with the card. --sp-5 between two 161px columns is 7% of
   the screen spent on the space between them. */
.shop-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3);
}

/* THE HIGHLIGHTS STRIP IS A CAROUSEL, NOT A GRID — at every width.

   It used to share the shop's grid, and the grid's auto-fit had a failure
   mode this section is uniquely exposed to: the shop page always has the
   whole range to lay out, but this strip shows only what the shop
   highlighted, and a business that has highlighted ONE product got that one
   card stretched across the entire row — a 1200px vial. A carousel fixes
   both ends of the count at once: every card is the same small width
   whether there is one or twenty, and past the viewport's worth they
   scroll horizontally rather than wrapping into rows. The "View the whole
   range" button stays below the strip in the markup, untouched by any of
   this.

   `grid-template-columns: none` matters: .cards in site.css declares `1fr`,
   and with auto-flow column that explicit first track would still catch the
   first card and stretch it — the exact bug this rule exists to end.

   `proximity` rather than `mandatory` snapping: mandatory fights trackpad
   momentum and makes half-step browsing impossible; proximity tidies a
   near-miss and otherwise leaves the hand in charge. */
#home-featured {
  display: grid;
  grid-template-columns: none;
  grid-auto-flow: column;
  /* One card is always this wide and no wider. The floor keeps a phone at
     roughly a card and three-quarters — the cut edge IS the "there's more"
     affordance — and the ceiling matches what auto-fit gives a shop card
     on a laptop, so the two pages' cards read as the same object. */
  grid-auto-columns: clamp(200px, 44vw, 300px);
  gap: var(--sp-3);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
  /* Air under the cards so a visible scrollbar sits off their shadow. */
  padding-bottom: var(--sp-2);
}

#home-featured > .p-card { scroll-snap-align: start; }

/* #home-featured by id, and not `.cards` — which is the class it actually
   carries. `.cards` is also the grid behind "What is in the box" and the
   ordering steps, whose cards are an icon and a sentence and want the full
   width of a phone. This selector is about the product card, which is what
   this file is for; it is only in site.css's `.cards` at all because the home
   page reuses that container. */

/* Everything the card is made of is at its SMALL size below, and the 560
   query puts the full-size values back — where the same two columns are 248px
   wide and the whole card fits again. Written that way round, rather than as
   a block of overrides here, because two rules for the same declaration in
   one file is how one of them ends up losing on source order without anybody
   noticing. That is exactly what happened on the first attempt.

   THE DESCRIPTION IS THE EXCEPTION and is hidden rather than shrunk. It is
   the one part of the card that cannot be made smaller without being made
   useless — a sentence in a 137px measure runs to six or seven lines and is
   taller than the photograph above it. What is left still answers the three
   questions a listing has to: what it is, what it was measured at, and
   whether it is in stock. The sentence is on the product page, one tap away.

   `display: none` and not `visibility: hidden`, so it is not read out either.
   Somebody on a phone with a screen reader gets the same card as everybody
   else, not a longer one. */
.p-card__more { display: none; }

/* -------------------------------------------------------------------------
   The product card
   ------------------------------------------------------------------------- */

/* Same glass recipe as .card in site.css — see the comment there for why 9px
   of blur and an inner glow rather than a heavy blur and a masked rim. The
   product card keeps its own radius: --r-md matches the media image's top
   corners, and 20px here would leave the photograph square inside a rounder
   frame. */
.p-card {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow:
    /* The hover rim. Declared in the resting state so it transitions, and
       transparent until --rim is set; see the hover block in site.css. */
    inset 0 0 0 1px var(--rim, transparent);

  /* Stays. It is what clips the media image's square top corners to the
     card's radius. It also used to be what stopped the full-height ::after
     lit edge squaring off those corners, and that edge has gone with the
     glass, but the image reason on its own is enough. */
  overflow: hidden;
}

.p-card__media,
.p-card__body,
.p-card__foot { position: relative; z-index: 2; }

/* The "New" / "Popular" flag, on the picture rather than under the name.

   .p-card__media is already `position: relative` above, so this only needs the
   offsets. z-index 1 puts it over the photograph and under nothing else —
   .p-card__body is z-index 2 and the flag never reaches it.

   The pill keeps its own fill and rim from .pill--blue, which is what lets it
   sit on a photograph at all: the contrast that matters is its own ink against
   its own ground, not against whatever is behind it. The rim is what stops a
   pale chip dissolving into a pale vial. */
.p-card__tag {
  position: absolute;
  top: var(--sp-2);
  left: var(--sp-2);
  z-index: 1;
}

/* WHAT THE THING IS, opposite the flag that says what the shop is doing with
   it. The same chip in the same slot on the other side, so a card carrying
   both keeps them apart with no flow to negotiate — two absolutely positioned
   pills sharing a corner would sit on top of one another, and a row of them
   would push the picture about.

   `--chrome` rather than `--blue`: the blue is spent on the research area,
   which is the pill this one most needs not to be mistaken for. Left aligned
   text and `text-align: right` are not needed — the pill is shrink-wrapped.

   `max-width` because "Collection" is one word here and the word a future
   shop uses might not be: a chip allowed to grow across the picture would
   meet the flag in the middle. */
.p-card__kind {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  z-index: 1;
  max-width: calc(100% - var(--sp-4) * 2);
}

.p-card:hover {
  box-shadow: inset 0 0 0 1px var(--rim, transparent);
  transform: translateY(-2px);
}

/* The card lights up when anything inside it takes focus, so a keyboard user
   can see which card they are standing in and not just which control. */
/* The rim brightens rather than a border colour changing — there is no
   border left to change. */
.p-card:focus-within {
  box-shadow: inset 0 0 0 1px var(--rim, transparent);
}

/* The focus ring is the card's own border, not the top-edge highlight.

   This used to repaint ::before, which was a rim running the whole perimeter.
   Under the new recipe ::before is a 1px line along the TOP only, so
   recolouring it marked the card with a single navy line at its head — easy
   to miss, and nothing like a focus indicator. The border goes navy instead,
   which is a ring all the way round. */
.p-card:focus-within {
  border-color: var(--blue-400);
}

.p-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  flex: 1 1 auto;
  padding: var(--sp-3);
  min-width: 0;
}

/* Two lines' worth of space whether the name uses them or not. "BPC-157 —
   5 mg" is one line and "Bacteriostatic water — 10 ml" is two, so without
   this every row below the name — the area pill, the description, the purity
   line, the stock dot — sits at a different height in each card of a row.
   The buttons already line up (margin-top: auto on the foot); this is what
   lines up everything between. 2.5em is two lines at the 1.25 above. */
.p-card__name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: max(15px, var(--t-small));
  line-height: 1.25;
  min-height: 2.6em;
  color: var(--ink);
}

/* The padding and the matching negative margin cancel out, so the name sits
   exactly where it did and its hit area is 44px tall instead of the 22px a
   single line of --t-h3 comes to. This is the link that opens the product, and
   on a phone it is a thumb aiming at one line of text. Nothing is added to the
   card's height; only the target grows. */
.p-card__name a {
  display: inline-block;
  padding-block: 11px;
  margin-block: -11px;
  color: inherit;
  text-decoration: none;
}

/* THE WHOLE CARD OPENS THE PRODUCT, since 14 September 2026.

   The name was the only link, and a buyer who tapped the picture, the size
   line or the price got nothing. Two things make the rest of the card open
   the page:

   1. `.p-card__media` is an <a> now (product-card.js), so the picture is a
      link in its own right — it sits beside the body, not in it, so nothing
      here can reach it.
   2. This pseudo-element: the name link's ::after is stretched over the
      body, which is the nearest positioned ancestor. One link, one tab stop,
      one accessible name, and a hit area the size of the text under the
      picture.

   Nothing has to be raised above it any more. The size chips that lived in
   the body, and the basket button in the foot, both came off the card on
   14 September 2026 — a card is a door to the product page and that is all
   it is — so there is no control left for this link to cover. */
.p-card__name a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* An <a> where a <div> was: block, so the box is unchanged, and no link
   colour bleeding into the pills it holds. */
a.p-card__media {
  display: block;
  color: inherit;
  text-decoration: none;
}

/* Hovering anywhere the card opens from underlines the name, so the whole
   surface reads as one link rather than as a picture and a title that
   happen to go the same place. */
.p-card:has(a.p-card__media:hover) .p-card__name a,
.p-card__body:hover .p-card__name a {
  color: var(--blue-700);
  text-decoration: underline;
}

.p-card__name a:hover {
  color: var(--blue-700);
  text-decoration: underline;
}

.p-card__meta {
  color: var(--ink-soft);
  font-size: 13px;
  overflow-wrap: anywhere;
}

/* The description absorbs the difference in copy length.

   `.p-card__body` is a flex column, so growing this one item pushes the
   purity line and the stock dot down to sit just above the foot — which
   means they land on the same line in every card of a row, however many
   lines the description above them runs to. The name and the tag are already
   aligned from the top; this is the other half, working up from the bottom.

   Only this element grows. Give two of them `flex: 1` and the slack is split
   between them, which aligns nothing.

   IT IS STILL NEEDED WITH THE DESCRIPTION FOLDED AWAY. A collapsed disclosure
   is one line in every card, but the NAME above it is not — "Bacteriostatic
   water 10 ml" wraps to two where "RETA 20 mg pen" does not — so without a
   slack absorber the price row still lands at a different height in each card
   of a row. */
.p-card__more { flex: 1 1 auto; }

/* The folded description.

   The marker is left alone. Browsers draw a disclosure triangle and it is the
   thing a reader already knows how to recognise; replacing it with a chevron
   of our own would cost a rule, a rotation and a pseudo-element to arrive at
   the same meaning.

   `cursor: pointer` because a summary does not get one by default and the
   word alone does not look clickable. The focus ring is the site's, inherited
   from :focus-visible — a summary IS focusable, and a disclosure nobody can
   see the focus on is a keyboard trap in all but name. */
.p-card__more > summary {
  cursor: pointer;
  color: var(--ink-soft);
  font-size: max(15px, var(--t-small));
  padding: var(--sp-1) 0;
}

.p-card__more > summary:hover { color: var(--ink); }

/* The sentence itself, once open. margin-top rather than a gap on the
   details, so the closed state stays exactly one line tall. */
.p-card__more > p { margin-top: var(--sp-2); }

/* The research-area tag, inside a card only.

   `.pill` is `white-space: nowrap` in site.css and that is right nearly
   everywhere — a tag broken across two lines stops reading as a tag. A card
   body on a 320px phone is 232px wide, and the longest tag in the range,
   "Standards & consumables", needs more than that. `.p-card` clips its
   overflow, so left as it is the tag is not merely tight, it is cut off
   mid-word on the narrowest screen the site supports. Inside a card it may
   wrap; the pill keeps its shape and nothing else on the site changes.

   The tag was "Reference standards & consumables" until it was shortened,
   which is why it now fits on one line in a four-across desktop row. That
   matters for more than the tag: a wrapped pill pushed the description, the
   purity line and the stock dot in that one card a line lower than the other
   three, and no amount of aligning the name above it fixed that. The footer
   already called the area "Standards & consumables"; the data now agrees. */
.p-card .pill { white-space: normal; }

.p-card__price {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* WHAT EVERYBODY ELSE PAYS, above what this customer is being asked for.

   Drawn only for a customer PXP has given an agreed price or a discount —
   Catalogue.listPrice answers null for everybody else, so this rule usually
   matches nothing at all.

   The line through it is decoration and nothing rests on it: the word "List"
   is in the text, because a struck number is silent to a screen reader and
   "£78 £70" announced with no subject is two prices and no answer about which
   one you pay.

   Its own line on the card rather than beside the price, at a size that
   cannot be mistaken for the number that matters. `display: block` because
   the foot is a flex row and an inline span would ride alongside the price
   and read as part of it. */
.p-card__was {
  display: block;
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: var(--t-small);
  font-weight: 400;
  text-decoration: line-through;
}

/* "6 SIZES", where the row of them used to be.

   A card carried every vial of the compound as a chip, and printed the chosen
   one under the name as well. Both are gone — the card is one compound and the
   choosing is on the product page — and this one line is what says there is a
   choice to make there. --ink-soft for the reason the rest of this stylesheet
   gives: a token somebody checked the contrast of, not a percentage laid over
   the ink. */
.p-card__count {
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* "From £42" — the word, at the size of the rest of the line but quieter, so
   the number is still the thing the eye lands on. Only drawn when the sizes
   cost different amounts; product-card.js decides. */
.p-card__from {
  color: var(--ink-soft);
  font-size: var(--t-small);
}

/* THE SIZES A COMPOUND COMES IN — on the product page, and only there.

   `.p-card__sizes` and `.p-card__size-btn` are named for the card because
   that is where they were born; the card stopped drawing them on 14
   September 2026 and js/product.js's picker under the product's h1 is the
   one place they are used now. Buttons and not a <select>, for the reason
   argued there: the range is the thing worth seeing, and six words across
   two lines show it at a glance where a closed dropdown shows one. Wraps,
   because six sizes do not fit across a narrow column and the alternative
   to wrapping is a horizontal scroller nobody discovers. */
.p-card__sizes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1);
  margin: var(--sp-2) 0;
}

/* Small, quiet, and unmistakably pressable. NOT `.btn` and not `.filter`,
   though it is plainly the filter chip's little brother: that control carries
   a 44px minimum height and 8px/16px padding built for one row of them across
   the top of a page, and six of those under a product title would be most of
   the column. The colours are the filter's, deliberately — two controls that
   mean "one of these is on" should not answer to two palettes. */
.p-card__size-btn {
  padding: 0.25em 0.6em;
  border: 1px solid var(--chrome-300);
  border-radius: var(--r-pill);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  cursor: pointer;
  transition: background-color .16s var(--ease), border-color .16s var(--ease),
              color .16s var(--ease);
}

.p-card__size-btn:hover {
  background: var(--blue-100);
  border-color: var(--blue-400);
}

/* THE CHOSEN ONE, and it is told by more than colour. The fill changes, and
   so does the weight, because "which of these six is on" carried by hue alone
   is the one state a colour-blind reader cannot recover — and unlike the
   filter row, there is no live region under this saying which was pressed.
   `aria-pressed` is what a screen reader hears; the weight is the same fact
   for everybody else.

   --on-accent and not --paper, for the reason the filter gives above: the
   accent is the same colour in both themes, so a label that flipped with the
   theme would be white on the accent at 2.2:1 in daylight. */
.p-card__size-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
}

/* Pushed to the bottom so that cards in a row line their prices up however
   long the names above them run. The direct-child padding covers the case
   where the foot is a sibling of the body rather than inside it — both are
   reasonable markup and neither should come out with a double gutter.

   Just the price in it now. The basket button that sat beside it, and the
   two rules that sized it, went with the button on 14 September 2026. */
.p-card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: auto;
  padding-top: var(--sp-2);
}

.p-card > .p-card__foot {
  padding: 0 var(--sp-3) var(--sp-3);
}

/* -------------------------------------------------------------------------
   The placeholder media

   There are no photographs of any of this, and there should not be: a
   photograph of a vial is a picture of a thing being used, and this site
   does not show that. So the media is drawn — a lyophilised vial standing in
   front of a molecular sketch, in pure CSS, with the lattice tiling behind
   it.

   Four variants rotate by position so a fourteen-card grid does not read as
   fourteen identical tiles. The variables are declared on the shared rule
   first, which is what lets `.pdp__media` — the only one that is not the
   nth child of anything — draw the same motif.
   ------------------------------------------------------------------------- */

.p-card__media,
.pdp__media {
  /* MINIMAL PASS: this used to stack two radial washes and a tiling lattice
     under a drawn vial AND a seven-atom molecule ring, with four variants
     rotating by nth-child so no two cards matched. It was a lot of picture
     for a placeholder. The vial is kept — it is the product, and its physical
     form is a fact the site is allowed to state — and everything behind and
     beside it is gone. One flat tint, one object. */
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background-color: rgba(245, 245, 247, .72);
  /* The placeholder photograph. A background rather than an <img> for one
     reason that outweighs losing srcset: twelve cards share ONE cached file,
     where twelve <img> elements would be twelve elements to lay out and
     twelve alt attributes to keep empty. At 640px square it covers a 320px
     card on a 2x screen and costs 16KB once.

     THE PLACEHOLDER IS THE SAME SHAPE AS A REAL PHOTOGRAPH ON PURPOSE. It is
     a 1:1 crop of assets/packshots/placeholder-image-coming-soon-packshot.png,
     framed exactly like the shop's own packshots — vial centred, same size
     in the frame — so a card with a photo and a card without one show a
     vial at the same scale. It used to be portrait, 524x640, and in the
     square box `cover` scaled that to the width and showed the vial about a
     fifth larger than in the cards beside it. */
  background-image: url("../assets/vial-placeholder-640.jpg");
  background-repeat: no-repeat;
}

/* The card: SQUARE, filled, and cropped to do it.

   1:1 rather than the 4:3 the shared rule above sets, since 14 September
   2026. The shop's own photographs are shot square — a vial upright on a
   plain ground, as the DSIP one is — and a 4:3 box took the top and bottom
   off every one of them, which on a vial is the cap and the label. A square
   box shows the photograph the shop took. The product page was already 1:1,
   so a card and its page now agree about the shape of the picture.

   The placeholder is square too (see the shared rule above), so in a square
   box `cover` is a straight fit and nothing is trimmed. The vertical bias
   `center 38%` is kept for the narrow card where the box can end up wider
   than tall: there `cover` trims the height instead, and the bias keeps the
   cap — the part that identifies the object — and loses the base and its
   shadow, so the vial reads as running off the bottom rather than as
   beheaded.

   This used to be `contain` on the argument that the vial is the subject and
   should not be cut. That produced two columns of flat tint either side of
   it, which is what a fill was asked for instead. */
.p-card__media {
  aspect-ratio: 1 / 1;
  border-radius: var(--r-md) var(--r-md) 0 0;
  background-size: cover;
  background-position: center 38%;
}

/* The product page fills the box too, and here that loses nothing.

   The placeholder is a square studio packshot with the vial in the middle
   60% of the frame, so covering a 1:1 box is a straight fit and keeps the
   whole vial, cap to base. `contain` used to sit here on the argument that the subject must not be
   cut; it also left the photograph's own background showing as two gutters
   against the box tint, which read as a picture stuck inside a picture. */
.pdp__media {
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background-size: cover;
  background-position: center;
}



/* A real photograph, if the shop ever carries one, sits on top of the drawn
   motif rather than replacing it — a positioned image with a z-index is the
   whole switch, and the pseudo-elements underneath cost nothing. */
.p-card__img,
.pdp__media img {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* -------------------------------------------------------------------------
   Stock state

   --good and --warn are both under 4.5:1 on white as given. They are correct
   as brand colours and wrong as text colours, so the text is mixed a fifth
   of the way towards --navy-900, which keeps the hue and clears AA. The dot
   is a graphic and takes whatever the text takes.
   ------------------------------------------------------------------------- */

.stock {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: 7px;
  font-size: max(15px, var(--t-small));
  font-weight: 400;
}

.stock::before {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: currentColor;
}

/* IN STOCK IS A CHIP, not a line. It is the one state a buyer scanning the
   grid is looking for, and the grid now puts it first, so it gets a filled
   pill that reads from across the room where the other states stay as quiet
   text. Low stock is still stock — it is the same chip in the warning hue, so
   "can I have it this week" is answered by the shape and "hurry" by the
   colour. The dot is kept: it is what makes the chip read as a status and
   not a tag, and it is the same mark the request and sold-out lines carry.

   The ground is the state colour at a tenth over the card, and the rim a
   third, so the chip holds its edge on a white card and on the dark theme
   alike — both come from the same --good / --warn tokens, which brand.css
   already lifts for dark. The text stays mixed towards --ink for AA. */
.stock--in,
.stock--low {
  padding: 2px 10px 2px 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--stock-rim);
  background: var(--stock-bg);
  font-weight: 500;
  white-space: nowrap;
}

.stock--in {
  --stock-bg: color-mix(in srgb, var(--good) 12%, transparent);
  --stock-rim: color-mix(in srgb, var(--good) 35%, transparent);
  color: color-mix(in srgb, var(--good) 80%, var(--ink));
}

.stock--low {
  --stock-bg: color-mix(in srgb, var(--warn) 12%, transparent);
  --stock-rim: color-mix(in srgb, var(--warn) 35%, transparent);
  color: color-mix(in srgb, var(--warn) 80%, var(--ink));
}
/* Genuinely unavailable, which on this site means bought in rather than made
   and not currently held. Muted rather than alarming: it is a fact about a
   delivery date, not a warning. */
.stock--gone { color: var(--label-secondary, #6b6b6b); }

/* Available on request. The accent rather than --bad, because it is not a
   warning: nothing is charged on this site, so a line PXP does not hold is a
   lead time to be confirmed and not a refusal. Red would say "you cannot have
   this" about a product you can, in fact, have. --accent-ink is the accent
   taken down to a colour that can be read at 15px. */
.stock--request { color: var(--accent-ink); }

/* -------------------------------------------------------------------------
   The product page
   ------------------------------------------------------------------------- */

.pdp {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-6);
  align-items: start;
}

.pdp__body {
  display: grid;
  gap: var(--sp-4);
  min-width: 0;
}

.pdp__body h1 { font-size: var(--t-h1); }

/* The buying block: price, stock, stepper and the button, boxed off from the
   chemistry underneath it so that the two are never read as one list. */
.pdp__buy {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.pdp__buy .p-card__price { font-size: 1.6rem; }

/* The vial, under the product page's h1 — the one place the strength is
   printed as a subtitle now that the card no longer does. Deliberately a
   size up from what the card used: this is the line that says which of the
   six Semaglutide pages you are on, and it sits under a 2rem heading. */
.pdp__size {
  margin: calc(var(--sp-2) * -1) 0 var(--sp-3);
  font-size: var(--t-body);
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.pdp__size:empty { display: none; }

/* The picker has room to breathe here — it is a control the buyer came to
   use, not a detail on a card being scanned. */
.pdp__sizes { margin: var(--sp-4) 0; }

/* -------------------------------------------------------------------------
   More than one photograph

   A product with one picture draws `.pdp__media` on its own, exactly as it
   always has, and none of this matches. A product with several is wrapped in
   `.pdp__gallery` and gets a strip under the big one.

   THE STRIP IS OUTSIDE `.pdp__media` ON PURPOSE, and the rule that forces it
   is fifty lines up: `.pdp__media img` is absolutely positioned and inset to
   0, so a photograph lies over the drawn vial rather than replacing it. A
   thumbnail put inside that box would be laid over the big picture instead of
   beneath it.
   ------------------------------------------------------------------------- */
.pdp__gallery {
  display: grid;
  gap: var(--sp-3);
  min-width: 0;
}

/* Wrapping rather than scrolling. A shop with four photographs gets one row;
   a shop with nine gets three, and neither hides anything behind a gesture a
   buyer has to discover. */
.pdp__thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* 64px is a deliberate floor: it is above the 44px tap target this site holds
   itself to, with room for a border that does not eat the picture. */
.pdp__thumb {
  position: relative;
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-sm, 8px);
  background: var(--paper-2);
  cursor: pointer;
}

.pdp__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The one on show. A ring rather than a colour wash over the picture: the
   thumbnail's job is to show which photograph it is, and tinting it would
   fight that. `aria-current` is the state itself rather than a class beside
   it — one fact, in the place a screen reader already reads. */
.pdp__thumb[aria-current="true"] {
  border-color: var(--blue-400, var(--ink));
  box-shadow: 0 0 0 2px var(--blue-400, var(--ink));
}

/* -------------------------------------------------------------------------
   What is in a collection

   Only ever drawn on a product that is one, so this rule matches nothing on
   most pages of most shops.
   ------------------------------------------------------------------------- */
.pdp__contents {
  margin: 0;
  padding-left: var(--sp-5);
  display: grid;
  gap: var(--sp-1);
  color: var(--ink);
}

.pdp__contents li { overflow-wrap: anywhere; }

/* The line on the card, which is the same list said in one sentence.

   Clamped to two lines: a hamper of nine things would otherwise be the
   tallest thing on a card 162px wide, and the product page is one tap away
   with the whole list on it. The fallback for a browser with no
   `line-clamp` is an ordinary paragraph, which is the honest degradation —
   it shows more, not less.

   DEVTOOLS WILL SAY `display: flow-root` HERE, AND THE RULE IS STILL WORKING.
   `.p-card__body` is a flex container, so this element is a flex item and the
   `-webkit-box` below is blockified before it is computed. The clamp survives
   that — measured, at 12 lines of content: clientHeight 42, scrollHeight 395 —
   which is why the declaration stays. Do not "fix" the computed value by
   deleting it. */
.p-card__contains {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* -------------------------------------------------------------------------
   The quantity stepper
   ------------------------------------------------------------------------- */

/* Same reason as `.pill`: the stepper sits in `.pdp__buy`, which is a grid,
   and a stepper stretched across the whole panel puts its two buttons an
   inch and a half apart from the number they change. */
.qty {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  border: 1px solid var(--chrome-300);
  border-radius: var(--r-pill);
  background: var(--paper);
  overflow: hidden;
}

.qty__btn {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex: none;
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.25rem;
  line-height: 1;
  transition: background-color .14s var(--ease);
}

.qty__btn:hover { background: var(--blue-100); }

.qty__btn[disabled] {
  color: var(--chrome-500);
  background: transparent;
  cursor: not-allowed;
}

.qty__n {
  min-width: 3ch;
  padding-inline: var(--sp-2);
  text-align: center;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------------------
   The basket

   A sheet up from the bottom of a phone, a drawer in from the right of
   anything wider. Both are the same panel; only the edge it is anchored to
   and the axis it travels on change.

   Note what is NOT here: nothing takes the page out of flow. The scroll lock
   is `body.is-locked` from brand.css, which is `overflow: hidden` and keeps
   the scroll offset — so closing the basket puts the reader back exactly
   where they were in the catalogue rather than at the top of it.
   ------------------------------------------------------------------------- */

.basket {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 90;
  display: flex;
  flex-direction: column;
  height: 90vh;
  height: 90dvh;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--paper);
  box-shadow: var(--shadow-2);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform .30s var(--ease), visibility 0s linear .30s;
}

/* `visibility` and not opacity alone: a transparent drawer still holds every
   one of its buttons in the tab order, and tabbing into a basket nobody can
   see is how a keyboard journey goes silently wrong. */
.basket--open {
  transform: none;
  visibility: visible;
  transition: transform .32s var(--ease), visibility 0s;
}

.basket__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex: none;
  padding: var(--sp-5);
  border-bottom: 1px solid var(--line);
}

.basket__head h2 { font-size: var(--t-h3); }

.basket__lines {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Without this, flicking the last line of the basket carries on and
     scrolls the catalogue underneath it. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 var(--sp-5);
}

.basket__line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--line);
}

.basket__line:last-child { border-bottom: 0; }

/* Whatever the name block is made of, it takes the width and the controls
   keep their own. */
.basket__line > :first-child {
  flex: 1 1 60%;
  min-width: 0;
}

.basket__line strong {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--ink);
}

.basket__line small,
.basket__line .p-card__meta {
  color: var(--ink-soft);
  font-size: max(15px, var(--t-small));
}

.basket__line .qty { flex: none; }

.basket__lines p {
  padding: var(--sp-6) 0;
  color: var(--ink-soft);
  text-align: center;
}

.basket__foot {
  flex: none;
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) max(var(--sp-5), env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--paper-2);
}

.basket__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-h3);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.basket__foot .form__note { margin-top: calc(var(--sp-1) * -1); }

/* The backdrop carries `.basket--open` as well as the drawer does — one class
   flipped on two elements, so the two can never disagree about whether the
   basket is open. The sibling selector covers a backdrop written after the
   drawer in the markup and toggled only there. */
.basket-backdrop {
  position: fixed;
  inset: 0;
  z-index: 89;
  background: rgba(7, 22, 52, .52);
  opacity: 0;
  visibility: hidden;
  transition: opacity .26s var(--ease), visibility 0s linear .26s;
}

.basket-backdrop.basket--open,
.basket--open ~ .basket-backdrop {
  opacity: 1;
  visibility: visible;
  transition: opacity .26s var(--ease), visibility 0s;
}

/* -------------------------------------------------------------------------
   The checkout flow

   Four panels shown one at a time, and a bar across the top saying which one
   you are on. The bar is a real <nav> with four buttons in it: a step you
   have finished can be gone back to by pressing its own label, and a step
   you have not reached is a disabled button rather than a decoration, so the
   control says what it can do without anybody having to try it.

   --ck-progress is written on .ck-flow by checkout.js as a number from 0 to
   1, and the rail is the only thing that reads it. Everything else about a
   dot — done, current, still to come — is a class on the <li>, because three
   states drawn from one number is how you end up with a rail that disagrees
   with the ticks beside it.
   ------------------------------------------------------------------------- */

.ck-flow {
  --ck-progress: 0;
  --ck-dot: 34px;
  --ck-steps: 4;

  display: grid;
  gap: var(--sp-4);
  margin-bottom: var(--sp-5);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--shadow-1);
}

/* Which step, in words, for the reader who wants the number rather than the
   picture. Not a live region: moving to a step moves focus to that panel's
   heading, which announces it, and a second announcement of the same news is
   how a live region becomes something people switch off. */
.ck-flow__count {
  margin: 0;
  color: var(--ink-soft);
  font-size: max(14px, var(--t-small));
  letter-spacing: .02em;
}

.ck-flow__bar { position: relative; }

/* From the centre of the first dot to the centre of the last, which is what
   makes it a connector rather than a gauge. Four equal columns put those two
   centres at 1/8 and 7/8 of the width, so the insets are 100% / (steps × 2). */
.ck-flow__rail {
  position: absolute;
  top: calc(var(--ck-dot) / 2 - 2px);
  left: calc(100% / (var(--ck-steps) * 2));
  right: calc(100% / (var(--ck-steps) * 2));
  height: 4px;
  border-radius: 4px;
  background: var(--line);
  overflow: hidden;
}

.ck-flow__fill {
  display: block;
  width: calc(var(--ck-progress) * 100%);
  height: 100%;
  border-radius: 4px;
  background: var(--accent-ink);
  transition: width .34s var(--ease);
}

.ck-flow__steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--ck-steps), minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Above the rail. The dots are opaque and the rail runs behind them, so
   without this the line would be drawn on top of every circle. */
.ck-flow__step { position: relative; }

.ck-flow__go {
  display: grid;
  justify-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-soft);
  cursor: pointer;
}

.ck-flow__go[disabled] {
  cursor: default;
  /* Dimmed, but not to the point of being unreadable: this is the map of
     where the reader is going and they have to be able to read the names of
     the places they have not been yet. */
  opacity: .75;
}

.ck-flow__dot {
  display: grid;
  place-items: center;
  width: var(--ck-dot);
  height: var(--ck-dot);
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink-soft);
  font-family: var(--font-display);
  font-size: .95rem;
  font-variant-numeric: tabular-nums;
  transition: background-color .2s var(--ease), border-color .2s var(--ease),
    color .2s var(--ease);
}

/* Both live in the same grid cell so the tick replaces the number without
   the dot changing size as it does. */
.ck-flow__n,
.ck-flow__tick { grid-area: 1 / 1; }

.ck-flow__tick {
  width: 17px;
  height: 17px;
  display: none;
}

.ck-flow__label {
  font-size: max(12.5px, .8rem);
  line-height: 1.25;
  text-align: center;
  /* Five columns of a 360px phone are about 60px each, and "Delivery" only
     just fits one. Leave to break inside the word rather than overflow into
     the next label; --ck-steps is 4 or 5, written by checkout.js. */
  overflow-wrap: anywhere;
}

/* ---- the three states ---- */

.ck-flow__step--done .ck-flow__dot {
  border-color: var(--accent-ink);
  background: var(--accent-ink);
  color: var(--paper);
}

.ck-flow__step--done .ck-flow__n { display: none; }
.ck-flow__step--done .ck-flow__tick { display: block; }

.ck-flow__step--done .ck-flow__label { color: var(--ink); }

.ck-flow__step--now .ck-flow__dot {
  border-color: var(--accent-ink);
  background: var(--paper);
  color: var(--accent-ink);
  /* The ring, so the step you are on reads as the step you are on at a
     glance rather than after comparing four circles. */
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 22%, transparent);
}

.ck-flow__step--now .ck-flow__label {
  color: var(--ink);
  font-weight: 500;
}

.ck-flow__go:hover:not([disabled]) .ck-flow__dot { border-color: var(--accent); }
.ck-flow__go:hover:not([disabled]) .ck-flow__label { color: var(--ink); }

/* Every step ticked, the rail full, and nothing current: the flow is over
   and the confirmation below it is what to read. */
.ck-flow--done .ck-flow__go[disabled] { opacity: 1; }

/* Announced, never seen. The visible label says "Basket"; this says whether
   it is done, current or still ahead, and on the review step's Edit buttons
   it says what is being edited. */
.ck-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------------------
   A step

   One panel of the flow. They are all children of the one <form>, hidden and
   shown by the `hidden` attribute — which brand.css makes `display: none
   !important`, so a step cannot be left visible by a display rule written
   here or anywhere else.
   ------------------------------------------------------------------------- */

.ck-step {
  display: grid;
  gap: var(--sp-5);
  min-width: 0;
}

/* It takes focus when the flow moves to it, and a dotted ring around a whole
   panel is not what that should look like. The heading and the progress bar
   above it are what say where the reader has landed. */
.ck-step:focus { outline: none; }

.ck-step__title { font-size: var(--t-h2); }

.ck-step__title,
.ck-step__lede { margin: 0; }

/* The gap between a heading and its own lede is the one place the panel's
   even spacing is wrong: they are one thought, not two. */
.ck-step__title + .ck-step__lede { margin-top: calc(var(--sp-5) * -1 + var(--sp-2)); }

.ck-step__lede { color: var(--ink-soft); }

/* Back on the left, forward on the right, and forward is the primary. On a
   narrow screen they stack with the primary on top, because the button you
   want is the one your thumb is already near. */
.ck-nav {
  display: flex;
  flex-wrap: wrap-reverse;
  gap: var(--sp-3);
  align-items: center;
  justify-content: space-between;
  padding-top: var(--sp-2);
}

/* On a phone they share the row and fill it. Anything wider and they go back
   to the width of their own labels — two buttons stretched across a
   1200px-wide form read as two bands, not as a choice. */
.ck-nav .btn { flex: 1 1 auto; }

@media (min-width: 560px) {
  .ck-nav .btn { flex: 0 0 auto; }
}

/* The editable basket on step one. The lines themselves are .basket__line,
   the same component as the drawer on the shop page and the read-only column
   beside the later steps — one look for one thing. */
.ck-basket {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.ck-basket .basket__line:last-child { border-bottom: 0; }

/* -------------------------------------------------------------------------
   The review

   What they typed, read back, with a way to change each part of it. A
   read-back nobody can act on is decoration.
   ------------------------------------------------------------------------- */

.ck-review {
  display: grid;
  gap: var(--sp-4);
}

.ck-review__block {
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.ck-review__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-3);
}

.ck-review__head h3 {
  margin: 0;
  font-size: var(--t-h3);
}

.ck-review__dl {
  display: grid;
  grid-template-columns: minmax(0, 8em) minmax(0, 1fr);
  gap: var(--sp-2) var(--sp-4);
  margin: 0;
}

.ck-review__dl dt {
  color: var(--ink-soft);
  font-size: max(14px, var(--t-small));
}

.ck-review__dl dd {
  margin: 0;
  color: var(--ink);
  /* An email address is one long word and will otherwise push the column
     out of the panel. */
  overflow-wrap: anywhere;
}

.ck-review__lines {
  margin: 0;
  color: var(--ink);
  line-height: 1.5;
}

/* The order-status panel's two lists.

   Both are ck-review__dl underneath, because they are the same furniture in
   the same card, and both want a column shape that panel does not: a review
   row is a short label against a long answer, and an order line is the other
   way round — a product name that runs on, against a price that never does.
   Hence the flip here rather than a second set of rules from nothing. */
.ck-status__lines,
.ck-status__total {
  grid-template-columns: minmax(0, 1fr) auto;
}

.ck-status__lines dd,
.ck-status__total dd {
  text-align: right;
  /* A price is a number and must not be broken across two lines to make room
     for a name that could have wrapped instead. Both lists share the rule so
     the total lands in the same column as the figures it adds up — a sum set
     in a different place from its addends is a sum you have to go looking
     for. */
  overflow-wrap: normal;
  white-space: nowrap;
}

/* Set apart from the lines it adds up. Without the rule the two lists run
   together and "Total" reads as one more thing that was bought. */
.ck-status__total {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}

/* -------------------------------------------------------------------------
   Checkout

   Stacked on a phone; form and summary side by side once there is room. No
   card fields exist anywhere in this file because none exist anywhere on the
   page — this checkout requests an order, it does not take a payment.
   ------------------------------------------------------------------------- */

.checkout {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-6);
  align-items: start;
  /* The step headings have their margins taken off so the panel can space
     itself evenly, which leaves nothing between the note above and the first
     one. This is that gap, put back where the grid can see it. */
  margin-top: var(--sp-6);
}

.checkout__form {
  display: grid;
  gap: var(--sp-5);
  min-width: 0;
}

/* Step one is the basket itself, so the read-only column beside it comes off:
   below 900px the summary stacks under the form, which would be the same list
   twice on a phone, and above it the two would sit side by side disagreeing
   about which one you are meant to edit. */
.checkout--solo .checkout__summary { display: none; }

/* And on the review, which reads the basket back itself as the first block,
   in step order. */
.checkout--review .checkout__summary { display: none; }

.ck-review__basket .basket__line { padding: var(--sp-3) 0; }

.ck-review__block .basket__total {
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}

.checkout__summary {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-2);
  min-width: 0;
}

.checkout__summary h2 { font-size: var(--t-h3); }

.checkout__summary .basket__line { padding: var(--sp-3) 0; }

.checkout__summary .basket__total {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

/* -------------------------------------------------------------------------
   The payment step

   The fifth panel, only when config.js says the checkout takes payment.
   The total is the database's — shop_quote, asked on the way in — drawn in
   the same .basket__total the earlier steps use, and the button underneath
   is the one thing on the page that leaves it.
   ------------------------------------------------------------------------- */

.ck-pay__total {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}

/* The confirmation. A plain card: it used to wear a green top edge, and
   the edge came off — a panel that starts "Pay by bank transfer" is not a
   celebration, and the amber and red below say more when they are the only
   colour a card can have. */
.order-done {
  max-width: 640px;
  margin-inline: auto;
  padding: var(--sp-7) var(--sp-6);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper);
  box-shadow: var(--shadow-1);
}

/* A payment that came back through the wrong door, an order still waiting
   on the bank, or one waiting on the customer's transfer: an amber edge on
   the same panel, because it is the same panel and not a different kind of
   news. AFTER .order-done, and it has to be: the base rule sets the whole
   border-top shorthand at the same specificity, and these used to sit above
   it, where the shorthand quietly won and every waiting order got the green
   edge of a done one. */
.order-done--waiting { border-top: 4px solid var(--warn); }
.order-done--failed { border-top: 4px solid var(--bad); }

.order-done > * + * { margin-top: var(--sp-4); }

.order-done h1,
.order-done h2 { font-size: var(--t-h2); }

.order-done p { color: var(--ink-soft); }

.order-done strong { color: var(--ink); }

/* The order reference, which people read out over the telephone. */
.order-done code {
  display: inline-block;
  padding: 4px 10px;
  border-radius: var(--r-sm);
  background: var(--blue-100);
  color: var(--ink);
  font-weight: 400;
  letter-spacing: .06em;
}

/* -------------------------------------------------------------------------
   The account page

   account.html: the two signed-out doors — sign in, or create an account —
   then the customer's own orders, invoices, quotes, history and documents,
   and the choose-a-new-password card the reset email lands on. Cards,
   notes, fields and buttons all come from site.css; what is here is only
   the shapes that page invents — an invoice line, an order's item list,
   the bank-details fold-out, the overdue flag.

   The line is the basket line's arrangement without being the basket line:
   `.basket__line` lives inside a fixed drawer and flexes its first child
   against a stepper, neither of which is true here, and borrowing it would
   couple two pages that only look alike today.
   ------------------------------------------------------------------------- */

.acct-loading { color: var(--ink-soft); }

/* ---- signed out: the two doors ----

   One column until the 900px block below puts them side by side. `start`
   because the cards are different heights and a stretched shorter one would
   draw a border around nothing. */
.acct-doors {
  display: grid;
  gap: var(--sp-5);
  align-items: start;
}

.acct-signin,
.acct-signup,
.acct-switch,
.acct-reset { max-width: 560px; }

/* .card__title reserves two lines so titles align across a row of cards.
   These cards' titles are their own headings, not a row to align. */
.acct-doors .card__title,
.acct-switch .card__title,
.acct-reset .card__title { min-height: 0; }

/* The two answers to "this link is for a different account". Wraps rather
   than squeezing, because the labels are sentences rather than words and a
   narrow phone should stack them instead of shrinking them. */
.acct-switch__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

.acct-signin .form,
.acct-signup .form,
.acct-reset .form { margin-top: var(--sp-5); }

/* ---- signup.html: the date of birth ----

   Three boxes in a row, sized for what goes in them — two figures, two
   figures, four — rather than stretched to the card. `.field` is a grid
   with a gap, and the fieldset borrows it so the legend, the hint, the row
   and the error stack the way every other field's parts do; the browser's
   own fieldset chrome is taken off because the card is the frame here. */
.su-dob {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.su-dob > legend {
  padding: 0;
  margin-bottom: var(--sp-2);
}

.su-dob__row {
  display: flex;
  gap: var(--sp-3);
}

.su-dob__part {
  display: grid;
  gap: var(--sp-2);
  width: 5.5rem;
}

.su-dob__part--year { width: 7.5rem; }

/* The three small labels under the one big one: smaller and softer, so
   "Day" reads as a part of the question above it and not as a question. */
.su-dob__label {
  font-size: max(14px, var(--t-small));
  color: var(--ink-soft);
}

/* "Already have an account?" sits under the form, apart from it. */
.su-have { margin-top: var(--sp-5); }

/* ---- signup.html: the whole page is one card wide ----

   The wrap is 560px plus its gutters, so .wrap's margin-inline:auto centres
   the heading, the form card and the done panel on a desktop. Without this
   they sat at the left of a 1200px column with nothing on the right. */
.su-wrap { max-width: calc(560px + var(--gutter) * 2); }

/* ---- signup.html: the "check your email" panel ----

   The checkout's .order-done panel with a tick on top. The tick is a ring
   and a mark, both strokes, both drawn in --good because this is the one
   place on the site where a green tick means exactly what it looks like.
   Centred, unlike the order panel: the form it replaces has gone, and the
   panel is the only thing on the page, so it reads as a notice and not as
   a form with a sentence at the foot. */
.su-done { max-width: 560px; margin-inline: auto; text-align: center; }

.su-done__tick {
  display: inline-block;
  width: 96px;
  height: 96px;
  color: var(--good);
}

.su-done__tick svg { width: 100%; height: 100%; overflow: visible; }

/* Both strokes start fully "undrawn" (dash = their own length, offset the
   same) and the animation runs the offset down to nothing: the ring draws
   round from the top, and the tick draws in once the ring has closed. The
   panel is hidden until js/signup.js shows it, and a hidden element does
   not animate, so the strokes start the moment it appears. */
.su-done__ring {
  stroke-dasharray: 183;
  stroke-dashoffset: 183;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
}

.su-done__mark {
  stroke-dasharray: 42;
  stroke-dashoffset: 42;
}

@media (prefers-reduced-motion: no-preference) {
  .su-done__ring { animation: su-draw 0.6s ease-out forwards; }
  .su-done__mark { animation: su-draw 0.35s ease-out 0.55s forwards; }
  .su-done__tick { animation: su-pop 0.4s ease-out 0.85s; }
}

@media (prefers-reduced-motion: reduce) {
  .su-done__ring,
  .su-done__mark { stroke-dashoffset: 0; }
}

@keyframes su-draw { to { stroke-dashoffset: 0; } }

@keyframes su-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}

/* The one instruction most people need: a soft amber box so it is the
   line the eye lands on after the tick. */
.su-done__junk {
  padding: var(--sp-4);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--warn) 14%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
}

/* The panel's own heading, under the tick rather than at the top. */
.su-done h2 { margin-top: var(--sp-5); }

.su-done:focus { outline: none; }
.su-done:focus-visible {
  outline: 2px solid var(--accent-ink);
  outline-offset: 4px;
}

/* The Set/Back pair on the reset card, spaced apart rather than touching. */
.acct-reset .btn + .btn { margin-left: var(--sp-3); }

/* "Forgotten your password?" — a native <details>, the site's disclosure,
   dressed like .acct-pay's summary so it reads as the same kind of thing. */
.acct-forgot { margin-top: var(--sp-4); }

.acct-forgot > summary {
  cursor: pointer;
  color: var(--blue-700);
  font-size: max(15px, var(--t-small));
  padding: var(--sp-1) 0;
}

.acct-forgot > summary:hover { color: var(--ink); }

.acct-forgot .form { margin-top: var(--sp-3); }

.acct-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-4);
}

.acct-who { color: var(--ink-soft); }

.acct-who strong {
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--ink);
}

/* ---- the two pages ----

   Orders and Your information, one address each (#orders, #information),
   so the browser's Back button moves between them and a link can name one.
   Two underlined labels rather than buttons: they are links to a hash, and
   a link is what the address bar says they are. The current one is marked
   aria-current="page" by js/account.js, and that attribute is the whole of
   what this styles — no class to fall out of step with it. */
.acct-tabs {
  display: flex;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
  border-bottom: 1px solid var(--line);
}

.acct-tabs a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-1);
  margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--ink-soft);
  font-family: var(--font-display);
  text-decoration: none;
}

.acct-tabs a:hover { color: var(--ink); }

.acct-tabs a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* Focused programmatically when a tab is pressed, so a screen reader is
   told what changed; it is not a control, so it draws no ring. */
.acct-page:focus { outline: none; }

.acct-block { margin-top: var(--sp-6); }

.acct-block h2 { font-size: var(--t-h3); }

.acct-block h2 + * { margin-top: var(--sp-2); }

/* ---- an invoice line ---- */

.acct-line {
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--line);
}

.acct-line:last-child { border-bottom: 0; }

.acct-line__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2) var(--sp-3);
}

/* The name block takes the width; the flag and the amount keep their own. */
.acct-line__head > :first-child {
  flex: 1 1 60%;
  min-width: 0;
}

.acct-line strong {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--ink);
}

.acct-line small {
  color: var(--ink-soft);
  font-size: max(15px, var(--t-small));
}

.acct-line__amount {
  font-family: var(--font-display);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* An order in the list: the whole line is a link to the order's own page.
   Block, so the link is the row and not the words in it; a chevron at the
   right edge, because a row that opens something should say so without a
   button. Hover lifts the number to the accent, the site's link colour. */
.acct-line--link {
  display: block;
  color: inherit;
  text-decoration: none;
}

.acct-line--link:hover strong { color: var(--blue-700); }

.acct-line__go {
  color: var(--ink-soft);
  font-size: 1.5rem;
  line-height: 1;
}

/* One order's page. */
.acct-order__back { margin-bottom: var(--sp-4); }

.acct-order h2 { font-size: var(--t-h3); }

.acct-order__meta {
  margin-top: var(--sp-1);
  color: var(--ink-soft);
}

.acct-order__title {
  margin-top: var(--sp-5);
  font-size: max(15px, var(--t-small));
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--ink-soft);
}

.acct-order__title + .acct-order__items { margin-top: var(--sp-2); }

.acct-order__total {
  display: flex;
  justify-content: space-between;
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}

.acct-order .acct-line__acts { margin-top: var(--sp-5); }

/* Quotes and the paid history: the same line, said more quietly. The reader
   is owed the facts, not a second column of things demanding attention. */
.acct-line--quiet strong { color: var(--ink-soft); }
.acct-line--quiet .acct-line__amount { color: var(--ink-soft); }

/* Overdue. An outlined pill rather than a filled one: it should be findable
   at a glance without shouting louder than the amount beside it. */
.acct-flag {
  display: inline-block;
  padding: 2px 10px;
  border: 1px solid currentColor;
  border-radius: var(--r-pill);
  color: var(--bad);
  font-size: max(13px, var(--t-small));
  white-space: nowrap;
}

/* ---- paying one ---- */

/* A native <details>, like the product card's description: the browser
   draws a marker the reader already recognises, and a summary is focusable,
   so the site's own focus ring applies without another rule. */
.acct-pay { margin-top: var(--sp-2); }

.acct-pay > summary {
  cursor: pointer;
  color: var(--blue-700);
  font-size: max(15px, var(--t-small));
  padding: var(--sp-1) 0;
}

.acct-pay > summary:hover { color: var(--ink); }

.acct-pay > .form__note { margin-top: var(--sp-2); }

.acct-pay > .btn { margin-top: var(--sp-3); }

/* The details somebody copies into their banking app, on their own panel so
   they read as a block to copy rather than as sentences to interpret. */
.acct-bank {
  display: grid;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.acct-bank > div {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--sp-1) var(--sp-3);
}

.acct-bank dt { color: var(--ink-soft); }

/* The value and its Copy travel together, pushed to the right edge. `flex: 1`
   is what keeps them there on a phone: the row wraps, the dd takes the whole
   second line, and the pair sits at its right end under the value column —
   rather than starting at the left margin, which put five Copy buttons at
   five different distances from the edge. */
.acct-bank dd {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-2);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The Copy beside each detail somebody would otherwise retype into a
   banking app. A quiet button — the number is the thing to look at, the
   button is a convenience over it — but 44px tall like every other target
   on the site, because it is pressed with a thumb on a phone while the
   banking app waits in the other hand. Says "Copied" for a moment when it
   has, in the site's good colour, and goes back to saying Copy. */
.bank-copy {
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink-soft);
  font: inherit;
  font-size: .9rem;
  line-height: 1;
  cursor: pointer;
}

.bank-copy:hover { color: var(--ink); border-color: var(--ink-soft); }

/* The reference, first in the block and marked out: the chip the order
   number has always worn on this page, so it reads as the same thing. */
.bank-block__ref dd > span {
  padding: 4px 10px;
  border-radius: var(--r-sm);
  background: var(--blue-100);
  font-family: var(--font-display);
  letter-spacing: .06em;
}

.bank-block__ref dt { color: var(--ink); font-weight: 600; }

/* On a phone the row is two lines: the label, then the value on the left
   with its Copy on the right. Right-aligning the value beside a Copy button
   on a 375px screen was squeezing "PXP TEXTILES LTD" into the middle, and a
   value that is about to be read into a banking app wants a steady left
   edge. */
@media (max-width: 599px) {
  .acct-bank > div {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-1);
  }

  .acct-bank dd {
    justify-content: space-between;
    text-align: left;
  }
}

.bank-copy--done {
  color: var(--good);
  border-color: var(--good);
}

/* On the checkout's confirmation the block sits in a panel whose own
   paragraphs are soft; the numbers in it are the point, so they stay ink. */
.order-done .acct-bank { margin-top: 0; }
.order-done .acct-bank dd { color: var(--ink); }
.order-done .acct-bank + .form__note { margin-top: var(--sp-2); }

.acct-claimed {
  margin-top: var(--sp-2);
  color: var(--good);
  font-size: max(15px, var(--t-small));
}

/* ---- an order ----

   The invoice line's arrangement with two extra rows: the items it held,
   and the Reorder button. The items keep real bullets — they are a list a
   person checks off against a delivery, not decoration. */

.acct-order__items {
  margin: var(--sp-2) 0 0;
  padding-left: 1.2em;
  color: var(--ink-soft);
  font-size: max(15px, var(--t-small));
}

.acct-order__items li + li { margin-top: 2px; }

/* The batch under the line it belongs to.

   ITS OWN LINE, and that is the whole rule. It is a different fact from
   "two of these" and reads as part of the product name when it trails one.

   NOT SMALLER, though it was for a draft. `.acct-line small` sets
   `font-size: max(15px, var(--t-small))` — a floor, put there so a small
   inside this card cannot fall below what a phone can read, and it also
   out-specifies a single class. Both point the same way: 12.9px here would
   have been fighting a rule that exists for a reason and losing on purpose
   is better than winning with `!important`. Same size, own line, and the
   indent does the work.

   Only rendered where the line HAS a batch — see orderLine() in
   js/account.js. Orders written before the batch columns existed carry
   null for ever, and so do things that have no batch. There is no empty
   state to style. */
.acct-order__batch {
  display: block;
}

.acct-line > .btn { margin-top: var(--sp-3); }

/* Two buttons under an order — Reorder, and the receipt. A row that wraps
   rather than a grid: on a narrow phone they stack, and neither is the one
   that must stay on the first line. `.acct-line > .btn` above does not
   reach them any more, so the gap above is this rule's. */
.acct-line__acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}

/* ---- what we hold about you ----

   The facts with no box to change them in — the email always, and the whole
   record when the details door is not installed. .acct-bank is the near
   relative and the wrong one to borrow: bank details are short values
   somebody copies, right-aligned and tabular, where an address is a block
   of lines to read. Label above value, and the value keeps its line breaks
   (js/account.js writes them as <br>). */
.acct-facts {
  display: grid;
  gap: var(--sp-3);
  margin-top: var(--sp-3);
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--paper-2);
}

.acct-facts dt {
  color: var(--ink-soft);
  font-size: max(15px, var(--t-small));
}

.acct-facts dd {
  margin: 0;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* Why the value above has no box beside it. Its own line, because it is a
   sentence about the field rather than part of the field. */
.acct-facts__why {
  display: block;
  margin-top: var(--sp-1);
  color: var(--ink-soft);
  font-size: max(14px, var(--t-small));
}

/* ---- closing the account ----

   Set apart from the blocks above it by a rule and a wider gap: it is the
   only control on this page that cannot be taken back, and it should not
   read as one more row of the account. The disclosure keeps it folded until
   somebody means it — dressed like .acct-pay's summary so it reads as the
   same kind of thing — and the button inside carries the danger colour that
   nothing else on the site uses. */
.acct-close {
  margin-top: var(--sp-7);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--line);
}

.acct-close__fold > summary {
  cursor: pointer;
  color: var(--blue-700);
  font-size: max(15px, var(--t-small));
  padding: var(--sp-1) 0;
}

.acct-close__fold > summary:hover { color: var(--ink); }

.acct-close__fold > .form__note { margin-top: var(--sp-2); }

.acct-close__fold .form { margin-top: var(--sp-4); max-width: 460px; }

/* ---- documents ---- */

.acct-doc {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
}

.acct-doc:last-child { border-bottom: 0; }

.acct-doc strong {
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--blue-700);
}

.acct-doc:hover strong { text-decoration: underline; }

.acct-doc small {
  color: var(--ink-soft);
  font-size: max(15px, var(--t-small));
}

/* -------------------------------------------------------------------------
   Wider screens
   ------------------------------------------------------------------------- */

/* ---- 560px: a third column, and the card stays the phone-sized one ----

   This block used to widen the card to go with the wider column: --sp-5 of
   padding, the name at --t-h3, the price at 1.35rem, the description unfolded.
   With two columns at 1300px that came to a 315px card 510px tall, and a
   screen of eleven products showed six of them. The card is now the compact
   one at every width, and the extra room goes on more columns instead: three
   here, four at 760 — and FOUR IS THE CEILING. There used to be five at 960
   and six at 1400, and a sixth of a 1300px wrap is a 200px card, which is
   where a compound sold in six vials wraps its size row onto three lines
   and the row of chips is taller than the picture. Four across on an iPad
   and a desktop gives each card the width to lay its sizes out on one or
   two, which is the thing a buyer is choosing between. */
@media (min-width: 560px) {
  .shop-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-4);
  }
}

/* ---- 760px: the drawer stops being a sheet, the product page splits ---- */
@media (min-width: 760px) {
  .basket {
    inset: 0 0 0 auto;
    width: min(430px, 92vw);
    height: 100%;
    border-radius: 0;
    transform: translateX(100%);
  }

  .basket--open { transform: none; }

  .pdp { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }

  .shop-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---- 900px: checkout splits.

   Not one of the four brand breakpoints, and deliberately so — the spec's
   page brief asks for two columns here, and the summary needs about 340px
   beside a form that stops being usable much under 520px. ---- */
@media (min-width: 900px) {
  .checkout { grid-template-columns: minmax(0, 1fr) 340px; }

  /* No summary on step one, so no column reserved for one. */
  .checkout--solo,
  .checkout--review { grid-template-columns: minmax(0, 1fr); }

  .checkout__summary {
    position: sticky;
    top: 96px;
    max-height: calc(100vh - 128px);
    overflow-y: auto;
  }

  /* The two signed-out doors, side by side. Same width each: neither card
     is the main one — which door is theirs is the visitor's fact, not the
     page's guess. */
  .acct-doors { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- 960px: nothing. There used to be a fifth column here and a sixth at
   1400; four is the ceiling, see the 560 block above. ---- */

/* -------------------------------------------------------------------------
   Print

   A printed catalogue page is a reference list. The basket, the filters and
   every control that does something come off; the names, the specifications
   and the prices stay.
   ------------------------------------------------------------------------- */

@media print {
  .basket,
  .basket-backdrop,
  .filters,
  .shop-search,
  .ck-flow,
  .ck-nav,
  .qty { display: none !important; }

  /* `:not([hidden])` on all three, and on .checkout it is load-bearing.
     `display: block !important` outranks the `[hidden]` attribute, so this
     rule used to put the checkout form back on the page after the code had
     hidden it — and the moment it is hidden is the moment the order is
     placed, when the screen is showing the confirmation. A customer printing
     or saving that confirmation got their name, email, telephone number and
     full delivery address on the sheet as well, from a form they could not
     see. The other two are never given the attribute, so the guard costs
     them nothing and stops the same trick being possible later. */
  .shop-grid:not([hidden]),
  .pdp:not([hidden]),
  .checkout:not([hidden]) {
    display: block !important;
  }

  .p-card {
    border: 1pt solid #999;
    box-shadow: none !important;
    transform: none !important;
    break-inside: avoid;
    margin-bottom: 10pt;
  }

  /* The drawn motif is a lot of ink for a picture that carries no
     information. The card keeps its name, its specification and its price. */
  .p-card__media,
  .pdp__media { display: none !important; }

  /* The unit joins them: on paper --ink-soft is a grey that costs ink and
     buys nothing, and "/ 5mg" is the half of the price line that says which
     vial the printout is about. */
  .p-card__price,
  .p-card__unit,
  .p-card__name,
  .stock { color: #000 !important; }

  /* The chip's tint is lost on paper anyway; a rim in the ink keeps "In
     stock" a chip on the printout rather than a word floating in a gap. */
  .stock--in,
  .stock--low {
    background: none !important;
    border-color: #000 !important;
  }

  .checkout__summary,
  .order-done,
  .pdp__buy {
    position: static !important;
    background: #fff !important;
    border: 1pt solid #999;
    box-shadow: none !important;
    break-inside: avoid;
  }
}
