/*
 * Products Overview (Phase B1) — search/filter controls, the dynamic product grid and its
 * empty state. Category tiles reuse the existing .category-grid/.category-card patterns
 * from product-pages.css; this file only adds what is new to the overview page.
 */

/* Phase B3 fix: .catalogue-card{display:flex} and .catalogue-grid{display:grid} below
   share the browser default [hidden]{display:none} rule's specificity (0,1,0); as author
   rules loaded after the UA stylesheet they were winning the cascade, so filtered-out
   cards and the empty grid stayed visually displayed even though `hidden` was set on them
   (search/filter/subcategory results were computed correctly but never actually hidden on
   screen). This also covers the category-page template (B2), which reuses these same
   classes. Making the hidden state explicit here is what actually hides them. */
[hidden]{
display:none !important
}

.discovery{
background:#fff
}

.catalogue-filters{
display:grid;
grid-template-columns:1.4fr 1fr 1fr auto;
align-items:end;
gap:16px;
margin-bottom:var(--space-4)
}

.field{
display:flex;
flex-direction:column;
gap:8px
}

.field label{
color:var(--color-muted);
font:500 13px/1.35 var(--font-sans);
letter-spacing:.01em;
text-transform:uppercase
}

.filter-input,
.filter-select{
height:48px;
padding:0 16px;
border:1px solid var(--color-line);
border-radius:var(--radius-sm);
background:var(--color-paper);
color:var(--color-text);
font:var(--text-base) var(--font-sans)
}

.filter-input:focus-visible,
.filter-select:focus-visible{
outline:3px solid var(--color-teal-dark);
outline-offset:2px
}

.clear-filters{
height:48px;
padding:0 4px;
border:0;
background:transparent;
color:var(--color-primary);
white-space:nowrap
}

.results-count{
margin-bottom:var(--space-3);
color:var(--color-muted);
font:500 var(--text-sm) var(--font-mono)
}

.catalogue-grid{
display:grid;
grid-template-columns:repeat(auto-fill,minmax(270px,1fr));
align-items:stretch;
gap:14px
}

/* height:100% is the explicit companion to .catalogue-grid's align-items:stretch above
   (already the grid default, stated here so both halves of "cards fill their row" are
   visible in one place): every card in a row already gets stretched to that row's own
   height by the grid; height:100% is what lets .catalogue-card-body's flex:1 below actually
   have a definite height to grow into, rather than each card's box merely sizing to its own
   unstretched content. */
/* background is hardcoded rather than var(--color-mist) as of the flagship-tint swap
   below: --color-mist is used across most of the rest of the site (about/contact/support
   pages, layout.css, etc.), so the token itself stays untouched — only this rule and
   .catalogue-filters move to the flat-color equivalent of rgba(91,170,170,.06) over white
   (the tint .catalogue-card--flagship used to carry), computed precisely as rgb(245,250,250). */
.catalogue-card{
position:relative;
display:flex;
flex-direction:column;
height:100%;
overflow:hidden;
background:rgb(245,250,250);
border:1px solid transparent;
border-radius:var(--radius-lg);
transition:border-color .22s ease,transform .22s ease,box-shadow .22s ease
}

/* Flagship accent — border/shadow only now; no dimension change (box-sizing:border-box
   means the 2px border doesn't shift the card's outer size). Background is var(--color-mist)
   — the token .catalogue-card/.catalogue-filters used to share before their swap to the
   flat teal-tint colour above; the flagship card recovers exactly what every other card and
   the filter panel used to look like, so the accent now reads via border/shadow contrast
   against its neighbours instead of via its own background tint. */
.catalogue-card--flagship{
border:2px solid var(--color-teal-dark);
background:var(--color-mist);
box-shadow:0 8px 20px rgba(91,170,170,.18)
}

/* Scoped to the flagship card only — the base .catalogue-card-media rule (its own block,
   above) stays untouched for every other card. */
.catalogue-card--flagship .catalogue-card-media{
border:1px solid var(--color-teal-dark)
}

