/* cdob-catalogue.css | v0.2.1 | 2026-08-03 | Generated by Claude, session CDOB-WOO-S18
   (v0.2.0 2026-08-02 CDOB-WOO-S15; v0.1.0 2026-08-02 CDOB-WOO-S11)

   v0.2.1 (CDOB-WOO-S18): image-overlap fix. The product-card image face
   (.cdob-catalogue__media) set a square via aspect-ratio but never clipped to
   it and left the <img> in normal flow, so once real featured images were
   attached (CDOB-WOO-S17) a thumbnail painted past the square and overlapped
   the card title; the card <li> overflow bounds the card outline, not this
   inner box. The box now sets overflow:hidden and the image is absolutely
   positioned to fill it (object-fit:cover then centres the crop). This aligns
   the tile with the theme's own image tiles, .cdob-storecard__imgwrap and
   .cdob-postcard__imgwrap, which both pair the aspect-ratio box with
   overflow:hidden. CSS-only; no colour pair introduced or changed, so the
   contrast ledger below stands.

   v0.2.0 (CDOB-WOO-S15, owner-directed): the catalogue grid is THREE cards
   wide, not two, and it is released from the reading measure that was
   silently capping it. Root cause of the two-wide render, read out of the
   cascade rather than inferred: cdob-core.css sets
   `.cdob-content .cdob-prose { max-width: 70ch }` (about 700px), the wrap
   renders inside .cdob-prose, and auto-fill with a 230px minimum needs
   3*230 + 2*24 = 738px to seat a third column, so it seated two. page.php
   v0.6.0 now emits a .cdob-prose--store modifier on the store pages and the
   rule below lifts the cap; the grid is then pinned to exactly three columns
   rather than left to auto-fill, because "three across" is the owner's
   requirement and not a happy consequence of some minimum width.

   W6 (catalogue half): the product-card grid for [cdob_catalogue]
   (includes/cdob-catalog.php v0.2.0), on the wholesale and supporter store
   pages. Enqueued by the cdob-starter loader when the shortcode is present;
   versioned by CDOB_STARTER_PLUGIN_VERSION.

   SPECIFICITY CONTRACT (lesson 148; a11y review S11 sections 3a/3b). The
   surface renders inside the theme's .cdob-prose container, whose
   element-level descendant rules (.cdob-prose h3, .cdob-prose p,
   .cdob-prose ul, .cdob-prose li, all (0,1,1)) outrank a bare component
   class (0,1,0), and .cdob-prose ul TIES ul.cdob-catalogue (0,1,1) with
   the winner decided by print order, which is non-deterministic across
   plugin/theme enqueue registration. So the renderer wraps everything in
   .cdob-catalogue-wrap and EVERY rule here compounds on that wrapper:
   (0,2,0)+ beats every (0,1,1) prose rule in every state, load order
   irrelevant. Do not add a bare .cdob-catalogue* rule to this file.

   COLOR SOURCES AND CONTRAST. Every color references the theme token layer
   (cdob-core.css :root); this sheet introduces NO new color pair. Pairs
   painted here, ratios re-derived by the S11 a11y audit's independent
   sRGB linearization (not copied from comments):

     ink   #1f1f1f on paper #ffffff   16.49:1  price, body        PASS 4.5
     navy  #2c3c77 on paper           10.39:1  titles, links      PASS 4.5
     meta  #555555 on paper            7.46:1  SKU, count, out    PASS 4.5
     fir   #1e4d2b on paper            9.76:1  in-stock line      PASS 4.5
     ink   on flame #f0523d            4.69:1  .cdob-btn CTA      PASS 4.5 (theme-owned)

   HOVER STATES ARE PINNED TO NAVY with an underline signal on every text
   link this sheet owns: the theme's global a:hover paints flame, and
   flame-on-paper is 3.51:1, a WCAG 1.4.3 FAIL for normal text (a11y
   review S11 section 3c). Never let a link here fall through to the
   global hover.

   Decorative-only values, carrying no text and no state (the admin
   sheet's hairline posture): the card hairline var(--cdob-ui-border)
   (0.18-alpha ink on paper, 1.44:1; cards are also separated by the grid
   gap) and the empty-media wash rgba(var(--cdob-ink-rgb), 0.05), a
   placeholder surface with no content on it. Neither may ever carry
   state.

   STATE IS NEVER COLOR ALONE (WCAG 1.4.1): stock prints as words; the
   out-of-stock state is a different WORD. The add-to-cart CTA rides the
   theme .cdob-btn (44px tap floor); sale prices keep the native del
   strikethrough. Focus: paper surface, theme base :focus-visible (navy)
   applies; no dark context introduced (lesson 217 checked). */

/* Head bar: count left, the wholesale manage-account link right. */
.cdob-catalogue-wrap .cdob-catalogue-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px 24px;
  margin: 0 0 24px;
}
.cdob-catalogue-wrap .cdob-catalogue-count {
  margin: 0;
  font-size: var(--fs-400);
  color: var(--cdob-on-light-meta); /* 7.46:1 on paper */
}
.cdob-catalogue-wrap .cdob-catalogue-manage {
  display: inline-block;
  padding: 10px 0; /* comfortable target height for a standalone link */
  font-size: var(--fs-400);
  font-weight: 600;
  color: var(--cdob-navy); /* 10.39:1 on paper */
}
.cdob-catalogue-wrap .cdob-catalogue-manage:hover {
  color: var(--cdob-navy); /* hold 10.39:1; never the 3.51:1 global flame */
  text-decoration: underline;
}

