/**
 * section-products.css — the front page product rail and the product card.
 *
 * Partner of template-parts/home/products.php (the section) and
 * template-parts/product/card.php (the card). Registered as
 * 'bedi-section-products' in src/Assets.php with bedi-base as its ONLY
 * dependency, and enqueued on is_front_page() only. It deliberately does not
 * depend on bedi-motion or bedi-woo: it consumes none of motion.css's glass,
 * glow, reveal or press primitives, and it must not drag 940 lines of shop
 * chrome onto the homepage for the sake of one add-to-cart button. Every rule
 * below stands alone.
 *
 * THIS IS A DESIGN, NOT A REPRODUCTION — AND THAT IS THE ONE WAY IT DIFFERS
 * FROM EVERY OTHER SECTION FILE IN THIS THEME. section-category-cards.css,
 * hero.css and header.css are pixel reproductions: their numbers were read out
 * of the original site's Elementor data and are quotations that may not be
 * retuned. There is no Elementor export behind this section. It replaces the
 * category tile row under the hero with something the old site never had, so
 * NOTHING here is extracted and nothing here is a quotation.
 *
 * That has a direct consequence for how the file is written, and it is the
 * reason this header is not a copy of the cards file's:
 *
 *   1. LENGTHS COME FROM THE TOKEN SCALE, ALWAYS. The cards file bypasses
 *      base.css's 4px scale because its paddings are extracted 28px and 40px
 *      values and rounding them would be exactly the "improvement" that pass
 *      exists to undo. Here there is no value to preserve, so there is no
 *      excuse for a bare number: every length below is a --bedi-space-*,
 *      --bedi-text-*, --bedi-radius-* or a multiple of one. The four
 *      exceptions are marked JUDGEMENT and each says what it was chosen from.
 *
 *   2. COLOURS ARE TOKENS, SO <html data-theme="dark"> REACHES THIS SECTION.
 *      This is the opposite call from the cards file and the same argument.
 *      That file pins its inks to literals because its BACKGROUNDS are
 *      extracted literals that cannot flip, and ink on a fixed background has
 *      to be as fixed as the background is. This section paints almost nothing:
 *      the card has no box, so its text sits directly on --bedi-bg and follows
 *      the canvas wherever the canvas goes. Reading those inks through var() is
 *      therefore correct, not lax, and the dark escape hatch works here for
 *      free.
 *
 *      THE EXCEPTION PROVES THE RULE. There is exactly one fixed surface in
 *      this file — the media box, held at a light neutral in BOTH schemes (see
 *      section 7) — and every ink that sits on it is pinned to a literal for
 *      precisely the reason the cards file gives. Measured, because someone
 *      will ask: the dark palette's --bedi-accent #4FB3E8 on that fixed panel
 *      is 2.17:1 and --bedi-ink-muted #9AA8BC is 2.29:1, both under the 3:1 a
 *      focus ring or an icon needs. So the heart, its ring and its pressed
 *      state are literals; the section title, the product name, the prices and
 *      the nav arrows are tokens. The dividing line is "does this ink sit on
 *      the one surface that does not flip", and nothing else.
 *
 *   3. NO var() FALLBACKS ON COLOURS. Same rule the cards file states and for
 *      the same reason: a fallback fires only when the token is UNDEFINED,
 *      never when it is defined to something else, so var(--bedi-ink, #252531)
 *      would silently keep painting #252531 while the rest of the page went
 *      dark. A colour here is either a plain var() — because it SHOULD follow
 *      the scheme — or a literal, because it must not. Never both. Non-colour
 *      tokens keep the usual var(--token, fallback) form; a missing length
 *      degrades to a slightly wrong layout, a missing colour degrades to
 *      invisible text.
 *
 * THE CARD'S READING ORDER IS PRICE, META, NAME — AND THE CSS IS ORDERED THAT
 * WAY TOO. Sections 7 to 12 below run in the same sequence as the markup: media,
 * prices, meta line, name, wish control, add-to-cart. That is not how the first
 * version of this file was written, and the two have to be re-synchronised in
 * the same edit whenever one of them moves, or the next reader loses the only
 * cheap way of checking the template against the stylesheet.
 *
 * WHY THE PRICE IS ON TOP. This rail's heading is a claim about price and its
 * ordering is a claim about price, so the number is the field being scanned and
 * the name is what gets read once a number has stopped somebody. It is also the
 * order of the client's reference, read off zoommer.ge's live cards: price, a
 * short meta line, the product name, the button. The template part's header
 * carries the full argument; it is repeated here in one sentence because a
 * stylesheet whose sections are in an order nobody explains gets reordered.
 *
 * THE ROW IS DENSE ON PURPOSE, AND DENSITY IS MOST OF WHAT "FINISHED" MEANS
 * HERE. The reference shows six or seven products at 1440px against this rail's
 * original five, and a 224px card carrying four elements with generous gaps is
 * what made ours read as sparse beside it — not the missing wrapper, which the
 * reference does not have either. So the card is narrower (section 1,
 * --prail-item-min), the vertical steps are tighter and deliberately unequal
 * (section 1, the rhythm note), and the type now has a real hierarchy: an 18px
 * price, then a 12px meta line, then a 13px name — where the two sizes the card
 * used to have sat one step of the scale apart and neither led.
 *
 * WHAT THIS FILE MUST NEVER DO
 *
 *   - NEVER GIVE THE CARD A BOX. No border, no box-shadow, no panel
 *     background, no radius on .bedi-pcard itself. The client asked for cards
 *     "without wrapper": they sit on the page and are separated from their
 *     neighbours by space alone. The only two surfaces that get a background
 *     are the media box and the add-to-cart button. Adding a hairline "just to
 *     tidy the row" is the one change that undoes the brief.
 *
 *   - NEVER FABRICATE A DISCOUNT. .bedi-pcard__badge and .bedi-pcard__price-was
 *     are skinned here and PRINTED NOWHERE. There is no `content:` string in
 *     this file, so no configuration of this stylesheet can produce a "-15%"
 *     pill or a struck-through price on a product WooCommerce does not report
 *     as on sale. The template part is the only thing that can emit them, and
 *     it does so only behind $product->is_on_sale() with a real regular price
 *     above the sale price. A crossed-out price that was never charged is
 *     deceptive and in many jurisdictions illegal; the CSS is written so it
 *     cannot become an accomplice. Today this matters in practice rather than
 *     in theory: the live catalogue has 1259 products and zero on sale, so
 *     these two rules currently paint nothing at all.
 *
 * WHAT BREAKS IF SOMEONE "TIDIES" THIS FILE
 *
 *   - Delete the min-height on .bedi-pcard__name (section 10) and every price
 *     and every button in the row stops sharing a baseline. It is the single
 *     most removable-looking declaration in the file and it is the point of the
 *     whole design. The comment there says so at length.
 *   - Delete the min-height on .bedi-pcard__meta (section 9) and the same thing
 *     happens one line further down, only intermittently — it breaks the row
 *     the day one product goes out of stock, or the day a site fills the line
 *     through the filter for some products and not others.
 *   - Delete .bedi-prail__nav[hidden] (section 14) and a visitor with no
 *     JavaScript gets two dead arrows, because an author `display` beats the
 *     UA's [hidden] rule no matter how weak the selector.
 *   - Delete min-inline-size: 0 from .bedi-prail__item (section 5) and one long
 *     Georgian product name widens its own card and breaks the row's rhythm.
 *
 * EVERY PRODUCT IN THIS CATALOGUE CURRENTLY RENDERS THE WOOCOMMERCE
 * PLACEHOLDER. Zero of 240 products sampled across four orderings carry an
 * image, so the empty media state is not an edge case here — it is every card
 * in the row until the client imports photography. Section 7 designs for that
 * deliberately, and it does NOT hide the media box when there is no picture:
 * hiding it would reflow the whole rail on the day the images land.
 *
 * MOTION is transform, colour and scroll position only. Nothing animates width,
 * height, top or left. Every duration comes from base.css and collapses under
 * prefers-reduced-motion globally; section 17 additionally undoes the resting
 * transforms and the smooth scroll, which a duration clamp cannot reach.
 *
 * @package Bedi\Theme
 */

/* =========================================================================
 * 1. THE VALUE TABLE
 *
 * The single place in this file where a number or a colour is chosen. Values
 * are mobile-first; section 15 restates the two the phone needs and section 16
 * the one that steps up at 768px.
 *
 * Read the four JUDGEMENT entries before changing anything: they are the only
 * values not taken straight off base.css's scale, and each one says what it was
 * derived from and what goes wrong at the edges.
 * ====================================================================== */