/* Hover/keyboard-focus parity: reference effect supplied 2026-09-12 (external site,
   inspected manually, not part of this repo). border-color/transform/box-shadow reuse
   existing tokens (--color-teal, --color-primary via color-mix, same color-mix precedent
   as css/product-detail.css's product-subnav hover); translateY(-3px) and the .22s
   duration are the reference's own literal values, with no equivalent design token.
   .catalogue-card is an <article>, not itself focusable — its "View product" link
   (.catalogue-card-cta below) is the actual focusable element, so :focus-within (not
   :focus-visible) is what gives keyboard users the same card-level effect as a mouse
   hover when they tab to that link. */
.catalogue-card:hover,
.catalogue-card:focus-within{
border-color:var(--color-teal);
transform:translateY(-3px);
box-shadow:0 20px 40px -30px color-mix(in srgb,var(--color-primary) 45%,transparent)
}

/* Placed after .catalogue-card:hover above deliberately: both selectors are equal
   specificity (0,2,0), so with equal specificity the later rule in source order wins —
   without this ordering, .catalogue-card:hover's border-color:var(--color-teal) would win
   the tie and the flagship accent would flash to the lighter default teal on hover instead
   of staying var(--color-teal-dark). */
.catalogue-card--flagship:hover{
border-color:var(--color-teal-dark)
}

/* Media area: shows media.primaryImage when a product has one (js/catalogue-render.js).
   object-fit:contain (not cover) — measured directly against the real aspect ratio of all
   57 catalogue photos against this box's 4:3 shape: cover was cropping 51 of them (any
   amount), 43 by 10%+, up to 87% gone on the most extreme case (a 10.6:1 near-flat photo)
   and a flat 25% lost on every one of the many exactly-square photos. contain guarantees the
   whole photo is always visible — the padded white/mist sub-card background (below) fills
   whatever gap the ratio mismatch leaves instead of the photo being cut to hide it.
   margin gives it breathing room from the card edges (was previously flush against them);
   now that it's inset rather than flush, it needs its own overflow:hidden+border-radius —
   .catalogue-card's own clipping no longer reaches it. flex-shrink:0 keeps the image area
   at its own aspect-ratio-driven size regardless of how much the text below needs (it
   never had a reason to shrink, but this makes that guarantee explicit rather than
   incidental). 16px matches this card's own established padding rhythm
   (.catalogue-card-body uses 20/22px) rather than introducing an unrelated new spacing
   value.
   Sub-card treatment: a white background of its own (not the card's own #edf4f2 mist) so a
   product photo with a white/near-white background — most of them — doesn't blend into the
   surrounding card. border-radius:var(--radius-lg) matches the outer card, following the
   shared card-corner treatment.
   padding:10px keeps the photo off the sub-card's own edges (box-sizing:border-box,
   sitewide via reset.css, keeps this inside the existing aspect-ratio:4/3 box rather than
   growing past it). The shadow is a new, small, proportionate addition — not a token that
   already existed on this card (.catalogue-card itself has never had one) — chosen at this
   scale rather than reusing --shadow-soft (variables.css), whose 24px/30px hard offset is
   sized for large decorative blocks (.category-featured, .contact-form-card) and would read
   as oversized on a ~250px-wide thumbnail; the gallery-thumb-nav circular buttons
   (css/product-detail.css) already established this same precedent of a small custom shadow
   for a small element rather than the sitewide token. */
.catalogue-card-media{
position:relative;
display:flex;
flex-shrink:0;
align-items:center;
justify-content:center;
margin:16px 16px 0;
padding:10px;
aspect-ratio:4/3;
overflow:hidden;
border-radius:var(--radius-lg);
background:#fff;
box-shadow:0 2px 8px rgba(15,47,47,.1)
}

.catalogue-card-media img{
width:100%;
height:100%;
object-fit:contain
}