/* Release the catalogue from the prose reading measure. A product grid is not
   prose, and cdob-core.css's `.cdob-content .cdob-prose { max-width: 70ch }`
   (0,2,0) was capping the store pages at about 700px, which is what held the
   grid to two columns even though page.php v0.5.0 had already given these
   pages full-width chrome. page.php v0.6.0 emits .cdob-prose--store on the
   shop and both catalogue pages; this rule is (0,3,0), so it beats the core
   cap deterministically without depending on enqueue order. */
.cdob-content .cdob-prose.cdob-prose--store {
  max-width: none;
}

/* The grid: exactly THREE cards across (owner-directed, v0.2.0), dropping to
   two on tablets and staying at two on phones. minmax(0, 1fr) rather than a
   px minimum so an unbreakable token can never force horizontal overflow
   (1.4.10 robustness, a11y review S11 note 6; the phone rule below inherits
   this guard rather than restating it).
   Margin and padding zeroed at (0,2,1), beating .cdob-prose ul's indent. */
.cdob-catalogue-wrap ul.cdob-catalogue {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 28px 24px;
}

@media (max-width: 900px) {
  .cdob-catalogue-wrap ul.cdob-catalogue {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* One product card. Paper surface + decorative hairline; whitespace and
   heading structure do the real separation. margin:0 beats .cdob-prose li. */
.cdob-catalogue-wrap .cdob-catalogue__item {
  display: flex;
  flex-direction: column;
  margin: 0;
  background: var(--cdob-paper);
  border: 1px solid var(--cdob-ui-border); /* decorative, no state */
  border-radius: 6px;
  overflow: hidden;
}

/* Image face. Square, cover-fit; the anchor is aria-hidden + tabindex=-1
   in the markup so the card exposes exactly one accessible link (the
   title). Empty state is a wash block with no content. */
.cdob-catalogue-wrap .cdob-catalogue__media {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden; /* CLIP to the square. Without this the featured image painted past the box onto the card title; the <li>'s overflow bounds the card, not this inner box. The theme's own tiles (.cdob-storecard__imgwrap / .cdob-postcard__imgwrap) carry the same guard. */
  background: rgba(var(--cdob-ink-rgb), 0.05); /* decorative wash */
}
.cdob-catalogue-wrap .cdob-catalogue__media img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover; /* absolutely filling the aspect-ratio box makes height:100% resolve against a definite height, so the crop centres in the flex-item context */
}
.cdob-catalogue-wrap .cdob-catalogue__noimg {
  display: block;
  width: 100%;
  height: 100%;
}

.cdob-catalogue-wrap .cdob-catalogue__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 16px 18px;
  flex: 1 1 auto;
}

/* Card title: h3 in markup, so this must beat .cdob-prose h3 (0,1,1) on
   BOTH font-size and margin or the card renders 20px/30px-margined. */
.cdob-catalogue-wrap .cdob-catalogue__name {
  margin: 0;
  font-family: var(--cdob-ff-serif);
  font-size: var(--fs-500);
  line-height: 1.3;
  font-weight: 700;
}
.cdob-catalogue-wrap .cdob-catalogue__name a {
  color: var(--cdob-navy); /* 10.39:1 on paper */
  text-decoration: none;
}
.cdob-catalogue-wrap .cdob-catalogue__name a:hover {
  color: var(--cdob-navy); /* hold 10.39:1; hover signal is the underline */
  text-decoration: underline;
}

.cdob-catalogue-wrap .cdob-catalogue__sku {
  margin: 0;
  font-size: var(--fs-300);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cdob-on-light-meta); /* 7.46:1 on paper */
}

.cdob-catalogue-wrap .cdob-catalogue__price {
  margin: 0;
  font-size: var(--fs-500);
  font-weight: 700;
  color: var(--cdob-ink); /* 16.49:1 on paper */
}
/* WooCommerce sale markup inside get_price_html(): the struck original
   price drops to meta weight; the current price keeps ink. The native
   strikethrough carries the state, not color alone. */
.cdob-catalogue-wrap .cdob-catalogue__price del {
  color: var(--cdob-on-light-meta); /* 7.46:1 on paper */
  font-weight: 400;
}
.cdob-catalogue-wrap .cdob-catalogue__price ins {
  text-decoration: none;
}

.cdob-catalogue-wrap .cdob-catalogue__stock {
  margin: 0;
  font-size: var(--fs-400);
  font-weight: 600;
  color: var(--cdob-fir); /* 9.76:1 on paper */
}
.cdob-catalogue-wrap .cdob-catalogue__stock.is-out {
  color: var(--cdob-on-light-meta); /* 7.46:1; the WORD changes too */
  font-weight: 400;
}

/* CTA: rides the theme .cdob-btn (flame bg + ink text 4.69:1, 44px tap
   floor via min-height, which this sheet never redeclares). This rule
   only sizes and places it. */
.cdob-catalogue-wrap .cdob-catalogue__add {
  margin-top: auto;
  align-self: flex-start;
  font-size: var(--fs-400);
  padding: 10px 18px;
}

@media (max-width: 480px) {
  .cdob-catalogue-wrap ul.cdob-catalogue {
    /* Column count is already two from the 900px rule; this block only
       tightens the gutters. The minmax(0,1fr) overflow guard now lives on the
       base rule, which every breakpoint inherits. */
    gap: 20px 14px;
  }
  .cdob-catalogue-wrap .cdob-catalogue__body {
    padding: 12px 12px 14px;
  }
}