.bedi-prail,
.bedi-pcard {
	/* ---- The band ---------------------------------------------------------
	   The shell and the shared gutter, so this row's content column lands on
	   exactly the same x as the hero above it and the footer below it.
	   --bedi-shell is the measure PLUS one gutter either side, which is what a
	   full-bleed band that paints its own padding has to cap at; capping at the
	   bare measure would sit this row 40px inside the header. See the note at
	   --bedi-content-width in base.css — five different numbers for one column
	   is what this token exists to end. */
	--prail-boxed: var(--bedi-shell);
	--prail-pad-inline: var(--bedi-gutter);
	/* The standard band rhythm, shared by every section on the homepage. */
	--prail-pad-block: var(--bedi-section-y);

	/* ---- The rail ---------------------------------------------------------
	   --prail-gap is the space BETWEEN cards and, because the cards have no box
	   and — since the media well went white above — no fill either, it is now
	   the ONLY thing separating one product from the next.

	   16px -> 24px (--bedi-space-4 -> --bedi-space-6). The old note called 24px
	   the step that "starts to break the row into individual objects", and
	   rejected it while the grey well was already drawing each card's bounds.
	   With that well gone, breaking the row into individual objects is exactly
	   what has to happen — at 16px two neighbouring product shots on white had
	   nothing at all between them and read as one wide photograph. */
	--prail-gap: var(--bedi-space-6);

	/* How many cards a WIDE desktop shows, and the number the density pass
	   moved: it was 5, and five 224px cards on a 1440px screen is what read as
	   sparse next to a reference showing seven. Section 5 turns this into a
	   width; it is a bare number and must stay one, because it is divided by. */
	--prail-cols: 7;

	/* JUDGEMENT — the card's minimum width, and the single number that drives
	   the whole responsive behaviour. 10.5rem is 168px.

	   It is not arbitrary and it is not freely adjustable. Section 5 gives each
	   card the LARGER of this and one seventh of the row, so this value alone
	   decides where the rail drops from seven cards to six to five to two. The
	   ladder for the shipped values, with the 40px gutter above 768px and the
	   20px one below it:

	     viewport 1440  content 1280  seventh = 169px  ->  7 up, flush
	     viewport 1280  content 1200  seventh = 158px  ->  min wins, 6 up + peek
	     viewport 1024  content  944  seventh = 121px  ->  min wins, 5 up + peek
	     viewport  900  content  820  seventh = 103px  ->  min wins, 4 up + peek
	     viewport  640  content  600  seventh =  72px  ->  min wins, 3 up + peek
	     viewport  480  content  440  seventh =  49px  ->  min wins, 2 up + peek

	   Raise it above 169px and the wide desktop loses its seventh card; drop it
	   much below 168px and a two-line Georgian name at 13px stops fitting two
	   useful words on a line. The reference's own cards measure between 140px
	   and 220px, so 168px sits inside the range the client is comparing us with
	   rather than at an edge of it.

	   THE CARD IS THE MEDIA BOX'S HEIGHT TOO, because that box is a square
	   (section 7). Changing this number therefore moves the nav arrows, which
	   are centred on the picture by a fixed length — see --prail-nav-inset-block
	   below, and re-derive it in the same edit. */
	--prail-item-min: 10.5rem;

	/* ---- The card ---------------------------------------------------------
	   THE VERTICAL RHYTHM IS THE DESIGN. A card with no box has nothing but
	   spacing to make it read as one object, so these four steps are doing the
	   work a border would otherwise do, and they are deliberately unequal. Top
	   to bottom the card now reads 12 / 4 / 8 / 12:

	     --prail-media-gap    12px under the picture — joint largest, because it
	                          separates the image block from the text block.
	     --prail-card-gap      4px, the base step, and therefore the gap between
	                          the PRICE and the META LINE. Those two are one
	                          thought — the number and its one qualifying fact —
	                          and the reference sets them just as tight.
	     --prail-name-gap      8px above the name, so the name reads as the next
	                          thing rather than as a third price line, without
	                          leaving the price block stranded on its own.
	     --prail-actions-gap  12px above the button, matching the media gap, so
	                          the card is bracketed by its two widest spaces and
	                          the text group sits between them.

	   ALL FOUR WENT DOWN IN THE DENSITY PASS and the shape of the ratio is what
	   matters, not the absolute numbers: the base step was 8px with 12px
	   brackets, which is a 2:3 relationship the eye reads as "evenly spaced".
	   At 4px against 12px the brackets are three times the base and the grouping
	   becomes visible. Flatten all four to one value and the card goes back to
	   reading as five unrelated lines.

	   The three gaps that are not the base are applied as DIFFERENCES in section
	   6 — a flex container has one gap, so anything wider than the base is that
	   element's own margin on top of it. */
	--prail-card-gap: var(--bedi-space-1);
	--prail-name-gap: var(--bedi-space-2);
	--prail-media-gap: var(--bedi-space-3);
	--prail-actions-gap: var(--bedi-space-3);

	/* ---- The media box ----------------------------------------------------
	   TWO RADII NOW, WHERE THERE USED TO BE ONE, and the reason is worth a line
	   because "one radius per card" was a stated rule of this file. The media
	   box is a picture frame and keeps the tight 6px corner; the add-to-cart
	   control is a BUTTON, and a button reads as pressable in proportion to how
	   round it is. A 6px corner on a 36px control is what made ours read as a
	   flat slab rather than as something to click. The rule the file actually
	   wanted was "no arbitrary corners", and two deliberate ones — 6px on
	   surfaces, 12px on controls — satisfy it. --bedi-radius-md is 6px,
	   --bedi-radius-lg is 12px.

	   The padding came down with the card: 12px inside a 168px box left the
	   product itself under 145px wide. */
	--prail-media-radius: var(--bedi-radius-md);
	--prail-media-pad: var(--bedi-space-2);
	--prail-cart-radius: var(--bedi-radius-lg);

	/* JUDGEMENT, AND A FIXED LITERAL ON PURPOSE. #F9F9F9 is byte-identical to
	   the LIGHT value of --bedi-surface-2 (and of --bedi-muted), so on a normal
	   request this changes nothing; it is written out rather than read through
	   var() so it cannot follow the token to #0E1420 under data-theme="dark".

	   WHY THE ONE FIXED SURFACE IN THE FILE. Product photography on this shop is
	   shot on white. A white cut-out sitting in a near-black well reads as a
	   glaring rectangle with a picture somewhere inside it, which is worse in
	   dark mode than a light panel is. Pinning the panel is the lesser of the
	   two, and it is what forces every ink that sits ON the panel to be pinned
	   too — see convention 2 in the file header, and --prail-wish-* below.

	   THIS IS THE REVISIT THE PREVIOUS NOTE ASKED FOR, AND IT IS NOW WHITE.
	   That note said: "#F9F9F9 ... Revisit this the day real product images
	   land; it is a one-line change and the layout does not move." The
	   photographs have landed, and the grey was doing the opposite of its job on
	   them. Catalogue product shots are cut out on white, so a #F9F9F9 well puts
	   a faintly-visible grey rectangle BEHIND a white subject — the eye reads
	   the well's edge as the product's edge, and a row of them reads as a row of
	   boxes rather than a row of products.

	   It is a token rather than a literal `#fff` so the well still tracks the
	   page: if the canvas is ever re-tinted, the media goes with it instead of
	   becoming the one white rectangle on a coloured page.

	   THE RADIUS AND THE 1:1 BOX STAY. The fill was the only thing doing the
	   separating, so section 5's --prail-gap is widened in the same pass to take
	   over that job. Changing one without the other is what would make the row
	   read as touching tiles. */
	--prail-media-bg: var(--bedi-surface, #ffffff);

	/* ---- Type -------------------------------------------------------------
	   Straight off base.css's scale, which is anchored on the kit's own 14px
	   base. The section heading steps up at 768px (section 16); nothing else
	   changes size at any width.

	   THE THREE CARD SIZES ARE THE HIERARCHY, and they moved apart rather than
	   down. They used to be 16px price / 14px name / nothing else, which is a
	   single step of the scale between the two things a shopper is comparing:
	   both read as "text", and the card had no focal point. Now:

	     price  18px / 700  --bedi-text-xl   the number, and the loudest thing
	                                         in the card after the picture.
	     name   13px / 500  --bedi-text-sm   deliberately quieter than the price,
	                                         by size and by weight, exactly as the
	                                         reference has it. Its COLOUR stays
	                                         --bedi-ink: base.css measures
	                                         --bedi-ink-muted at 4.48:1 and says
	                                         in as many words to use the full ink
	                                         for anything that has to be read
	                                         rather than skimmed, and a product
	                                         name is read. Quietness is size and
	                                         weight here, never contrast.
	     meta   12px / 400  --bedi-text-xs   the floor of base.css's scale, muted,
	                                         and the quietest line in the card.

	   THE BUTTON LABEL IS THE FOURTH SIZE AND IT IS 12px, MEASURED RATHER THAN
	   CHOSEN. WooCommerce's Georgian add-to-cart string is კალათაში
	   დამატება — seventeen glyphs. On a 168px card, once the button's own
	   padding, the cart glyph and the gap beside it are taken out, the label has
	   about 132px to live in. At 13px it needs roughly 121px in one fallback face
	   and more in another, so it wrapped, and a wrapped label made the button
	   44px tall — taller than the 40px slab the pass set out to shrink. At 12px
	   it has real headroom in every face in base.css's stack, which matters here
	   because the shop's webfont binaries are not in the theme yet and the
	   rendering face is whatever the visitor's machine supplies.

	   12px is base.css's floor and the label stays legible at it: white on
	   #0668FC is 4.78:1, which clears AA for text at any size. If the label ever
	   has to grow back, the card has to widen first. */
	--prail-title-size: var(--bedi-text-xl, 1.125rem);
	--prail-all-size: var(--bedi-text-base, 0.875rem);
	--prail-name-size: var(--bedi-text-sm, 0.8125rem);
	--prail-name-weight: var(--bedi-weight-medium, 500);
	--prail-price-size: var(--bedi-text-xl, 1.125rem);
	--prail-meta-size: var(--bedi-text-xs, 0.75rem);
	--prail-was-size: var(--bedi-text-xs, 0.75rem);
	--prail-badge-size: var(--bedi-text-xs, 0.75rem);
	--prail-cart-size: var(--bedi-text-xs, 0.75rem);

	/* THE PRODUCT NAME'S LINE-HEIGHT, AND IT MUST STAY A UNITLESS NUMBER.
	   Section 10 multiplies it by 2em to reserve exactly two lines. Give it a
	   unit — 1.4rem, 20px — and that calc() becomes invalid, the min-height is
	   dropped, and the row silently loses the shared baseline the whole design
	   is built on. --bedi-leading-snug is 1.4 and is unitless for this reason.

	   --prail-meta-leading carries the same requirement for the same reason one
	   line further down, where section 9 multiplies it by 1em to reserve a
	   single line. It is a separate token rather than a reuse because the two
	   reservations must be able to move independently: the name's box is two
	   lines of 13px, the meta line's is one line of 12px. */
	--prail-name-leading: var(--bedi-leading-snug, 1.4);
	--prail-meta-leading: var(--bedi-leading-snug, 1.4);

	/* ---- Ink that follows the scheme --------------------------------------
	   Plain var(), no fallback, per convention 3. All of it sits on --bedi-bg,
	   so all of it flips with the canvas. */
	--prail-ink: var(--bedi-ink);
	--prail-ink-muted: var(--bedi-ink-muted);
	--prail-rule: var(--bedi-rule);

	/* THE SALE PRICE'S INK, AND WHY IT IS A PAIR OF TOKENS RATHER THAN ONE.
	   A full-price product prints a BLACK price — that is what the reference
	   does and it is what keeps the accent worth something. Only a genuinely
	   discounted one turns red, beside its struck former price, so the colour
	   carries information instead of decorating every card in the row.

	   The brief asked for --bedi-danger. Measured on the two canvases this
	   section actually paints on, that token is right in one scheme and wrong in
	   the other, and so is the obvious alternative:

	     --bedi-danger #F2442C on #FFFFFF  3.72:1   FAILS a 18px/700 price
	     --bedi-danger #FF6B6B on #0B0F17  6.95:1   passes
	     --bedi-alert  #D50045 on #FFFFFF  5.36:1   passes
	     --bedi-alert  #D50045 on #0B0F17  3.60:1   FAILS the same price

	   18px at weight 700 is 13.5pt bold, just under the 14pt bold that would let
	   it use the 3:1 large-text threshold, so it needs 4.5:1 and neither token
	   clears that in both schemes. --bedi-alert is a brand constant base.css
	   deliberately leaves OUT of its dark block, which is exactly why it cannot
	   follow the canvas down; --bedi-danger is redefined there and can. So the
	   light scheme takes alert and the dark scheme takes danger, in the one
	   override directly below this rule. Both are the same red family and the
	   swap is invisible — it is a legibility fix, not a colour change.

	   base.css states the underlying rule itself: "--bedi-alert is the one to
	   use when the danger state has to be READ". The sale badge two sections
	   down already made this call for the same reason. */
	--prail-sale: var(--bedi-alert);

	/* The "ყველა" link and the product name on hover. --bedi-link-brand, not
	   --bedi-accent: base.css is explicit that the accent #2D81FF is a FILL
	   colour at 3.69:1 on white and that the text role is #0668FC at 4.78:1.
	   Both of these are small, body-size strings. */
	--prail-link: var(--bedi-link-brand);

	/* The add-to-cart fill, and the ink base.css pairs with it. Same argument:
	   the button label is 13px, so the fill is --bedi-accent-strong (#0668FC,
	   white on it 4.78:1) rather than --bedi-accent (3.69:1). In dark the pair
	   inverts wholesale — #06121C on #8FD2F5 — which is why this is a pair of
	   tokens and not a colour plus a guess. */
	--prail-cart-bg: var(--bedi-accent-strong);
	--prail-cart-ink: var(--bedi-on-accent);

	/* THE BUTTON'S WEIGHT, AND IT IS THE POINT OF THE BUTTON PASS. It was 700 —
	   the same weight as the price, on a saturated blue fill, 40px tall and full
	   width, which is four ways of being the loudest thing in a card whose job
	   is to sell a product rather than a button. Dropping the label to 500 puts
	   it level with the product name and leaves the price alone at the top of
	   the card. The fill and the full width stay: width was never the problem.

	   The height came down 40px -> 36px. It still clears the 24px minimum
	   target size WCAG 2.2 asks for by half again, and 36px is the same height
	   as the wish control, so the two controls in the card agree. */
	--prail-cart-weight: var(--bedi-weight-medium, 500);
	--prail-cart-min-block: 2.25rem;
	--prail-cart-icon: 1rem;
	/* 4px, not 8px, and the four pixels went to the label. See the measurement
	   at --prail-cart-size: on this card the button is width-bound, and a gap is
	   the cheapest thing in it to give up. */
	--prail-cart-gap: var(--bedi-space-1);

	/* The nav arrows sit on the page, so they take the raised-panel set and
	   flip with it. */
	--prail-nav-bg: var(--bedi-surface);
	--prail-nav-ink: var(--bedi-ink);
	--prail-nav-shadow: var(--bedi-shadow-2);

	/* ---- Ink that is PINNED, because it sits on the fixed media panel ------
	   Every value here is the LIGHT value of the token named beside it, written
	   out so it cannot flip while the panel underneath it stays #F9F9F9.
	   Contrasts are measured against #F9F9F9, not against white.

	     --prail-wish-ink   #777777  --bedi-ink-muted    4.25:1  (icon, needs 3)
	     --prail-wish-on    #F2442C  --bedi-danger       3.54:1  (icon, needs 3)
	     --prail-wish-focus #0668FC  --bedi-accent-2     4.54:1  (ring, needs 3)

	   For comparison, the DARK values of the same three tokens on the same fixed
	   panel are 2.29:1, 2.64:1 and — for --bedi-accent — 2.17:1. All three fail.
	   That is the entire argument for pinning them, and it is the same argument
	   section-category-cards.css makes for --cards-focus-on-tile.

	   --prail-wish-bg is a white disc at 92% — the light value of
	   --bedi-glass-strong — so the heart still reads once real product
	   photography arrives behind it. On today's near-white empty panel the disc
	   is almost invisible, which is correct: the control should be quiet until
	   there is a picture for it to sit on. */
	--prail-wish-ink: #777777;
	--prail-wish-on: #f2442c;
	--prail-wish-focus: #0668fc;
	--prail-wish-bg: rgba(255, 255, 255, 0.92);

	/* THE SALE BADGE, AND WHY IT IS THE ONE COLOUR PAIR THAT NEEDS NO PINNING.
	   --bedi-alert #D50045 is one of the handful of brand constants base.css
	   deliberately leaves OUT of its dark block, so it is already the same
	   colour in both schemes, and white on it is 5.36:1 either way.

	   It is --bedi-alert and NOT --bedi-danger, although danger is the obvious
	   token for a red pill. --bedi-danger is #F2442C, which puts white text at
	   3.72:1 — fine for a fill, an icon or a border, short of the 4.5:1 a
	   12px "-15%" needs. base.css says this in as many words: "--bedi-alert is
	   the one to use when the danger state has to be READ". This one is read. */
	--prail-badge-bg: var(--bedi-alert);
	--prail-badge-ink: #ffffff;

	/* ---- Controls ---------------------------------------------------------
	   40px and 32px. Both clear the 24px minimum target size WCAG 2.2 asks for
	   and the 44px iOS guideline is met by the padding around the wish button's
	   disc rather than by the disc itself.

	   The heart came down from 36px with the card. On a 168px media box a 36px
	   disc plus its 8px inset occupied a quarter of the picture's width, which
	   on a row of placeholders reads as a row of buttons with a grey square
	   behind them. 32px is the same control at the same target size, in
	   proportion to the smaller frame. */
	--prail-nav-size: 2.5rem;
	--prail-wish-size: 2rem;
	--prail-wish-inset: var(--bedi-space-2);

	/* JUDGEMENT — how far down .bedi-prail__inner the arrows sit, measured to
	   their CENTRE. The arrows have to land on the picture, and the picture is a
	   square whose height equals the card's width, which CSS at this level
	   cannot read — so the distance is derived once, here, and re-derived
	   whenever --prail-item-min moves.

	   8.5rem is 136px, and the arithmetic is: the head is 29px of 24px/1.2
	   heading, plus its 20px margin, plus the viewport's own 4px of block
	   padding, so the media box starts about 53px down. The box is 168px to
	   169px tall at every width section 5 can produce between 480px and the
	   1280px cap, so its middle is about 53 + 84 = 137px. The arrow is 40px
	   tall, so it spans 116px to 156px and sits comfortably inside the picture.

	   Below 768px the heading is 18px rather than 24px and the head loses 7px,
	   which moves the arrow 7px below the true centre and changes nothing that
	   is visible. This value WAS 7.5rem, derived against a 224px media box and
	   without counting the head at all; the old number still landed on the
	   picture, which is why nobody noticed. */
	--prail-nav-inset-block: 8.5rem;

	--prail-dur: var(--bedi-duration, 200ms);
	--prail-ease: var(--bedi-easing, cubic-bezier(0.2, 0, 0.2, 1));
}

/*
 * THE ONE VALUE THAT CANNOT BE CHOSEN ONCE FOR BOTH SCHEMES.
 *
 * Everything else in the table above is either a token that follows the canvas
 * or a literal pinned to the one surface that does not. The sale price is
 * neither: it sits on the canvas, so it has to flip, but the two red tokens
 * base.css offers each fail on one of the two canvases at this size. The
 * measurements are at --prail-sale.
 *
 * (0,2,1) against the table's (0,1,0), so it wins wherever it applies, and it
 * applies only when the document has actually opted into dark. Nothing else in
 * this file needs a dark branch.
 */
:root[data-theme="dark"] .bedi-prail,
:root[data-theme="dark"] .bedi-pcard {
	--prail-sale: var(--bedi-danger);
}

/* =========================================================================
 * 2. THE BAND
 *
 * A direct child of <main>, full width, no background of its own — the canvas
 * shows through, which is the whole point of a section whose cards have no
 * boxes. The boxed width and the gutter live on the inner element so the
 * scroll rail inside can still run to the viewport edge if a later pass wants
 * it to.
 * ====================================================================== */

.bedi-prail {
	display: block;
	color: var(--prail-ink);
	padding-block: var(--prail-pad-block);
}

.bedi-prail__inner {
	box-sizing: border-box;
	margin-inline: auto;
	max-inline-size: var(--prail-boxed);
	/*
	 * THIS IS WHAT STOPPED THE WHOLE PAGE SCROLLING SIDEWAYS.
	 *
	 * `.bedi-prail__viewport` is the scroller and it does clip its own
	 * children — but the track's scrollable width was still reaching the
	 * document, so the homepage had a scrollWidth of 2192 against a 1440
	 * viewport and could genuinely be dragged left. Measured by clipping each
	 * level in turn: the track, the viewport and the hero's own slider all made
	 * no difference; clipping HERE was what fixed it.
	 *
	 * `clip`, not `hidden`, and the difference matters. `hidden` makes this a
	 * scroll container, which becomes the nearest scrollport for everything
	 * inside it — that changes how a sticky descendant resolves and adds a
	 * programmatically scrollable box for no reason. `clip` just clips.
	 *
	 * `overflow-clip-margin` gives the nav arrows their bleed back: they sit
	 * 20px outside this box by design, and a bare `clip` would cut them in
	 * half.
	 */
	overflow-x: clip;
	overflow-clip-margin: var(--bedi-space-6);
	padding-inline: var(--prail-pad-inline);
	/* The containing block for the two absolutely positioned nav arrows in
	   section 14. Nothing else in this file is positioned against it. */
	position: relative;
}

/* =========================================================================
 * 3. THE HEAD
 *
 * Heading on the start edge, "ყველა" on the end edge, aligned on their shared
 * baseline rather than centred on each other — the link is 14px and the
 * heading 18/24px, and centring them leaves the link visibly floating.
 * ====================================================================== */

.bedi-prail__head {
	align-items: baseline;
	display: flex;
	gap: var(--bedi-space-4);
	justify-content: space-between;
	margin-block-end: var(--bedi-space-5);
}

.bedi-prail__title {
	color: var(--prail-ink);
	/*
	 * MarkGEO Caps, like every other title on the site. This was the body face
	 * — so the page's own section headings ("პოპულარული პროდუქცია") were set in
	 * Mulish while the h1 above them, the hero and the footer headings were all
	 * in the display face.
	 */
	font-family: var(--bedi-font-display);
	font-size: var(--prail-title-size);
	font-weight: var(--bedi-weight-bold, 700);
	line-height: var(--bedi-leading-tight, 1.2);
	/* min-inline-size: 0 so a long Georgian section name wraps instead of
	   pushing the "ყველა" link off the end of the row. */
	min-inline-size: 0;
}

/*
 * base.css underlines links only inside .bedi-entry__content and
 * .bedi-empty__text, so this link starts with no underline and does not need to
 * opt out of one. What it DOES need is its own colour: `a { color:
 * var(--bedi-accent) }` is (0,0,1) and loses to this (0,1,0) rule outright.
 */
.bedi-prail__all {
	color: var(--prail-link);
	flex: none;
	font-size: var(--prail-all-size);
	font-weight: var(--bedi-weight-bold, 700);
	text-decoration: none;
}

/*
 * THE HOVER STATE HAS TO BE AN UNDERLINE, NOT A COLOUR, AND THAT IS FORCED.
 *
 * base.css sets `:root :where(a:hover) { color: var(--bedi-accent-strong) }`,
 * which is (0,1,0) — a tie with the rule above that this file wins on source
 * order, since bedi-section-products is printed after bedi-base. So the colour
 * IS ours to choose. The problem is that there is nothing to choose: in the
 * light palette --bedi-link-brand and --bedi-accent-strong are the same value,
 * #0668FC, so a hover that "darkens to the strong accent" would be a hover that
 * does nothing at all. The underline is what actually changes state here.
 *
 * Restated at :active and :focus-visible as well, because base.css's pair
 * covers :active too and would otherwise take the one state back.
 */
.bedi-prail__all:hover,
.bedi-prail__all:active,
.bedi-prail__all:focus-visible {
	color: var(--prail-link);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 0.18em;
}

/* =========================================================================
 * 4. THE VIEWPORT AND THE TRACK
 *
 * The rail is a real scroll container, not a transform-driven carousel. It
 * works with a trackpad, with a mouse wheel held sideways, with a touch flick
 * and with the keyboard before a single line of JavaScript has run — which is
 * what lets the prev/next arrows be pure progressive enhancement (section 14).
 *
 * THE SCROLL CONTAINER IS __viewport AND THE FLEX ROW IS __track, which keeps
 * the <ul>/<li> semantics intact. No display: contents anywhere: it still drops
 * list roles in some assistive technology, and a rail that announces "list, 20
 * items" is materially more navigable than one that announces twenty loose
 * links.
 * ====================================================================== */

.bedi-prail__viewport {
	overflow-x: auto;
	/* A horizontal flick at the end of the rail must not become a browser
	   back-navigation. This is the single most annoying failure mode of a
	   horizontal scroller on a trackpad and it is one declaration to prevent. */
	overscroll-behavior-inline: contain;
	scroll-behavior: smooth;
	/*
	 * mandatory, as specified, and it is safe here only because section 5 caps
	 * the card width. A mandatory snap container whose items are WIDER than the
	 * viewport can trap scrolling between two snap points; our widest possible
	 * card is one viewport minus a fifth (section 15), so there is always a
	 * reachable snap position. If a later pass ever lets an item grow past the
	 * viewport, change this to proximity in the same edit.
	 */
	scroll-snap-type: x mandatory;
	/* Half the arrow, which is exactly how far the arrow overlaps the content
	   area — see the straddle note in section 14. Without it a snapped or
	   focus-scrolled card comes to rest with its own corner, its badge or its
	   heart underneath a nav button. It does not affect the resting state of
	   the rail: scroll position cannot go below zero, so the first card is
	   still flush with the start edge. */
	scroll-padding-inline: calc(var(--prail-nav-size) / 2);

	/*
	 * 4px of block padding, and it is not decoration.
	 *
	 * `overflow-x: auto` computes overflow-y to auto as well — one axis cannot
	 * be visible while the other scrolls — so this box CLIPS vertically. The
	 * add-to-cart button sits flush with the bottom of the card and its focus
	 * ring is drawn 2px outside itself with a 2px offset, so without this the
	 * keyboard ring on the last control of every card would be sliced off, or
	 * would raise a stray vertical scrollbar. 4px is exactly
	 * --bedi-focus-width + --bedi-focus-offset.
	 */
	padding-block: var(--bedi-space-1);

	/* The scrollbar is hidden and the rail stays fully scrollable: none of
	   these properties affect the wheel, the trackpad, the keyboard or a touch
	   flick, they only remove the painted bar. The arrows and the peeking next
	   card are the affordance instead. */
	scrollbar-width: none;
	-ms-overflow-style: none;
}

.bedi-prail__viewport::-webkit-scrollbar {
	display: none;
}

/*
 * If a browser makes the scroll container itself focusable — Chrome does this
 * for scrollers with no focusable children — the ring has to be drawn INSIDE,
 * because the box clips. Our cards do contain focusable links, so this rule
 * will usually never fire; it costs two declarations and covers the case where
 * a future variant of the card is display-only.
 */
.bedi-prail__viewport:focus-visible {
	outline: var(--bedi-focus-width, 2px) solid var(--bedi-accent);
	outline-offset: calc(var(--bedi-focus-offset, 2px) * -1);
}

/*
 * base.css already zeroes list padding and removes the markers for any ul
 * carrying a class, and `* { margin: 0 }` covers the margin. All three are
 * restated because this row must not depend on the track keeping a class
 * attribute for its own list reset — that is an attribute selector holding up a
 * layout, and it is invisible when it breaks.
 */
.bedi-prail__track {
	display: flex;
	/* stretch, so every card in the row is the height of the tallest one. This
	   plus the auto margin in section 6 is what puts the buttons on a shared
	   baseline even when one card's price wraps. */
	align-items: stretch;
	gap: var(--prail-gap);
	list-style: none;
	margin: 0;
	padding: 0;
}

/* =========================================================================
 * 5. THE ITEM WIDTH — ONE FORMULA, NOT SIX BREAKPOINTS
 *
 * The brief asks for roughly seven cards on a wide desktop, six at 1280, five at
 * 1024, four at 900, three at 640 and two at 480. Written as media queries that
 * is six blocks that have to be kept consistent with each other and with the
 * gap. Written as a width it is one declaration: each card is the LARGER of the
 * minimum width and its share of the row, so the rail sheds a column by itself
 * whenever the share falls under the minimum.
 *
 * The density pass changed the two numbers this formula reads and did not touch
 * the formula, which is the whole argument for writing it this way: going from
 * five cards to seven was two values in section 1, not six new breakpoints.
 *
 * The step table for the shipped values is at --prail-item-min in section 1.
 *
 * The percentage resolves against the track's content box, which is the
 * viewport's content box, because the track is a block-level flex container
 * inside the scroller and takes its full width. The items then overflow it —
 * which is the scroll.
 * ====================================================================== */

.bedi-prail__item {
	/* A flex box so .bedi-pcard can fill the stretched item; see section 6. */
	display: flex;

	flex: 0 0 max(
		var(--prail-item-min),
		calc((100% - (var(--prail-cols) - 1) * var(--prail-gap)) / var(--prail-cols))
	);

	/*
	 * NOT REMOVABLE. A flex item's automatic minimum size is its content, so
	 * with min-inline-size at its `auto` default a single unbreakable string —
	 * and Georgian product names are full of Latin model numbers like
	 * "DSP-8PH4-03" — makes the item wider than the basis above and knocks one
	 * card out of step with the rest of the row. Zero here caps it at the
	 * basis and hands the wrapping problem to the name's own overflow-wrap in
	 * section 10, which is where it belongs.
	 */
	min-inline-size: 0;

	scroll-snap-align: start;
}

/* =========================================================================
 * 6. THE CARD — NO WRAPPER, AND WHAT HOLDS IT TOGETHER INSTEAD
 *
 * Read the file header before adding anything to this rule. There is no border,
 * no shadow, no background and no radius, deliberately: the card sits on the
 * page and is separated from its neighbours by --prail-gap alone.
 *
 * What makes it read as a single object rather than as four stacked lines is
 * the unequal spacing scale in section 1 and the alignment below. That is the
 * client's main visual ask and it is the reason this section is mostly comment.
 * ====================================================================== */

.bedi-pcard {
	display: flex;
	/* Fills the stretched <li>, so every card in the row is the same height and
	   the auto margin below has something to push against. */
	flex: 1 1 auto;
	flex-direction: column;
	gap: var(--prail-card-gap);
	min-inline-size: 0;
	/* Stated rather than assumed: nothing in this file may give the card a
	   surface, and writing the zero out makes an accidental inheritance from a
	   future shared card rule visible in a diff. */
	background: none;
	border: 0;
	border-radius: 0;
	box-shadow: none;
	padding: 0;
}

/*
 * THE THREE SPACES THAT ARE NOT THE CARD'S GAP.
 *
 * The flex gap above is the 4px base step, which is the space between the price
 * and the meta line. The picture, the name and the button each want more — see
 * the rhythm note in section 1 — and a flex container has exactly one gap, so
 * each of the three adds its own margin ON TOP of the base. What is written
 * below is therefore the DIFFERENCE, never the total: 4 + 8 = 12 under the
 * picture, 4 + 4 = 8 above the name, 4 + 8 = 12 above the button.
 *
 * Writing them as differences rather than as flat values is what keeps the
 * rhythm coherent when --prail-card-gap changes: move the base step and all
 * four spaces move with it, in proportion, instead of three of them staying
 * where they were and the grouping quietly inverting.
 */
.bedi-pcard__media {
	margin-block-end: calc(var(--prail-media-gap) - var(--prail-card-gap));
}

/*
 * The name's own extra 4px. It is here, with the other two, rather than in
 * section 10 with the rest of the name's rules, because this is where the
 * card's vertical rhythm lives and all three differences have to be read
 * together to make sense of any one of them.
 */
.bedi-pcard__name {
	margin-block-start: calc(var(--prail-name-gap) - var(--prail-card-gap));
}

/*
 * The actions row is pushed to the bottom of the card by an auto margin and
 * given its air by padding. The two cannot fight: the margin owns the push, the
 * padding owns the gap.
 *
 * THE AUTO MARGIN IS THE THIRD PART OF THE SHARED BASELINE. Sections 9 and 10
 * make every card's meta line and title box the same height, which handles the
 * common case between them. This
 * handles the rest of them — a product whose price is hidden, a price that
 * wraps to two lines on a narrow card, a future variant with one more line —
 * by pinning the button to the bottom edge of a row whose cards are already all
 * the same height. With both, there is no content difference between two
 * products that can put their buttons on different lines.
 */
.bedi-pcard__actions {
	display: flex;
	flex-direction: column;
	gap: var(--bedi-space-2);
	margin-block-start: auto;
	padding-block-start: calc(var(--prail-actions-gap) - var(--prail-card-gap));
}

/* =========================================================================
 * 7. THE MEDIA BOX
 *
 * A fixed 1:1 box with a calm neutral behind it, and it is drawn whether or not
 * there is a photograph to put in it.
 *
 * NOT ONE PRODUCT IN THIS CATALOGUE HAS AN IMAGE TODAY — 0 of 240 sampled —
 * so every card in the shipped row renders WooCommerce's placeholder. Two
 * consequences, both deliberate:
 *
 *   1. The box is NOT hidden when the image is a placeholder. Hiding it would
 *      make the whole rail reflow, and every card jump, on the day the client
 *      finishes importing photography. The box is the same size either way.
 *   2. The placeholder gets its own quiet treatment at the bottom of this
 *      section, so twenty identical grey squares read as a catalogue awaiting
 *      pictures rather than as twenty broken images.
 *
 * aspect-ratio reserves the box before anything loads, which together with the
 * width/height attributes WooCommerce prints means this row cannot shift the
 * page as images arrive.
 * ====================================================================== */

.bedi-pcard__media {
	aspect-ratio: 1 / 1;
	background-color: var(--prail-media-bg);
	border-radius: var(--prail-media-radius);
	inline-size: 100%;
	/*
	 * The containing block for the badge and the wish button. overflow stays
	 * VISIBLE here on purpose — the clipping happens one level down, on the
	 * link, so the wish button's focus ring is not sliced off by the corner
	 * radius of the box it is pinned inside.
	 *
	 * THAT CLIP IS ALSO WHAT CONTAINS THE HOVER ZOOM (section 13), and it is the
	 * only clip in the card: .bedi-pcard has no wrapper to clip against, by
	 * design, so a picture that grew past its frame would grow over the price of
	 * the card beside it. The requirement is "overflow hidden somewhere between
	 * the media box and the image", and satisfying it on the link rather than
	 * here costs nothing and keeps the focus ring whole.
	 */
	position: relative;
}

/*
 * The image link is tabindex="-1" aria-hidden="true" in the markup, and that is
 * not an oversight to be corrected. The product name below it is the same
 * destination, so exposing both would give every product two identical tab
 * stops and two identical announcements. The picture stays clickable for the
 * mouse; the name is the accessible one. Nothing here should ever add a focus
 * style to it — it cannot receive focus.
 *
 * border-radius: inherit rather than a repeated value, so the clip can never
 * drift away from the box that paints the corner.
 */
.bedi-pcard__media-link {
	align-items: center;
	border-radius: inherit;
	display: flex;
	justify-content: center;
	overflow: hidden;
	padding: var(--prail-media-pad);

	/*
	 * ABSOLUTE, AND THAT IS WHAT ACTUALLY ENFORCES THE SQUARE.
	 *
	 * The box above declares `aspect-ratio: 1 / 1`, which reads like a
	 * guarantee and is not one: aspect-ratio sets a PREFERRED size, and
	 * in-flow content that wants to be taller still wins. This link was
	 * in-flow at block-size 100%, and its image resolved its own height from
	 * the file's intrinsic ratio — so the three sizes chased each other and
	 * the picture won.
	 *
	 * It only ever showed on a thumbnail that is not square. WooCommerce
	 * crops most of this catalogue to 400x400, so every card looked correct
	 * by coincidence; a 300x400 crimping tool made its card 219px tall
	 * against its neighbours' 168 and knocked the whole row's prices and
	 * buttons out of line.
	 *
	 * Taken out of flow, the link can no longer contribute height to its
	 * parent. The square comes from the aspect-ratio alone, and the image
	 * letterboxes inside it on `object-fit: contain`, which is what that
	 * declaration was always for.
	 */
	inset: 0;
	position: absolute;
}

/*
 * (0,1,1), which is what it takes to beat base.css's `img { height: auto }` at
 * (0,0,1) and let the picture fill the square.
 *
 * object-fit: CONTAIN, never cover. These are product shots on white, not
 * photographs with a subject — cropping a router in half to fill a square is
 * not a design decision, it is a mistake with a trim.
 */
.bedi-pcard__media img {
	block-size: 100%;
	inline-size: 100%;
	object-fit: contain;
	object-position: center;
	transition: transform var(--prail-dur) var(--prail-ease);
}

/*
 * THE EMPTY STATE, WHICH IS CURRENTLY EVERY CARD.
 *
 * .woocommerce-placeholder is WooCommerce's own class on the image
 * wc_placeholder_img() returns, so this rule is the one place in the file that
 * depends on a class this theme does not own. It degrades to nothing if
 * WooCommerce ever renames it: the placeholder simply shows at full strength,
 * which is the current upstream default and not a broken layout.
 *
 * STILL NO REPLACEMENT GRAPHIC, for the reason it never had one: the theme is
 * presentation only and has no business shipping its own "no image" artwork
 * into a shop whose photography is days away. What changed is how the
 * upstream graphic is presented. At full bleed and 0.6 it filled the frame, so
 * a row of seven cards read as seven failed image loads. Held to half the box
 * and 0.35 it reads as a small, centred, deliberate mark in an empty frame —
 * which is what it is. The (0,2,1) selector is what beats the width and height
 * set on every image two rules above.
 *
 * NOTHING HERE IS CONDITIONAL AND NOTHING SHRINKS. The media box keeps its
 * full square either way. The only thing that gets smaller is the placeholder
 * graphic inside it, so the day real photography lands not one card in the row
 * changes height and not one price moves.
 *
 * The hover zoom in section 13 is deliberately NOT suppressed for placeholders.
 * It would be easy to argue that zooming a picture which is not there is
 * meaningless — but today it is EVERY card, so suppressing it would mean the
 * card had no hover response at all until the photography arrives.
 */
.bedi-pcard__media img.woocommerce-placeholder {
	block-size: 50%;
	inline-size: 50%;
	opacity: 0.35;
}

/* =========================================================================
 * 8. PRICES — NOW THE FIRST THING UNDER THE PICTURE
 *
 * Two elements, and the second one is almost never present: the live catalogue
 * has 1259 products and zero on sale, so .bedi-pcard__price-was and
 * .bedi-pcard__badge are skinned here against the day a sale runs.
 *
 * NEITHER RULE CAN CREATE A DISCOUNT. There is no content: string anywhere in
 * this file. The template part prints the struck price and the badge only when
 * WooCommerce reports the product on sale with a real regular price above the
 * sale price, and no change to this stylesheet can alter that.
 *
 * THE SAME GUARANTEE NOW COVERS THE PRICE'S COLOUR, which is the one thing
 * this section gained in the redesign. A full-price product prints a black
 * price; only a discounted one turns red. The selector that paints it red is
 * conditioned on an old price actually being present in the element — see the
 * rule itself — so the accent cannot appear without the struck figure that
 * justifies it, and it cannot be forced on by a class, an attribute or an
 * argument, because there is none to set.
 * ====================================================================== */

.bedi-pcard__prices {
	align-items: baseline;
	display: flex;
	gap: var(--bedi-space-2);
	/*
	 * Wrapping is safe here, and it is worth saying why, because on most cards
	 * a wrapping price row is how a grid loses its alignment. It cannot here:
	 * the track stretches every card to the tallest one and the actions row is
	 * pushed down by an auto margin (section 6), so a price that wraps makes the
	 * whole ROW taller and leaves every button exactly where it was.
	 */
	flex-wrap: wrap;
}

.bedi-pcard__price {
	color: var(--prail-ink);
	font-size: var(--prail-price-size);
	font-weight: var(--bedi-weight-bold, 700);
	/* "170,00 ₾" — GEL, two minor digits, Woo's own formatting. Tabular figures
	   so the decimal parts line up down the row instead of drifting by the
	   width of a 1. */
	font-variant-numeric: tabular-nums;
	line-height: var(--bedi-leading-snug, 1.4);
}

/*
 * THE SALE COLOUR, AND THE SELECTOR IS THE ARGUMENT.
 *
 * The reference turns a price orange ONLY when it is discounted, and pairs it
 * with a struck grey former price. A normal, full-price product keeps a black
 * one. That is not a stylistic detail: a shop where every price is an accent
 * colour has taught its customers that the accent means nothing, and then has
 * nothing left to say with when a real sale runs.
 *
 * So the rule is conditioned on the struck price BEING THERE. :has() looks
 * inside the element for .bedi-pcard__price-was, which the template part emits
 * only behind $product->is_on_sale() with a regular price that survived the
 * arithmetic in its section 4. There is no modifier class and no data
 * attribute, deliberately: those can be set by hand, by a filter or by a
 * future caller in a hurry, and a red price with no old price beside it is a
 * discount claim with nothing behind it. This one cannot be written by
 * accident, because the only way to turn it on is to print the number that
 * makes it true.
 *
 * WHERE :has() IS MISSING — a pre-2023 engine — the price simply stays
 * --prail-ink. That is the correct degradation: a black price on a sale card
 * understates the offer, which is the safe direction, and the struck former
 * price and the -N% badge both still render and both still carry the numbers.
 *
 * (0,2,0), so it beats the rule above it.
 */
.bedi-pcard__prices:has(.bedi-pcard__price-was) .bedi-pcard__price {
	color: var(--prail-sale);
}

/*
 * text-decoration is stated rather than inherited from the UA's default for
 * <s>. The element carries the meaning; a reset elsewhere must not be able to
 * silently turn a struck former price into what looks like a second current
 * price.
 *
 * It stays 12px and muted while the current price went up to 18px, which is
 * the whole of the pairing the reference uses: the number being charged is the
 * loud one and the number no longer being charged is a footnote beside it.
 */
.bedi-pcard__price-was {
	color: var(--prail-ink-muted);
	font-size: var(--prail-was-size);
	font-variant-numeric: tabular-nums;
	line-height: var(--bedi-leading-snug, 1.4);
	text-decoration: line-through;
	text-decoration-thickness: 1px;
}

/*
 * The -N% pill. Pinned to the start corner of the picture, opposite the heart.
 *
 * Its colour pair needs no dark variant: --bedi-alert is one of the brand
 * constants base.css deliberately leaves out of its dark block, and white on
 * #D50045 is 5.36:1 in both schemes. See the note in section 1 for why this is
 * --bedi-alert rather than the more obvious --bedi-danger — and note that the
 * sale price above reaches the same token by a different route, and for the
 * light scheme only.
 */
.bedi-pcard__badge {
	background-color: var(--prail-badge-bg);
	border-radius: var(--bedi-radius-pill, 999px);
	color: var(--prail-badge-ink);
	font-size: var(--prail-badge-size);
	font-variant-numeric: tabular-nums;
	font-weight: var(--bedi-weight-bold, 700);
	inset-block-start: var(--prail-wish-inset);
	inset-inline-start: var(--prail-wish-inset);
	line-height: 1;
	padding: var(--bedi-space-1) var(--bedi-space-2);
	position: absolute;
	/* Above the image, below nothing else. A local 1, not a rung of base.css's
	   shared z ladder — see the note in section 14. */
	z-index: 1;
}

/* =========================================================================
 * 9. THE META LINE
 *
 * One quiet line between the price and the name. In the reference this slot
 * carries an instalment offer; here it carries stock availability, because
 * this shop has no credit partner and a monthly figure derived from a price is
 * an advertisement for a loan nobody has agreed to lend. The template part's
 * section 6 makes that argument at length and owns the
 * `bedi_theme_product_card_meta` filter a shop with real instalment terms
 * would use instead.
 *
 * THIS SECTION'S ONE JOB IS THE RESERVED HEIGHT, and it is the same job the
 * title box does one section down — read that one first, because the reasoning
 * is identical and is written out there in full.
 *
 * The line is optional CONTENT and is never optional SPACE. A site that fills
 * it for some products and not others, or a catalogue where one product goes
 * out of stock and its neighbour does not, must not produce a row in which two
 * cards' names start at different heights and two buttons sit a line apart.
 * That zig-zag is the exact defect this card was commissioned to remove, and
 * the meta line is the newest and quietest way of reintroducing it.
 * ====================================================================== */

.bedi-pcard__meta {
	color: var(--prail-ink-muted);
	font-size: var(--prail-meta-size);
	line-height: var(--prail-meta-leading);

	/*
	 * NOT DECORATION, AND NOT REMOVABLE. This element is a <p>, and base.css
	 * gives every <p> in the theme a 16px margin-block-end at (0,0,1), which is
	 * right for running prose. Inside a flex card that margin lands between
	 * the meta line and the name and adds itself to the 4px gap and the name's
	 * own 4px, so the 8px step designed in section 1 renders as 24px and the
	 * card's rhythm inverts: the loosest space in the text block ends up in the
	 * middle of it rather than at its edges. It was measured in a browser, not
	 * inferred.
	 *
	 * margin-block rather than margin, so a future inline-axis need is not
	 * silently overwritten by a reset that was only ever about the block axis.
	 */
	margin-block: 0;

	/*
	 * ONE LINE, CLAMPED. The default is Woo's own availability string and is
	 * short, but the filter can return anything, and a two-line meta line in one
	 * card of a row is the same misalignment as no line at all. All four
	 * declarations are required together, exactly as in section 10.
	 */
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 1;
	line-clamp: 1;
	overflow: hidden;

	/*
	 * THE RESERVATION. 1em is this element's own font-size — em on min-height
	 * resolves against the element's own type, not its parent's — multiplied by
	 * the declared line-height ratio above, so changing --prail-meta-size cannot
	 * desynchronise the reserved height from the real one.
	 *
	 * --prail-meta-leading MUST STAY UNITLESS for this calc to be valid, for the
	 * reason spelled out at --prail-name-leading in section 1.
	 *
	 * An EMPTY element with a min-height is the point: the template part prints
	 * this element unconditionally, so a card whose filter returned '' holds the
	 * space anyway and its name starts level with its neighbours'.
	 */
	min-block-size: calc(var(--prail-meta-leading) * 1em);

	/* base.css applies overflow-wrap to p, and this is a p, so this is a
	   restatement rather than a fix — stated because the element's tag is the
	   template part's to choose and a future <span> would lose it silently. */
	overflow-wrap: break-word;
}

/* =========================================================================
 * 10. THE TITLE BOX — THE POINT OF THIS FILE
 *
 * Georgian product names on this catalogue vary wildly in length. Two real
 * ones, side by side in the same row:
 *
 *   "სტეპლერის ტყვიები Topmaster 511336 10mm (U-Type)"
 *   "HDMI სპლიტერი Gembird HDMI Splitter 8 Ports (DSP-8PH4-03)"
 *
 * One wraps to two lines, one to three, and a short one takes a single line.
 * Left alone, every card's button lands at a different height and the row's
 * buttons zig-zag by a line-height. That is the exact thing the client asked us
 * to fix.
 *
 * THE FIX IS A RESERVED BOX: the name is clamped to at most two lines AND holds
 * the height of two lines even when it only has one. Both halves are required
 * and they solve opposite problems — the clamp stops a long name pushing the
 * button down, the min-height stops a short name pulling it up.
 *
 * THE NAME MOVED BELOW THE PRICE IN THE REDESIGN and none of the above changed
 * with it. It changed what the box protects, and only that: with the name on
 * top, the reservation kept the price and the button on a shared baseline;
 * below the price, it keeps the button on one. The auto margin in section 6 is
 * still the belt to this bracing.
 * ====================================================================== */

.bedi-pcard__name {
	color: var(--prail-ink);
	font-size: var(--prail-name-size);
	/*
	 * 500, not 400, and it is not a contradiction of "quieter than the price".
	 * The name dropped a full step of the scale, from 14px to 13px, and 400 at
	 * 13px on a 168px card reads as a caption rather than as the product. Medium
	 * holds it up as a heading while the price keeps 18px/700 and stays,
	 * unambiguously, the loudest text in the card. The reference sets its own
	 * titles at 500 for the same reason.
	 */
	font-weight: var(--prail-name-weight);
	line-height: var(--prail-name-leading);
	text-decoration: none;

	/* The clamp. -webkit-box is still the only combination with real support;
	   `line-clamp` is the standard property and is declared alongside it so
	   this rule keeps working the day engines ship the unprefixed form and drop
	   the prefixed one. All four declarations are required together: without
	   the box display the clamp does nothing, without overflow: hidden the
	   third line still paints. */
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;

	/*
	 * THE DECLARATION THAT LOOKS REMOVABLE AND IS NOT.
	 *
	 * The clamp above caps a long name at two lines. It does nothing whatsoever
	 * for a SHORT one: a single-line title occupies one line, the button rides
	 * up by 18px, and that card's add-to-cart sits a line higher than its
	 * neighbours'. In a row of seven cards with seven different name lengths the
	 * result is seven different button heights, which is precisely the zig-zag
	 * the client described and asked to have removed.
	 *
	 * So the box keeps two lines' worth of height whatever it contains. 2em is
	 * two of THIS element's own font-size — em on min-height resolves against
	 * the element's own type, not its parent's — multiplied by the declared
	 * line-height ratio above. Changing --prail-name-size therefore cannot
	 * desynchronise the reserved height from the real one, which is the whole
	 * reason it is written this way rather than as a flat pixel value. It is
	 * also why the density pass could drop the name to 13px without touching a
	 * single height anywhere in the file.
	 *
	 * --prail-name-leading MUST STAY UNITLESS for this calc to be valid. See
	 * the note on it in section 1.
	 *
	 * If you are reading this because the cards look like they have too much
	 * space under short titles: that space is the feature. Delete this line and
	 * look at a row of twenty real Georgian product names before deciding.
	 */
	min-block-size: calc(var(--prail-name-leading) * 2em);

	/* base.css applies overflow-wrap to p, li, headings and table cells — not
	   to <a>. Georgian compounds and long Latin model numbers both overflow a
	   168px card, and although the clamp hides the overflow, a break is what
	   actually lets the second line be useful. The narrower the card got, the
	   more this one matters. */
	overflow-wrap: break-word;

	transition: color var(--prail-dur) var(--prail-ease);
}

/* =========================================================================
 * 11. THE WISH BUTTON
 *
 * A heart, and only a heart: zoommer's card carries a compare control beside
 * its wishlist one and ours deliberately does not. There is no .bedi-pcard
 * compare class anywhere in the contract and none should be added.
 *
 * IT HAS NO STORAGE AND THIS FILE MUST NOT IMPLY OTHERWISE. bedi-theme carries
 * no wishlist data and is not allowed to invent any — the header solves the
 * same problem through the bedi_theme_wishlist_url / bedi_theme_wishlist_count
 * filters, and the card follows that pattern. What this section provides is the
 * skin for BOTH states of aria-pressed so that the day a wishlist plugin lands
 * and starts toggling the attribute, the filled state is already correct and
 * already measured. Until then the button renders, announces itself properly
 * and does nothing, which is the honest state.
 *
 * Every colour here is a pinned literal, because all of it sits on the one
 * fixed surface in the file. The measurements are in section 1.
 * ====================================================================== */

.bedi-pcard__wish {
	align-items: center;
	background-color: var(--prail-wish-bg);
	block-size: var(--prail-wish-size);
	border: 0;
	border-radius: var(--bedi-radius-round, 50%);
	color: var(--prail-wish-ink);
	display: flex;
	inline-size: var(--prail-wish-size);
	inset-block-start: var(--prail-wish-inset);
	inset-inline-end: var(--prail-wish-inset);
	justify-content: center;
	padding: 0;
	position: absolute;
	z-index: 1;
	transition:
		color var(--prail-dur) var(--prail-ease),
		transform var(--prail-dur) var(--prail-ease);
}

/* 16px in a 32px disc, holding the 1:2 ratio the 18px glyph had in the 36px
   one. The glyph is the control's only visible content, so the ratio is what
   keeps it reading as a heart rather than as a dot or as a filled circle. */
.bedi-pcard__wish svg {
	block-size: 1rem;
	inline-size: 1rem;
}

/*
 * THE PRESSED STATE, AND THE PRESENTATION-ATTRIBUTE TRAP IN IT.
 *
 * The heart is an outline glyph drawn with fill="none" stroke="currentColor" —
 * the same shape the header uses. Those are PRESENTATION ATTRIBUTES, which lose
 * to any author declaration whatever its specificity, so `fill: currentColor`
 * here really does fill the heart. Both the <svg> and the <path> are named: the
 * attribute sits on the <svg> and is inherited by a <path> that has none of its
 * own, but a future icon whose path carries its own fill="none" would ignore a
 * rule that only targeted the parent. Naming both costs nothing and removes the
 * failure mode entirely.
 */
.bedi-pcard__wish[aria-pressed="true"] {
	color: var(--prail-wish-on);
}

.bedi-pcard__wish[aria-pressed="true"] svg,
.bedi-pcard__wish[aria-pressed="true"] path {
	fill: currentColor;
}

/*
 * An INSET ring, which is the one place in this file that overrides base.css's
 * global :focus-visible offset. The button is pinned 8px inside a media box
 * whose own corner is rounded, and an outward ring on a control that close to a
 * clipped corner reads as a clipped ring. The colour is pinned for the reason
 * every colour in this section is pinned: #0668FC measures 4.54:1 on the fixed
 * panel, the dark palette's accent measures 2.17:1 on the same panel and would
 * fail outright.
 */
.bedi-pcard__wish:focus-visible {
	outline: var(--bedi-focus-width, 2px) solid var(--prail-wish-focus);
	outline-offset: calc(var(--bedi-focus-offset, 2px) * -1);
}

@media (hover: hover) {
	.bedi-pcard__wish:hover {
		color: var(--prail-wish-on);
	}
}

.bedi-pcard__wish:active {
	transform: scale(0.92);
}

/* =========================================================================
 * 12. THE ADD-TO-CART BUTTON
 *
 * The markup is WooCommerce's own loop button given our class, so this rule has
 * to work on an <a class="button add_to_cart_button ajax_add_to_cart
 * bedi-pcard__cart"> and must not assume WooCommerce's stylesheet is present.
 * It usually is not: woocommerce.css skins loop buttons through
 * `.woocommerce a.button`, and the homepage has no .woocommerce ancestor, so on
 * this page Woo's own button skin does not apply at all. Everything the button
 * looks like is below.
 *
 * This is the second and last surface in the card that gets a background.
 *
 * WHAT WAS WRONG WITH IT, IN THE CLIENT'S WORDS AND THEN IN NUMBERS. It read as
 * "a flat blue slab" and it dominated a card whose job is to sell a product.
 * Four things were doing that, and only one of them was the width:
 *
 *   40px tall          on a card whose whole text block was about 100px.
 *   weight 700         the same weight as the price, so nothing in the card
 *                      was louder than anything else.
 *   a 6px corner       a barely-rounded rectangle is a panel, not a control.
 *   no depth at all    flat fill, flat edge, and a hover that only changed
 *                      brightness, which is a state you notice after you have
 *                      already decided to click.
 *
 * So it is now 36px, weight 500, a 12px corner, and it has a resting shadow
 * that deepens and lifts under the pointer. THE WIDTH IS UNCHANGED at 100%,
 * deliberately: a full-width button in a 168px card is a normal retail button,
 * and narrowing it would have left the actions row with a ragged edge that no
 * other element in the card has.
 *
 * THE ICON ARRIVES FROM THE TEMPLATE PART, not from a ::before with a data URI.
 * It is a real inline <svg> inserted into Woo's own anchor by a filter that
 * runs for one call and is detached again — that file's section 9 explains why
 * that is the only insertion point Woo leaves open and what it refuses to do
 * when the markup is not the shape it expects. It carries no class of its own,
 * so the selector below is the same shape as the heart's.
 * ====================================================================== */

.bedi-pcard__cart {
	align-items: center;
	background-color: var(--prail-cart-bg);
	border: 0;
	/* Softer than the media box's 6px, and the one place in the card where two
	   radii are correct rather than sloppy — see --prail-cart-radius. */
	border-radius: var(--prail-cart-radius);
	box-shadow: var(--bedi-shadow-1);
	color: var(--prail-cart-ink);
	display: flex;
	font-size: var(--prail-cart-size);
	font-weight: var(--prail-cart-weight);
	/* The space between the glyph and the label. Both are centred as a pair,
	   which is why this is a gap and not a margin on the icon: with a margin,
	   the pair centres around the label and the icon hangs off it. */
	gap: var(--prail-cart-gap);
	inline-size: 100%;
	justify-content: center;
	line-height: var(--bedi-leading-snug, 1.4);
	min-block-size: var(--prail-cart-min-block);
	padding: var(--bedi-space-1) var(--bedi-space-2);
	text-align: center;
	text-decoration: none;
	transition:
		box-shadow var(--prail-dur) var(--prail-ease),
		filter var(--prail-dur) var(--prail-ease),
		transform var(--prail-dur) var(--prail-ease);
}

/*
 * flex: none, because a flex item's default basis is auto and its default
 * shrink is 1: on the narrowest card, with the longest Georgian label, the
 * icon would be the thing that gives way and the cart would render as an
 * ellipse. The label wraps instead, which costs the card a line and costs the
 * ROW nothing — the actions row is bottom-anchored (section 6).
 */
.bedi-pcard__cart svg {
	block-size: var(--prail-cart-icon);
	flex: none;
	inline-size: var(--prail-cart-icon);
}

/*
 * base.css's `:root :where(a:hover)` is (0,1,0) and would repaint this label
 * with the accent on hover — on a blue fill, which is the "1.9:1 text nobody
 * notices until a customer cannot find add to cart" failure base.css itself
 * warns about. Restated at all three states to take it back.
 */
.bedi-pcard__cart:hover,
.bedi-pcard__cart:active,
.bedi-pcard__cart:focus-visible {
	color: var(--prail-cart-ink);
}

/*
 * DARKEN, NEVER BRIGHTEN, AND IT IS A CONTRAST DECISION RATHER THAN A TASTE
 * ONE. In the light scheme the fill is #0668FC carrying white at 4.78:1;
 * brightening it walks that number down through the 4.5:1 line. Reducing
 * luminance only widens the gap. In dark the pair inverts — dark ink on the
 * light blue #8FD2F5 — and a 6% darkening there costs a couple of points out of
 * a ratio in the double digits. One direction is safe in both schemes.
 */
@media (hover: hover) {
	.bedi-pcard__cart:hover {
		box-shadow: var(--bedi-shadow-2);
		filter: brightness(0.94);
		transform: translateY(-1px);
	}
}

/*
 * The press, and it is the shadow that does the work rather than the scale.
 * scale(0.99) on a 168px button is under two pixels of movement and reads as a
 * rendering artefact; dropping the lift and removing the shadow puts the button
 * back on the page, which is what being pressed looks like. Both shadow tokens
 * are base.css's, so the whole gesture costs the file no new colour.
 */
.bedi-pcard__cart:active {
	box-shadow: none;
	filter: brightness(0.9);
	transform: translateY(0);
}

/*
 * The in-flight state. WooCommerce's AJAX handler adds .loading to the button
 * and .added when it succeeds, and paints its own spinner into ::after from the
 * WooCommerce icon font — a font this page does not load. So the pending state
 * is expressed with opacity and a dead pointer instead of with a glyph that
 * would render as a tofu box. Deliberately no `cursor: wait`: the button is
 * still focusable and a cursor is not a state a keyboard user can see.
 */
.bedi-pcard__cart.loading {
	opacity: 0.65;
	pointer-events: none;
}

/*
 * THE CONFIRMATION HAPPENS IN THE BUTTON, exactly as it does on the product
 * page — the client asked for this twice, and the catalogue tile was the half
 * that still did it the old way.
 *
 * WooCommerce's own AJAX handler gives us both states for free: it adds
 * `.loading` when the request goes out and `.added` when it comes back. No
 * JavaScript is needed to produce the look; assets/js/toast.js only has to take
 * `.added` back off again, because WooCommerce never does (it assumes the
 * button is permanently "added" from then on, which is why a tile pressed once
 * used to stay in that state until the page was reloaded).
 *
 * The leading cart glyph is hidden and a check is drawn in its place, so the
 * button's width does not change and the row of tiles does not reflow.
 */
/*
 * (0,5,2). `.bedi-pcard__cart.added` on its own is (0,2,0) and loses to
 * section-catalog.css's `.woocommerce ul.products li.product.bedi-pcell
 * .bedi-pcard__cart` at (0,4,2) — which is why the first attempt at this left
 * the button accent-blue while correctly adding and removing the class. The
 * bare-class selector is kept alongside for any surface that renders a card
 * outside `ul.products`.
 */
.woocommerce ul.products li.product.bedi-pcell .bedi-pcard__cart.added,
.woocommerce-page ul.products li.product.bedi-pcell .bedi-pcard__cart.added,
.bedi-pcard__cart.added {
	background-color: var(--bedi-ok);
	border-color: var(--bedi-ok);
}

/* Hover and focus must not repaint a button that is mid-confirmation. */
.woocommerce ul.products li.product.bedi-pcell .bedi-pcard__cart.added:hover,
.woocommerce ul.products li.product.bedi-pcell .bedi-pcard__cart.added:focus-visible,
.woocommerce-page ul.products li.product.bedi-pcell .bedi-pcard__cart.added:hover,
.woocommerce-page ul.products li.product.bedi-pcell .bedi-pcard__cart.added:focus-visible {
	background-color: var(--bedi-ok);
	border-color: var(--bedi-ok);
}

.bedi-pcard__cart.added svg {
	display: none;
}

/*
 * WOOCOMMERCE DRAWS ITS OWN GLYPH IN ::after AND IT HAS TO GO.
 *
 * woocommerce-general.css puts `content: "\e01c"` (a spinner) on
 * `a.button.loading::after` and `content: "\e017"` (a tick) on
 * `a.button.added::after`, both set in the `WooCommerce` icon font. With our
 * own check in ::before that is a SECOND tick on the same button — which is
 * what it looked like: "✓ კალათაში ✓".
 *
 * An older comment in this file claimed the font was not loaded and the glyph
 * would render as tofu. That was wrong: woocommerce-general IS enqueued on the
 * archive (it is only dequeued on the checkout, by Bedi\Theme\Checkout), the
 * font loads with it, and the glyph renders perfectly.
 *
 * (0,5,3) to beat core's `.woocommerce a.button.added::after`.
 */
.woocommerce ul.products li.product.bedi-pcell .bedi-pcard__cart::after,
.woocommerce-page ul.products li.product.bedi-pcell .bedi-pcard__cart::after,
.bedi-pcard__cart::after {
	content: none;
	display: none;
}

.bedi-pcard__cart.added::before {
	/*
	 * A masked data-URI rather than a border-and-rotate check: the mask takes
	 * `currentColor`, so it stays legible if the success colour is ever
	 * retuned, and it is the same stroke weight as the icon it replaces.
	 */
	background-color: currentColor;
	block-size: 18px;
	content: "";
	display: inline-block;
	flex: 0 0 auto;
	inline-size: 18px;
	-webkit-mask: var(--bedi-check-mask) center / contain no-repeat;
	mask: var(--bedi-check-mask) center / contain no-repeat;
}

/*
 * THE "VIEW CART" LINK IS REMOVED, NOT STYLED.
 *
 * WooCommerce's wc-add-to-cart.js appends
 * `<a class="added_to_cart wc-forward">View cart</a>` as a SIBLING of the
 * button after every successful add. This file used to give it a colour and a
 * text-decoration, which made it look intentional — it is the exact element
 * the client asked twice to be rid of.
 *
 * toast.js removes the node on the `added_to_cart` event. This rule is the
 * anti-flash guard: the anchor is inserted synchronously by WooCommerce's
 * handler and removed on the event it fires immediately afterwards, so without
 * a CSS rule it would paint for a frame or two on a slow device.
 *
 * SCOPED TO `.bedi-js` — written on the root by nav.js/header.js — so a
 * visitor whose JavaScript never runs still gets the link, because for them the
 * non-AJAX add is a full page load and that anchor is the only thing telling
 * them where their cart went.
 *
 * (0,5,2), which beats woo.css's (0,4,2) `display: inline-flex` outright rather
 * than on source order.
 */
.bedi-js .woocommerce ul.products li.product .added_to_cart,
.bedi-js .woocommerce-page ul.products li.product .added_to_cart,
.bedi-js .bedi-pcard__actions .added_to_cart {
	display: none;
}

/*
 * The rule that used to dress the injected "View cart" link lived here. It is
 * gone: the link is hidden under `.bedi-js` above and removed from the DOM by
 * toast.js, because the confirmation now happens inside the button.
 */

/* =========================================================================
 * 13. CARD STATES
 *
 * base.css sets `:root :where(a:hover) { color: var(--bedi-accent-strong) }` at
 * (0,1,0). Every (0,1,0) selector in this file ties with it and wins on source
 * order, because bedi-section-products is printed after bedi-base — but only if
 * the state is actually restated. Anything left unstated goes to the token.
 *
 * Colour is NOT gated on (hover: hover), following the same call the cards file
 * makes: a colour change is harmless on a touch screen, and leaving it out
 * would let the base rule paint a tapped product name a colour this component
 * never chose. MOVEMENT is gated, because :hover sticks after a tap and would
 * leave one card's picture permanently zoomed.
 *
 * THE HOVER IS THE WHOLE CARD, NOT THE ELEMENT UNDER THE POINTER, and on a card
 * with no wrapper that is the only way it can be: there is no box to tint, no
 * border to darken and no shadow to raise, so the response has to come from the
 * two things the card does own — the picture and the name. Pointing at the
 * price, the meta line or the empty space beside them lights both, because both
 * selectors hang off .bedi-pcard:hover rather than off the elements themselves.
 * Most of the "finished" feeling the client was asking for is in that detail and
 * in the focus rings at the bottom of this section, and neither costs a byte of
 * markup.
 * ====================================================================== */

.bedi-pcard__name:hover,
.bedi-pcard__name:active,
.bedi-pcard__name:focus-visible,
.bedi-pcard:hover .bedi-pcard__name {
	color: var(--prail-link);
}

/*
 * The picture lifts very slightly under the pointer. 1.03 and not more: the
 * media box clips at the corner radius, so a larger scale visibly crops the
 * edges of a product shot that was framed to fit.
 */
@media (hover: hover) {
	.bedi-pcard:hover .bedi-pcard__media img {
		transform: scale(1.03);
	}
}

/*
 * Keyboard parity, done with :focus-within on the card rather than with a
 * sibling combinator off the name. The name comes AFTER the media box in the
 * document, so there is no combinator that reaches back up to the picture from
 * it — `~` only ever selects forwards. :focus-within also covers the card's
 * other two tab stops, the heart and the add-to-cart button, which is the
 * behaviour we want anyway: the whole card responds while a keyboard user is
 * anywhere inside it. The picture link is excluded from all of this by being
 * tabindex="-1", which is the point of that attribute.
 *
 * The focus RING itself comes from base.css's global :focus-visible; only the
 * zoom has to be restated here.
 */
.bedi-pcard:focus-within .bedi-pcard__media img {
	transform: scale(1.03);
}

/*
 * THE FOCUS RINGS, RESTATED FOR THE CARD'S TWO TAB STOPS.
 *
 * base.css already draws exactly this ring on every :focus-visible in the
 * theme, so this rule changes nothing today and is not here to. It is here
 * because a wrapper-less card gives a keyboard user NOTHING else to go on: no
 * panel to highlight, no border to thicken, no background to invert. The ring
 * is the entire affordance, on the one control that adds to the cart and the
 * one link that reaches the product, and a future reset that quietly drops the
 * global rule must not be able to take it with it. That is worth four
 * declarations and this paragraph.
 *
 * THE OFFSET IS LOAD-BEARING ON THE BUTTON. Its fill is #0668FC and the ring is
 * #2D81FF; drawn flush they are two blues touching and the ring disappears into
 * the button. The 2px offset puts a gap of the card's own background between
 * them, and the ring is then measured against THAT: 3.69:1 on white and 8.2:1
 * on the dark canvas, against the 3:1 a non-text indicator needs. This is the
 * opposite call from the heart in section 11, whose ring is inset — that
 * control is pinned inside a rounded, clipped media box, where an outward ring
 * reads as a clipped one. Same reasoning, different geometry.
 *
 * Neither ring is clipped by the scroller: section 4's 4px of block padding is
 * exactly --bedi-focus-width + --bedi-focus-offset, and it was put there for
 * this button.
 */
.bedi-pcard__name:focus-visible,
.bedi-pcard__cart:focus-visible {
	outline: var(--bedi-focus-width, 2px) solid var(--bedi-accent);
	outline-offset: var(--bedi-focus-offset, 2px);
}

/* =========================================================================
 * 14. THE NAV ARROWS
 *
 * PURE PROGRESSIVE ENHANCEMENT. The buttons ship with the `hidden` attribute in
 * the markup and assets/js/products.js removes it, and only when the track
 * actually overflows. A visitor whose JavaScript never arrives gets a rail that
 * still scrolls with a trackpad, a wheel, a flick and the keyboard, rather than
 * two arrows that do nothing — which is the entire reason the visibility is
 * done that way round.
 *
 * THIS RULE IS WHY THAT WORKS, AND IT IS THE LINE MOST LIKELY TO BE DELETED BY
 * SOMEONE TIDYING UP. An author `display` beats the UA stylesheet's
 * `[hidden] { display: none }` no matter how weak the selector is — author
 * origin always wins over UA origin — so the moment this rule gives the button
 * `display: flex`, the `hidden` attribute stops hiding it and the no-JS visitor
 * gets exactly the dead arrows the attribute was there to prevent. The
 * [hidden] rule below restores it.
 * ====================================================================== */

.bedi-prail__nav {
	align-items: center;
	background-color: var(--prail-nav-bg);
	block-size: var(--prail-nav-size);
	border: var(--bedi-border-width, 1px) solid var(--prail-rule);
	border-radius: var(--bedi-radius-round, 50%);
	box-shadow: var(--prail-nav-shadow);
	color: var(--prail-nav-ink);
	display: flex;
	inline-size: var(--prail-nav-size);
	inset-block-start: var(--prail-nav-inset-block);
	justify-content: center;
	padding: 0;
	position: absolute;
	/* Centred on --prail-nav-inset-block rather than hanging from it. */
	transform: translateY(-50%);
	transition:
		filter var(--prail-dur) var(--prail-ease),
		opacity var(--prail-dur) var(--prail-ease);
	/*
	 * A local 1, and deliberately NOT a rung of base.css's shared z ladder. The
	 * arrow only has to beat its own siblings inside .bedi-prail__inner; taking
	 * --bedi-z-rail or anything above it would enter the page-wide stack and put
	 * a homepage arrow over the header's dropdowns. This is the mistake
	 * documented at --bedi-z-rail in base.css, not repeated here.
	 */
	z-index: 1;
}

/* Restores the UA behaviour the display above destroys. Do not remove. */
.bedi-prail__nav[hidden] {
	display: none;
}

.bedi-prail__nav svg {
	block-size: 1.25rem;
	inline-size: 1.25rem;
}

/*
 * The arrows straddle the content edge, half in the gutter and half over the
 * first and last card — the standard rail affordance, and the reason the
 * viewport carries scroll-padding-inline in section 4 so a snapped card never
 * comes to rest underneath one.
 *
 * max(0px, …) because the gutter is 20px on a phone and the arrow is 40px: the
 * calculation goes negative below 48em and would hang the button off the edge
 * of the section. Clamped, it simply sits flush.
 */
.bedi-prail__nav--prev {
	inset-inline-start: max(0px, calc(var(--prail-pad-inline) - (var(--prail-nav-size) / 2)));
}

.bedi-prail__nav--next {
	inset-inline-end: max(0px, calc(var(--prail-pad-inline) - (var(--prail-nav-size) / 2)));
}

@media (hover: hover) {
	.bedi-prail__nav:hover {
		filter: brightness(0.97);
	}
}

/*
 * BOTH DISABLED SPELLINGS, because the JavaScript may reasonably use either and
 * this file should not dictate which. `disabled` removes the button from the
 * tab order, which is right when the arrow is decorative; `aria-disabled` keeps
 * it focusable, which is right when a keyboard user should still be able to
 * find it and be told it does nothing. Skinning both means the choice can be
 * made in products.js without a CSS change.
 *
 * pointer-events is NOT set here. On an [aria-disabled] button the click must
 * still reach the handler for it to be ignored deliberately; killing the event
 * in CSS turns an announced no-op into a dead region.
 */
.bedi-prail__nav[disabled],
.bedi-prail__nav[aria-disabled="true"] {
	box-shadow: none;
	cursor: default;
	opacity: 0.35;
}

/*
 * NO ARROWS WHERE THERE IS NO POINTER TO CLICK THEM WITH.
 *
 * On a phone the gutter is 20px and the arrow is 40px, so the max(0px, …)
 * clamp above puts it flush with the section edge and it comes to rest on top
 * of the first and last product's picture. That is a real cost, and on a touch
 * screen it buys nothing: the flick is the interaction, and the peeking card
 * section 15 arranges is the affordance that advertises it.
 *
 * THIS DOES NOT FIGHT products.js. That script's job is to REMOVE the `hidden`
 * attribute when the track overflows; it never sets `display`. So on a touch
 * device it still runs, still does its measuring, and this rule simply means
 * nobody sees the result. Nothing has to be co-ordinated between the two files.
 *
 * Both halves of the query are required. `pointer: coarse` alone catches a
 * touchscreen laptop whose primary input is still a trackpad, and `hover: none`
 * alone catches a stylus. A device that has neither a hover state nor a fine
 * pointer is a phone or a tablet, which is exactly the set we want.
 */
@media (hover: none) and (pointer: coarse) {
	.bedi-prail__nav {
		display: none;
	}
}

/* =========================================================================
 * 15. PHONES — THE PEEK
 *
 * The only place the item width leaves the formula in section 5, and the reason
 * is that the formula's minimum is wrong at this size — now more so than before
 * the density pass, not less. On a 375px screen the 168px minimum would fit one
 * whole card and most of a second, which reads as a two-column grid that got cut
 * off rather than as an invitation to scroll sideways.
 *
 * So the phone gets a card sized to show exactly one full card plus a fifth of
 * the next: card = (row - gap) / 1.2 puts 20% of a card in view on the end
 * edge, which is unmistakably a peek. At 375px that is a 269px card and a 54px
 * sliver.
 *
 * 30em is 480px. Above it the formula's own steps take over, and the first one
 * it reaches is two cards up.
 * ====================================================================== */

@media (max-width: 47.9375em) {
	.bedi-prail {
		--prail-gap: var(--bedi-space-3);
	}

	/*
	 * 2.3 CARDS, NOT 1.2.
	 *
	 * One and a bit cards is a gallery; two and a bit is a shop. At 375px this
	 * is a 146px card — enough for the photograph, the price and a readable
	 * name — and the third card breaking the right edge is what tells you the
	 * row scrolls without needing an arrow to say so.
	 */
	/*
	 * TUNED AGAINST THE MEASUREMENT, NOT THE ARITHMETIC.
	 *
	 * "2.6 cards" means 2.6 of them VISIBLE in the 375px viewport, and the
	 * divisor here divides the 335px CONTENT box — the viewport less its two
	 * gutters — so the two numbers are not the same. Dividing by 2.6 gave a
	 * 121px card, which lays out as 2.73 visible once the gutter and the gaps
	 * are counted. 2.5 gives 127px, which measures 2.61.
	 */
	.bedi-prail__item {
		flex: 0 0 calc((100% - var(--prail-gap) * 1.5) / 2.5);
	}

	/*
	 * AND THE ROW SCROLLS EDGE TO EDGE.
	 *
	 * The scroller sat inside the page gutter, so a card slid under a 20px
	 * margin and vanished at a hard line twenty pixels short of the screen —
	 * which reads as a clipped layout rather than as content continuing past
	 * the edge.
	 *
	 * Negative margin pulls the SCROLLER out to the full width; equal padding
	 * puts the CONTENT back where it was. So the first card still lines up
	 * with the heading above it and with everything else on the page, and the
	 * last one runs off the real edge of the screen.
	 *
	 * scroll-padding-inline-start matches, or a snapped card would come to
	 * rest against the viewport edge instead of against the gutter it is
	 * supposed to align to.
	 */
	.bedi-prail__viewport {
		margin-inline: calc(var(--bedi-gutter) * -1);
		padding-inline: var(--bedi-gutter);
		scroll-padding-inline-start: var(--bedi-gutter);
	}
}

/* =========================================================================
 * 16. 768px AND UP
 *
 * The one type step in the file. Everything else that changes with the viewport
 * changes on its own: --bedi-gutter steps 20px -> 40px in base.css and the band
 * and the arrows follow it, and the card width is a formula rather than a set
 * of breakpoints.
 *
 * 48em is 768px, the breakpoint base.css and every other section in this theme
 * already use.
 * ====================================================================== */

@media (min-width: 48em) {
	.bedi-prail {
		--prail-title-size: var(--bedi-text-2xl, 1.5rem);
	}
}

/* =========================================================================
 * 17. REDUCED MOTION
 *
 * base.css clamps every duration in the theme, which handles the transitions.
 * Two things a duration clamp cannot reach: a resting transform, and a scroll
 * behaviour. Both are undone here.
 *
 * THE SMOOTH SCROLL IS THE IMPORTANT ONE. base.css's global block already sets
 * scroll-behavior: auto with !important on every element, so this is belt and
 * braces — but a rail that animates a full viewport sideways is exactly the
 * vestibular trigger the setting exists for, and it is worth one explicit
 * declaration at the component level rather than trusting a wildcard rule three
 * files away to keep covering it.
 * ====================================================================== */

@media (prefers-reduced-motion: reduce) {
	.bedi-prail__viewport {
		scroll-behavior: auto;
	}

	/*
	 * Every resting transform in the file, and the list grew with the button
	 * pass: the cart button now LIFTS on hover as well as pressing on click, and
	 * a hover lift is exactly the kind of small unrequested movement this
	 * setting exists to stop. The shadow change under it is left alone — a
	 * shadow is not motion, and removing it would leave the button with no hover
	 * response at all for a visitor who asked for less movement, not none.
	 */
	.bedi-pcard:hover .bedi-pcard__media img,
	.bedi-pcard:focus-within .bedi-pcard__media img,
	.bedi-pcard__wish:active,
	.bedi-pcard__cart:hover,
	.bedi-pcard__cart:active {
		transform: none;
	}
}

/* =========================================================================
 * 18. PRINT
 *
 * A printed homepage is a list of product names and prices. The blue buttons,
 * the arrows and the heart are ink nobody asked for, and a horizontally
 * scrolling rail on paper shows five products out of twenty.
 *
 * So the rail becomes a wrapping grid: overflow visible, the track wraps, and
 * every card in the row prints. The media box is dropped entirely — every image
 * on this page is currently a placeholder, and a page of grey squares is the
 * worst possible use of a toner cartridge.
 * ====================================================================== */

@media print {
	.bedi-prail {
		--prail-ink: var(--bedi-print-ink, #000);
		--prail-ink-muted: var(--bedi-print-ink, #000);
		--prail-link: var(--bedi-print-ink, #000);
		--prail-rule: var(--bedi-print-ink, #000);
		/* The sale price goes black with everything else. A red price is the one
		   ink in this section a colour printer would actually spend, and on paper
		   the struck former price beside it is what carries the discount. */
		--prail-sale: var(--bedi-print-ink, #000);
		padding-block: var(--bedi-space-6);
	}

	.bedi-prail__viewport {
		overflow: visible;
		padding-block: 0;
	}

	.bedi-prail__track {
		flex-wrap: wrap;
	}

	.bedi-prail__item {
		flex: 0 0 auto;
		inline-size: 33%;
	}

	/*
	 * THE SALE BADGE GOES WITH THE MEDIA BOX, because it is a child of it in
	 * the frozen markup and there is nowhere else for it to be. That is an
	 * acceptable loss rather than an oversight: a discount is still legible on
	 * paper from the struck former price sitting next to the current one, which
	 * is the pair that carries the actual numbers. It is stated here so nobody
	 * adds an outlined-pill print rule for .bedi-pcard__badge and then spends
	 * an afternoon working out why it never fires.
	 */
	.bedi-pcard__media,
	.bedi-pcard__wish,
	.bedi-prail__nav {
		display: none;
	}

	/* The filled button prints as an outlined one: the label survives, the
	   flood of ink does not. */
	.bedi-pcard__cart {
		background-color: transparent;
		border: var(--bedi-border-width, 1px) solid var(--bedi-print-ink, #000);
		box-shadow: none;
		color: var(--bedi-print-ink, #000);
	}
}