/* Products without approved photography use the Sterifast symbol rather than a substitute product image. */
.catalogue-card-media--placeholder{
background:var(--color-mist)
}

.catalogue-card-media--placeholder img{
width:44%;
height:44%;
object-fit:contain;
opacity:.55
}

.catalogue-card-body{
display:flex;
flex:1;
flex-direction:column;
padding:20px 22px 22px
}

/* Its own rule, not folded into .catalogue-card-meta below — reuses that block's exact
   color/font/letter-spacing (same tokens, nothing new), but brand names are always one
   short word, so this doesn't need (and must stay out of) the fixed-height/line-clamp
   budget that block below is deliberately tuned to. */
.catalogue-card-brand{
margin-bottom:2px;
color:var(--color-teal-dark);
font:500 var(--text-sm) var(--font-mono);
letter-spacing:.3px;
text-transform:uppercase
}

/* Reserves a fixed 2-line height (line-height:1.2, matching this element's own previously-
   unset "normal" default measured at that exact ratio, now made explicit) regardless of
   whether the category/subcategory combination actually needs one line or two — the
   longest real combination in the catalogue ("Cleaning & Disinfection › Cleaning &
   Disinfection", 49 characters) needs 2 lines at the grid's own narrowest real column width
   (226px, i.e. the 270px grid minimum minus .catalogue-card-body's 22px side padding).
   -webkit-line-clamp is the ceiling (truncates the rare case that could exceed 2 lines);
   min-height is the floor (keeps 1-line combinations from collapsing shorter) — together
   they're what make this block the same height on every card, not just clamp alone. */
.catalogue-card-meta{
margin-bottom:8px;
overflow:hidden;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:2;
min-height:2.4em;
color:var(--color-teal-dark);
font:500 var(--text-sm) var(--font-mono);
line-height:1.2;
letter-spacing:.3px;
text-transform:uppercase
}

/* Same fixed-height technique as .catalogue-card-meta above, sized for 3 lines: the
   longest real product name in the catalogue ("Sterifast Approved Automatic Thermosealing
   Machine", 50 characters) needs 3 lines at the grid's narrowest real column width (226px)
   — verified directly against the actual name list, not assumed. 3 lines is the minimum
   that guarantees no real catalogue product name is ever silently truncated with "…" at any
   supported card width, per this project's data-integrity rule against hiding real approved
   content without reason. */
.catalogue-card h3{
font-size:20px;
line-height:1.3;
letter-spacing:-.4px;
overflow:hidden;
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:3;
min-height:3.9em
}

/* No longer flex:1 — .catalogue-card-footer's margin-top:auto (below) is the single
   mechanism now doing the bottom-pinning; flex:1 here as well was redundant with (and, per
   the flexbox auto-margin resolution order, could starve) that auto margin rather than
   adding anything of its own. */
.catalogue-card-description{
margin:12px 0 18px;
color:var(--color-muted);
font-size:var(--text-base);
line-height:1.55
}

.catalogue-card-footer{
display:flex;
align-items:center;
justify-content:space-between;
gap:var(--space-1);
margin-top:auto
}

.catalogue-card-cta{
display:inline-flex;
align-items:center;
gap:6px;
font-size:11px;
font-weight:700;
transition:color .22s ease
}

/* Scoped to a.catalogue-card-cta (the real "View product" link) rather than the base
   class, so .catalogue-card-cta-pending (a <span>, no href, "Product page in
   preparation") never picks up a hover/focus color change that would falsely imply
   it's interactive. */
a.catalogue-card-cta:hover,
a.catalogue-card-cta:focus-visible{
color:var(--color-teal-dark)
}

/* Stretched link: makes the whole card clickable/tappable, not just the CTA link's own
   text. a.catalogue-card-cta stays position:static (its default — set explicitly here so
   this isn't accidentally broken by a future positioned-CTA change); its ::after escapes
   that static box and stretches to fill the nearest *positioned* ancestor, which is
   .catalogue-card itself (position:relative, above). Scoped to a.catalogue-card-cta, not
   the base .catalogue-card-cta class, so .catalogue-card-cta-pending (a <span>, no href)
   never gets an invisible full-card overlay for a link that doesn't exist. Verified no
   other descendant of .catalogue-card uses position:absolute/fixed, so nothing needs a
   z-index bump to stay clickable above this overlay. */
a.catalogue-card-cta{
position:static
}
a.catalogue-card-cta::after{
content:"";
position:absolute;
inset:0
}

.catalogue-card-cta-pending{
color:#75918e;
font-weight:600;
text-transform:none;
letter-spacing:0
}

.empty-state{
padding:60px var(--page-padding);
background:var(--color-mist);
border-radius:var(--radius-lg);
color:var(--color-muted);
text-align:center
}

@media (max-width:780px), (orientation:landscape) and (max-height:500px) and (max-width:1024px){
.catalogue-grid{
grid-template-columns:1fr
}
}

/* Pagination (js/products.js) — 20 products per page. Real <button> elements (not <a
   href="#">) per the accessibility requirement: no history-polluting fragment navigation,
   and native keyboard/focus semantics come for free. aria-current="page" marks the active
   page; disabled Previous/Next at either end use the browser's native disabled semantics
   (skipped in Tab order, announced as unavailable) rather than a custom aria-disabled
   workaround. */
.pagination{
margin-top:var(--space-4)
}

.pagination-list{
display:flex;
flex-wrap:wrap;
justify-content:center;
gap:8px;
padding:0;
margin:0;
list-style:none
}

.pagination-btn{
display:inline-flex;
align-items:center;
justify-content:center;
gap:6px;
min-width:44px;
height:44px;
padding:0 14px;
border:1px solid var(--color-line);
border-radius:var(--radius-sm);
background:#fff;
color:var(--color-text);
font:600 var(--text-sm) var(--font-sans);
cursor:pointer;
transition:border-color var(--transition),background var(--transition),color var(--transition)
}

.pagination-btn:hover:not(:disabled){
border-color:var(--color-teal-dark)
}

.pagination-btn[aria-current="page"]{
background:var(--color-primary);
border-color:var(--color-primary);
color:#fff
}

.pagination-btn:disabled{
opacity:.4;
cursor:not-allowed
}

.pagination-btn:focus-visible{
outline:3px solid var(--color-teal-dark);
outline-offset:2px
}

.pagination-prev,
.pagination-next{
padding:0 18px;
font:600 var(--text-xs) var(--font-mono);
letter-spacing:.3px;
text-transform:uppercase
}

@media(max-width:480px){
.pagination-btn{
min-width:38px;
height:38px;
padding:0 10px;
font-size:12px
}
.pagination-prev,
.pagination-next{
padding:0 12px
}
}

/* .catalogue-filters' 4-column desktop grid (1.4fr 1fr 1fr auto, above) needs
   more room than the 780px breakpoint provides: grid items default to
   min-width:auto, which floors each fr-track at its content's min-content
   size (the search/select fields' own padding+content, and the "Clear
   filters" button's intrinsic width, which can't shrink at all). Between
   ~781px and ~884px that natural floor totals more than the available row
   width. This does not produce a visible scrollbar — instead, the browser's
   mobile-viewport algorithm silently widens the entire page's layout
   viewport to fit, which is not a classic overflow and was not caught by a
   plain scrollWidth-vs-innerWidth check until tools/mobile-diagnostic/
   check-overflow.js added a requested-vs-actual-width comparison (see
   docs/ARCHITECTURE.md). Reusing the single-column mobile layout
   already correct below 780px, extended up to 900px, is the minimal fix:
   it's the same layout already verified working, not a new, untested
   cramped 4-column state. .clear-filters moves with it, since its
   justify-self:start override only makes sense paired with the stacked
   single-column layout. */
@media(max-width:900px){
.catalogue-filters{
grid-template-columns:1fr
}
.clear-filters{
justify-self:start
}
}

/* Phase B1 approved catalogue layout: facet sidebar on desktop, stacked above the grid on
   the established <=900px mobile/tablet breakpoint. */
.catalogue-layout{display:grid;grid-template-columns:minmax(230px,280px) minmax(0,1fr);gap:var(--space-5);align-items:start}
.catalogue-filters{display:block;margin:0;padding:var(--space-3);background:rgb(245,250,250);border-radius:var(--radius-lg)}
.catalogue-filters .field{margin-bottom:var(--space-3)}
.filter-facet{padding:var(--space-4) 0;margin:0;border:0;border-top:1px solid var(--color-line)}
.filter-facet legend{padding:0;color:var(--color-text);font:500 13px/1.35 var(--font-sans);letter-spacing:.01em;text-transform:uppercase}
.filter-options{display:grid;gap:var(--space-1);margin-top:var(--space-2)}
.filter-option{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:var(--space-1);align-items:start;color:var(--color-text);font-size:var(--text-base);line-height:1.35;cursor:pointer}
.filter-option input{margin:2px 0 0;accent-color:var(--color-primary)}
.filter-option small{color:var(--color-muted);font:var(--text-sm) var(--font-mono)}
.filter-option:has(input:disabled){color:var(--color-muted);cursor:not-allowed}
.catalogue-filters .clear-filters{height:auto;margin-top:var(--space-1);padding:0}
.catalogue-results{min-width:0}
.catalogue-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-2);margin-bottom:var(--space-4)}
.catalogue-stat{display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-3);border:1px solid var(--color-line);border-radius:var(--radius-md);background:var(--color-paper)}
.catalogue-stat strong{font:600 var(--text-lg) var(--font-sans);letter-spacing:-.4px}
.catalogue-stat span{color:var(--color-muted);font:500 var(--text-xs) var(--font-mono);letter-spacing:.3px;text-transform:uppercase}
.catalogue-card-badge{display:inline-flex;align-items:center;flex-shrink:0;padding:5px 9px;border-radius:999px;background:var(--color-primary-dark);color:#acf8ed;font:400 var(--text-xs) var(--font-mono);letter-spacing:.2px;text-transform:uppercase}
/* background var(--color-teal-dark) paired with color var(--color-primary-dark) — the only
   pairing checked here that clears WCAG AA for small text (5.3:1); white text on
   --color-teal-dark alone is 2.7:1, and var(--color-primary) text on it is 3.66:1 — both
   fail 4.5:1. --color-primary-dark is an existing token, already used as this badge's
   sibling (.catalogue-card-badge) background — no new colour introduced. */
.catalogue-card-badge--flagship{background:var(--color-teal-dark);color:var(--color-primary-dark)}
.catalogue-card-badge--corner{position:absolute;top:8px;left:8px;z-index:1}
@media(max-width:900px){
.catalogue-layout{grid-template-columns:1fr;gap:var(--space-4)}
.catalogue-filters{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-4)}
.catalogue-filters .field{grid-column:1/-1;margin:0}
.filter-facet{padding:0;border-top:0}
.catalogue-filters .clear-filters{grid-column:1/-1;justify-self:start}
}
@media(max-width:600px){
.catalogue-filters,.catalogue-stats{grid-template-columns:1fr}
.filter-facet{padding-top:var(--space-3);border-top:1px solid var(--color-line)}
.catalogue-filters .field,.catalogue-filters .clear-filters{grid-column:auto}
}

/* Audit fix: keep the discovery heading and intro copy from overlapping on narrow screens. */
@media(max-width:600px){
.discovery .section-head{
display:grid;
grid-template-columns:minmax(0,1fr);
align-items:start;
gap:var(--space-3)
}
.discovery .section-head>div,
.discovery .section-head .head-copy{
min-width:0
}
.discovery .head-copy{
width:auto;
max-width:34rem;
margin:0
}
}
.catalogue-card-badge--flagship{background:var(--color-teal);color:var(--color-primary-dark)}
