/*
    ONE CLASS FOR EVERY PRODUCT GRID.

    Everything in this file was scoped to #sets, which is the homepage's
    section id. That worked until the same product cards started rendering
    somewhere else: /find/<slug> draws the identical markup through
    products-all.php inside a plain .wrapper, so it got none of this and fell
    back to products.css -- flex instead of grid, 224px cards, the old radius
    and shadow. The same grid, two different looks depending on the page.

    :is(#sets, .oz-grid) rather than a rename. Every existing #sets rule keeps
    matching exactly as before, and :is() takes the specificity of its
    strongest argument -- the id -- so nothing in the cascade moves. Any
    section that renders the shared card template just adds class="oz-grid".
*/
/*
    assets/css/grids-shop.css

    Third component of the restyle, after the header and the slider: the
    homepage's section rhythm and the counters band. Added 2026-08-20.

    NO MARKUP CHANGED.

    Values from ozroofracks.shop/app/globals.css:
      .section      padding 92px 0
      .stats-band   44px 0, on --ink, white
      .stats-band-grid strong   32px, tracking -.03em
      .stats-band-grid span     10px, uppercase, .08em, 800, #9fb3c2

    Loaded last in template/css.php so it beats main.css.

    NOT TOUCHED, by request: #customerPanel and #cartPanel. Nothing here can
    reach them -- every rule is scoped to section.wrapper / .counters, and both
    panels are siblings of #main injected at the end of template/scripts.php.

    ONE THING DELIBERATELY LEFT FOR LATER, because checking beat guessing:

    - Section headings. .shop styles .section-heading h2 at clamp(30px,3.5vw,
      46px). Here only ONE h2 on the whole homepage is in that position --
      "Share your set-up" in #gallery. Every other section keeps its heading
      inside its own partial, and #sets's h2 is not a title at all but the
      subtitle line "- Each product sells separately -". A blanket h2 rule
      would blow that line up to 46px. Headings need per-section work.

    Product cards WERE on that list and are now done, at the bottom of this
    file -- the reason they came off it is recorded there, because the reason
    was that the first check had been read wrongly.
*/

/* ------------------------------------------------------- section rhythm */

/*
main.css gives every .wrapper 48px 0 16px. .shop breathes at 92px 0. This
roughly doubles the vertical space between sections, which is most of what
makes .shop feel less cramped -- it is the single biggest change in this file,
and the easiest one to dial back if it reads as too airy on a long page.
*/
section.wrapper,
section.wrapper.split,
section.wrapper.style2 {
    padding: 92px 0;
}

/* .shop alternates white against --surface; main.css's style2 was #f3f5f7 */
section.wrapper.style2 {
    background-color: var(--surface);
}

/* --------------------------------------------------------- counters band */

/*
main.css renders this as white-on-white: 45px #444 numbers over #fff, with
16px uppercase #444 labels. .shop makes it a dark band -- the one full-width
block of --ink on the page, which is what separates the hero from the content
below it.

The whole section has to flip, not just the numbers: the country blurb under
the row is inside this section too, and leaving it at #444 would put dark grey
on dark navy.
*/
.counters {
    padding: 44px 0;
    background-color: var(--ink);
    color: var(--white);
}

.counters .counter {
    font-family: var(--font-display);
    font-size: 32px;
    font-weight: 800;
    letter-spacing: -.03em;
    color: var(--white);
}

.counters h3 {
    margin-top: 4px;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: #9fb3c2;
}

/* the exported-countries blurb that shares this section */
.counters center,
.counters p,
.counters .inner > center {
    color: #9fb3c2;
}

.counters a {
    color: var(--white);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ---------------------------------------------------------- product cards */

/*
CORRECTION to the note at the top of this file. An earlier pass concluded the
grid had "no card wrapper to paint" and that cards would need markup changes.
That was wrong, and the mistake is worth recording: the check sampled
`#sets [class*="col-"]`, which matched a .col-3 belonging to a different block
on the page, not the product grid at all. The grid is
`.posts > section.post.products > div`, and section.post.products is ALREADY a
card -- white, 12px radius, 15.2px padding.

So this is paint-only after all. No markup changed.

Values from ozroofracks.shop/app/globals.css:
  .product-card         radius 13, shadow 0 5px 22px rgba(15,34,49,.07)
  .product-card-image   aspect-ratio 1.2, bg #f3f5f6, img object-fit contain
  .product-card-grid    gap 18px
  body > small kicker   --blue, uppercase, .09em, 900
  body h2               21px, line-height 1.25
*/

/*
main.css lays the grid out with flex and a 12px gap, five cards to a row.
.shop uses a fixed 3-column grid at 18px and that is what this now is.

It was auto-fill/minmax first, on the reasoning that 41 cards want to reflow
and a hard 3 would leave very wide cards on a big monitor. Wrong call: on a
1600px screen auto-fill produced five narrow cards, which is the old dense
look rather than .shop's. Capping the grid at .shop's 1180px container solves
the wide-monitor worry properly -- three cards of ~380px, the same size .shop
shows -- instead of letting the column count drift with the window.
*/
/*
The grid and its group headings are flex items of main.css's .row, which is
display:flex. While .posts was itself a flex container its max-content width
filled the row, so each `<h3 class="category-group-heading">` wrapped onto its
own line. As a grid its max-content is narrower, and the "Bundles" heading
started sitting to the RIGHT of the product grid instead of above its own.
Both have to claim the full row so they stack again.
*/
:is(#sets, .oz-grid) .posts,
:is(#sets, .oz-grid) .category-group-heading {
    flex: 0 0 100%;
    width: 100%;
}

:is(#sets, .oz-grid) .posts {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 18px;
    align-items: start;
    max-width: 1180px;
    margin: 0 auto;
}

/*
products.css:29 sizes the cards with `width: calc(20% - 0.75em)` -- a flex
percentage, with 33.333% and 100% variants in its tablet and mobile queries.
Inside a grid container that percentage resolves against the TRACK, so each
card collapsed to a fifth of an already-narrow column and its contents spilled
out. The track sizing has to be the only thing deciding card width.
*/
:is(#sets, .oz-grid) .posts .post {
    width: auto;
}

/* .shop's card carries no border, only the shadow above */
:is(#sets, .oz-grid) .post.products {
    border: 0;
}

:is(#sets, .oz-grid) .post.products {
    border-radius: 13px;
    box-shadow: 0 5px 22px rgba(15, 34, 49, .07);
    transition: box-shadow .2s ease, transform .2s ease;
}

:is(#sets, .oz-grid) .post.products:hover {
    box-shadow: 0 10px 30px rgba(15, 34, 49, .12);
    transform: translateY(-2px);
}

/*
.shop sits its product shots on a light panel rather than white-on-white, so
the silhouette reads. multiply keeps the photos' own white backgrounds from
showing as a white block on that panel -- these are all cut-out product shots
on white, which is exactly the case it is for.
*/
/*
The image panel goes edge to edge, as .shop's .product-card-image does: it is
the FIRST thing in the card and its top corners ARE the card's top corners.
products.css pads .post.products by 0.95em all round, which left the panel
floating inside a white margin with its own 9px radius -- a rounded rectangle
inside a rounded rectangle. Negative margins pull it back out to the card's
edges and the radius follows the card's 13px on the top two corners only.
*/
/*
The image panel is the first thing in the card, so its top corners ARE the
card corners -- as .shop's .product-card-image is.

Escaping the padding took two goes. products.css pads .post.products by
0.95em, so the first attempt pulled the image back by -0.95em. But the body
padding was then set to .shop's 20px on the INNER div, leaving two nested
paddings (15.2px + 20px) and a negative margin that only cancelled the outer
one -- so the image still floated ~20px inside the card.

The card's own padding is dropped instead, so there is exactly one padding to
escape and one number to keep in step.
*/
:is(#sets, .oz-grid) .post.products {
    padding: 0;
}

:is(#sets, .oz-grid) .image-overlay-container {
    aspect-ratio: 1.2;
    height: auto;
    margin: -20px -20px 0;

    /*
    width has to be stated, not left to the negative margins. Something in the
    cascade pins this container to width:100%, so -20px either side only
    SHIFTED it 20px left instead of widening it -- flush on the left, a 40px
    gap on the right, which is easy to glance past. Measured: card 219-589,
    image 219-549.
    */
    width: calc(100% + 40px);

    border-radius: 13px 13px 0 0;
    background: #f3f5f6;
    overflow: hidden;
}

:is(#sets, .oz-grid) .image-overlay-container img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    mix-blend-mode: multiply;
}

/* the hover "View OZ41" chip, onto .shop's button language */
:is(#sets, .oz-grid) .overlay .text.button {
    min-height: 40px;
    padding: 0 18px;
    border-radius: 8px;
    background-color: var(--blue);
    color: var(--white);
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 800;
    text-transform: none;
    line-height: 40px;
}

/*
Card hierarchy. The SKU was the loudest thing on the card at 36px weight 400
grey, with the actual product name in a <small> beneath it. .shop inverts
that: the SKU is a small blue kicker and the name carries the weight.
*/
:is(#sets, .oz-grid) .bigtitle {
    display: block;
    margin-top: 14px;
    color: var(--blue);
    font-family: var(--font-display);
    font-size: 12px;
    font-weight: 900;
    letter-spacing: .09em;
    text-transform: uppercase;
}

:is(#sets, .oz-grid) .post.products small a {
    color: var(--ink);
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 700;
    line-height: 1.3;
}

:is(#sets, .oz-grid) .bigprice {
    color: var(--ink);
    font-family: var(--font-display);
    font-weight: 800;
    letter-spacing: -.02em;
}

/* ------------------------------------------------------ benefits section */

/*
.shop's .benefits-section, for template/features.php's six trust points.
Measured off the live .shop rather than read off the source, so these are the
computed values:

  .benefits-section  padding 75px 0 35px
  .benefits-grid     4 columns, NO gap -- the columns are divided by rules,
                     not by space, which is what stops six cells looking like
                     six loose boxes
  .benefit           padding 18px 22px, gap 14px, 1px --line right border
  .benefit h2        15px, 7px below   (emitted as h3 here -- see features.php)
  .benefit p         12px / 1.55, --muted
  icon               29px, --blue

Three columns rather than .shop's four: .shop has four benefits and this has
six, so 4 would leave a row of four and an orphan row of two. 3 x 2 is the
tidy equivalent at the same proportions.
*/
.benefits-section {
    padding: 75px 0 35px;
}

.benefits-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    max-width: 1180px;
    margin: 0 auto;
    padding: 0 1.5em;
}

.benefit {
    display: flex;
    gap: 14px;
    padding: 18px 22px;
    border-right: 1px solid var(--line);
}

/* The rules divide columns, so the last of each row must not carry one, and
   the first row gets a bottom rule instead of the grid gap .shop does without. */
.benefit:nth-child(3n) {
    border-right: 0;
}

.benefit:nth-child(-n + 3) {
    border-bottom: 1px solid var(--line);
}

.benefit > span {
    flex: 0 0 auto;
    color: var(--blue);
    font-size: 29px;
    line-height: 1;
}

.benefit h3 {
    margin: 0 0 7px;
    font-size: 15px;
    line-height: 1.25;
}

.benefit p {
    margin: 0;
    color: var(--muted);
    font-size: 12px;
    line-height: 1.55;
}

@media screen and (max-width: 980px) {
    .benefits-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .benefit:nth-child(3n) {
        border-right: 1px solid var(--line);
    }

    .benefit:nth-child(2n) {
        border-right: 0;
    }

    .benefit:nth-child(-n + 4) {
        border-bottom: 1px solid var(--line);
    }
}

@media screen and (max-width: 640px) {
    .benefits-grid {
        grid-template-columns: 1fr;
    }

    .benefit {
        border-right: 0 !important;
        border-bottom: 1px solid var(--line);
    }

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

/* ------------------------------------------- why-ozroofracks (#spotlight) */

/*
.shop's #why-ozroofracks / .confidence-grid, for template/spotlight.php.

  .confidence-grid   1.05fr .95fr, gap clamp(45px, 8vw, 100px), centred
  .confidence-image  min-height 520px, radius 14, cover
  badge              white pill bottom-left, 11px 14px, radius 7, 12px/800
  .confidence-copy p --muted, 15px, line-height 1.8, 23px margins
  list               gap 13px, 14px/700, --blue icons

#spotlight is class="wrapper split", which already lays its two <section>
children side by side, so only the grid values are set here rather than
rebuilding the layout.
*/
#spotlight > .inner {
    display: grid;
    grid-template-columns: 1.05fr .95fr;
    align-items: center;
    gap: clamp(45px, 8vw, 100px);
}

#spotlight .confidence-image {
    position: relative;
    display: block;
    border-radius: 14px;
    overflow: hidden;
}

/*
.shop's .confidence-image is a 520px cover-cropped PHOTO. /images/spotlight/
holds promotional GRAPHICS -- illustrations with their own margins and
lettering -- and cover-cropping those to a fixed height letterboxes them and
slices the artwork. The radius and the badge are the parts worth keeping; the
image keeps its own aspect ratio.
*/
#spotlight .confidence-image {
    min-height: 520px;
    border-radius: 14px;
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
}

#spotlight .confidence-badge {
    position: absolute;
    left: 20px;
    bottom: 20px;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 11px 14px;
    border-radius: 7px;
    background: var(--white);
    color: var(--ink);
    font-family: var(--font-display);
    font-size: 12px;
    font-weight: 800;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .15);
}

#spotlight .confidence-badge i {
    color: var(--blue);
}

#spotlight .confidence-copy > p {
    margin: 23px 0;
    color: var(--muted);
    font-size: 15px;
    line-height: 1.8;
}

/*
Two columns for the list. .shop's is one column because it carries three
items; this carries twenty, and a single column of twenty would be a wall.
*/
/*
One column, as .shop has it. Two columns were a workaround for twenty items
in a full-width section; with six beside the photo, one column reads.
*/
#spotlight .confidence-points {
    display: grid;
    grid-template-columns: 1fr;
    gap: 13px;
    margin: 0 0 30px;
    padding: 0;
    list-style: none;
}

#spotlight .confidence-points li {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: 14px;
    font-weight: 700;
    line-height: 1.4;
}

#spotlight .confidence-points li i {
    flex: 0 0 auto;
    margin-top: .15em;
    color: var(--blue);
    font-size: 15px;
}

/* .shop's .button-dark */
#spotlight .button-dark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    min-height: 50px;
    padding: 0 24px;
    border-radius: 8px;
    background-color: var(--ink);
    color: var(--white);
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 800;
    text-transform: none;
}

#spotlight .button-dark:hover {
    background-color: var(--ink-soft);
}

@media screen and (max-width: 980px) {
    #spotlight > .inner {
        grid-template-columns: 1fr;
    }

    #spotlight .confidence-image img {
        min-height: 400px;
    }
}

@media screen and (max-width: 640px) {
    #spotlight .confidence-points {
        grid-template-columns: 1fr;
    }
}

/* ------------------------------------------------ category cards */

/*
.shop's .category-card, measured off the live site: a 440px photographic tile
with a dark gradient and white copy sitting at the bottom.

  .category-grid  1.5fr 1fr 1fr, gap 18px
  .category-card  min-height 440, radius 14, flex, align-items flex-end
  > div           padding 28
  p               13px #dce9f2, 6px below   (ABOVE the h3)
  h3              24px white, 19px below
  span            13px / 800
  hover           translateY(-4px), 0 15px 35px rgba(9,28,43,.16)

The FIRST version of this was a light image-panel card, on the reasoning that
.shop needs photographs and this site only has product cut-outs. That was
wrong -- .shop pulls its photographs from this site. The tile is the real one
now; the light variant survives only for #skus, where the images genuinely
ARE cut-outs (images/<sku>/default.jpg).
*/
.category-grid {
    display: grid;
    grid-template-columns: 1.5fr 1fr 1fr;
    gap: 18px;
    max-width: 1180px;
    margin: 0 auto;
}

.category-card {
    position: relative;
    display: flex;
    align-items: flex-end;
    min-height: 440px;
    overflow: hidden;
    border: 0;
    border-radius: 14px;
    background-color: var(--ink);
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
    color: var(--white);
    text-decoration: none;
    transition: transform .25s ease, box-shadow .25s ease;
}

.category-card:hover {
    transform: translateY(-4px);
    box-shadow: 0 15px 35px rgba(9, 28, 43, .16);
}

.category-card > div {
    padding: 28px;
}

.category-card p {
    margin: 0 0 6px;
    color: #dce9f2;
    font-size: 13px;
    line-height: 1.5;
}

.category-card h3 {
    margin: 0 0 19px;
    color: var(--white);
    font-size: 24px;
    line-height: 1.2;
    letter-spacing: -.03em;
}

.category-card > div > span {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    color: var(--white);
    font-size: 13px;
    font-weight: 800;
}

/*
#skus keeps the LIGHT card. Its images are images/<sku>/default.jpg -- product
cut-outs on white -- and white text over a gradient really would be
unreadable on those. Same component, two treatments, each matched to what the
picture actually is.
*/
#skus .category-card {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    min-height: 0;
    background: var(--white);
    box-shadow: 0 5px 22px rgba(15, 34, 49, .07);
}

#skus .category-card h3 {
    color: var(--ink);
    margin: 0 0 12px;
}

#skus .category-card p {
    color: var(--muted);
    margin: 0 0 19px;
}

#skus .category-card-cta {
    color: var(--blue);
}

.category-card-image {
    display: block;
    aspect-ratio: 1.2;
    background: #f3f5f6;
    overflow: hidden;
}

.category-card-image img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    mix-blend-mode: multiply;
}

.category-card-body {
    display: block;
    padding: 28px;
}

.category-card-cta {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 13px;
    font-weight: 800;
}

@media screen and (max-width: 980px) {
    .category-grid {
        grid-template-columns: 1fr;
    }

    .category-card {
        min-height: 320px;
    }
}

@media screen and (max-width: 980px) {
    :is(#sets, .oz-grid) .posts {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media screen and (max-width: 640px) {
    :is(#sets, .oz-grid) .posts {
        grid-template-columns: 1fr;
    }
}

/* --------------------------------- product card body, exactly as .shop */

/*
.shop's .product-card-body:
    padding 20px
    > small   --blue, uppercase, .09em, 900          (the SKU kicker)
    h2        21px / 1.25, 9px margins               (the product name)
    > p       11px / 1.65, --muted, min-height 58px  (keeps card bottoms level)
    > div     flex, space-between, 12px gap, 14px padding-top, 1px --line top
              strong 13px          (price)
              a      11px / 900 --blue with an arrow

Here the bottom row holds the quantity stepper and Add to Cart instead of a
link, because this grid sells directly rather than linking through -- that is
the one difference, and it is the point of the row. Everything around it is
.shop's.
*/
:is(#sets, .oz-grid) .post.products > div {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: 20px;
}

/* the name grows so every card's action row lands on the same line */
:is(#sets, .oz-grid) .post.products small:first-of-type {
    flex: 1 0 auto;
}

:is(#sets, .oz-grid) .bigprice {
    margin: 0 0 4px;
    font-size: 21px;
}

:is(#sets, .oz-grid) .post.products .stock-shipping-row {
    color: var(--muted);
    font-size: 11px;
    line-height: 1.65;
}

/*
.shop's ruled-off bottom row.

The child rules below are #sets-scoped rather than plain, because
products.css:316 styles `.post.products .btn, .post.products .button` at
(0,0,3,0) -- higher than `.product-card-actions .button` -- so the border
radius silently kept products.css's 6px instead of .shop's 8px.
*/
.product-card-actions {
    display: flex;
    /*
    WRAP, because a fifth control appears once an address is on file.

    With no address the row is stepper + Add to Cart and fits. Once
    cookieto-postcode is set and a shipping quote comes back, 1-Click
    Checkout joins it: 34 + 44 + 34 + 117 + 143 plus four 8px gaps is 404px
    in a 325px card, and at nowrap the last button simply hung 50px past the
    card edge.

    1-Click already declares `flex: 1 1 100%` -- it was written to sit on its
    own line, and only nowrap was stopping it. Allowing the wrap is the whole
    fix; the row-gap keeps the two lines from touching.
    */
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: auto;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

:is(#sets, .oz-grid) .product-card-actions .value-button {
    flex: 0 0 auto;
    width: 34px;
    height: 40px;
    display: grid;
    place-items: center;
    border: 1px solid var(--line);
    background: var(--white);
    color: var(--blue);
    font-size: 18px;
    font-weight: 800;
    cursor: pointer;
    user-select: none;
}

:is(#sets, .oz-grid) .product-card-actions .form-control {
    flex: 0 0 44px;
    width: 44px;
    height: 40px;
    margin: 0;
    padding: 0;
    border: 1px solid var(--line);
    border-left: 0;
    border-right: 0;
    border-radius: 0;
    background: var(--white);
    color: var(--ink);
    text-align: center;
    font-family: var(--font-display);
    font-weight: 800;
}

.product-card-actions .form-control::-webkit-inner-spin-button,
.product-card-actions .form-control::-webkit-outer-spin-button {
    appearance: none;
    margin: 0;
}

:is(#sets, .oz-grid) .product-card-actions .button {
    flex: 1 1 auto;
    min-height: 40px;
    margin: 0 !important;
    padding: 0 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    border-radius: 8px;
    background-color: var(--blue);
    color: var(--white);
    font-family: var(--font-display);
    font-size: 12px;
    font-weight: 800;
    text-transform: none;
    white-space: nowrap;
}

:is(#sets, .oz-grid) .product-card-actions .button:hover {
    background-color: #1591e6;
}

/* 1-Click sits under the row rather than squeezing it to nothing */
:is(#sets, .oz-grid) .product-card-actions .button.blue {
    flex: 1 1 100%;
    margin-top: 8px !important;
    background-color: var(--ink);
}

:is(#sets, .oz-grid) .product-card-actions .button.blue:hover {
    background-color: var(--ink-soft);
}

@media screen and (max-width: 400px) {
    .product-card-actions {
        flex-wrap: wrap;
    }
}

/* ---------------------------------- section heading, categories, bundles */

/*
.shop's .section-heading -- an eyebrow over a large sentence-case title, with
34px beneath it before the content starts.

  .section-heading h2  clamp(30px, 3.5vw, 46px), line-height 1.14, -.04em
*/
.section-heading {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 30px;
    max-width: 1180px;
    margin: 0 auto 34px;
    padding: 0 1.5em;
}

.section-heading h2 {
    max-width: 650px;
    margin: 0;
    font-size: clamp(30px, 3.5vw, 46px);
    line-height: 1.14;
    letter-spacing: -.04em;
}

.section-heading .eyebrow {
    color: var(--blue);
}

/*
.shop's complete-kit panel. Rounded and INSET rather than full-bleed -- that
is what keeps it reading as a card dropped into the page instead of another
section background, which matters here because the stats band directly above
is already a full-width dark band. Two dark full-bleed bands in a row would
just look like one long one.
*/
.bundles-teaser-section {
    padding-top: 0;
}

.bundles-teaser {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 30px;
    max-width: 1180px;
    margin: 0 auto;
    padding: 46px 50px;
    border-radius: 14px;
    background: var(--ink);
    color: var(--white);
}

.bundles-teaser .eyebrow {
    color: #9fd6ff;
}

.bundles-teaser h2 {
    max-width: 620px;
    margin: 0 0 14px;
    color: var(--white);
    font-size: clamp(26px, 3vw, 38px);
    line-height: 1.14;
    letter-spacing: -.04em;
}

.bundles-teaser p {
    max-width: 520px;
    margin: 0;
    color: #b7c8d4;
    font-size: 14px;
    line-height: 1.7;
}

.bundles-teaser .button-primary {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    min-height: 50px;
    padding: 0 24px;
    border-radius: 8px;
    background-color: var(--blue);
    color: var(--white);
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 800;
    text-transform: none;
}

.bundles-teaser .button-primary:hover {
    background-color: #1591e6;
}

/*
.shop's .confidence-grid: 1.05fr / .95fr, centred, gap clamp(45px,8vw,100px).
The image half is back -- as a cover PHOTOGRAPH, which is what .shop uses and
what the first attempt got wrong by reaching for /images/spotlight/.
*/
#spotlight > .inner {
    grid-template-columns: 1.05fr .95fr;
    align-items: center;
    gap: clamp(45px, 8vw, 100px);
    max-width: 1180px;
    margin: 0 auto;
}

@media screen and (max-width: 860px) {
    .bundles-teaser {
        flex-direction: column;
        align-items: flex-start;
        padding: 34px 26px;
    }
}

/* ------------------------------------- catalogue tools above the grid */

/*
.shop's .catalogue-tools -- search on the left, category pills on the right.

  .catalogue-search  min(350px,100%), 48px, 1px #ccd8e0, 9px radius
  .category-tabs     7px gap, wrap, right-aligned
  tabs button        39px, 0 15px, 1px #ccd8e0, 20px radius, 11px/800
  .active            --blue fill, --blue border, white text
  .empty-catalogue   75px 20px, 1px dashed #c6d3db, 13px radius
*/
.catalogue-tools {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 14px;
    max-width: 1180px;
    margin: 0 auto 18px;
}

.catalogue-search {
    display: flex;
    align-items: center;
    gap: 9px;
    width: min(350px, 100%);
    height: 48px;
    padding: 0 14px;
    border: 1px solid #ccd8e0;
    border-radius: 9px;
    background: var(--white);
    color: var(--muted);
}

.catalogue-search input {
    flex: 1;
    min-width: 0;
    height: auto;
    margin: 0;
    padding: 0;
    border: 0;
    outline: 0;
    background: none;
    color: var(--ink);
    font-family: var(--font-display);
    font-size: 14px;
}

.category-tabs {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 7px;
}

.category-tabs button {
    min-height: 39px;
    padding: 0 15px;
    border: 1px solid #ccd8e0;
    border-radius: 20px;
    background: var(--white);
    color: var(--ink-soft);
    font-family: var(--font-display);
    font-size: 11px;
    font-weight: 800;
    text-transform: none;
    cursor: pointer;
}

.category-tabs button.active {
    border-color: var(--blue);
    background: var(--blue);
    color: var(--white);
}

.catalogue-result-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    max-width: 1180px;
    margin: 0 auto 18px;
    color: var(--muted);
    font-size: 12px;
}

.catalogue-result-line strong {
    color: var(--ink);
    font-size: 13px;
    font-weight: 800;
}

.empty-catalogue {
    max-width: 1180px;
    margin: 0 auto;
    padding: 75px 20px;
    border: 1px dashed #c6d3db;
    border-radius: 13px;
    background: var(--white);
    color: var(--muted);
    text-align: center;
}

.empty-catalogue h3 {
    margin: 0 0 6px;
    color: var(--ink);
}

.empty-catalogue p {
    margin: 0;
    font-size: 12px;
}

.empty-catalogue button {
    min-height: 42px;
    margin-top: 12px;
    padding: 0 17px;
    border: 0;
    border-radius: 7px;
    background: var(--blue);
    color: var(--white);
    font-family: var(--font-display);
    font-weight: 800;
    text-transform: none;
    cursor: pointer;
}

.empty-catalogue[hidden],
.category-tabs button[hidden] {
    display: none;
}

@media screen and (max-width: 760px) {
    .catalogue-tools {
        flex-direction: column;
        align-items: stretch;
    }

    .category-tabs {
        justify-content: flex-start;
    }
}

/*
The instruction line under the product heading. It used to be an <h2> and is a
<p> now -- see the note in products-all.php. Sized as body copy, which is what
it always was.
*/
.section-subtitle {
    max-width: 1180px;
    margin: -22px auto 26px;
    padding: 0 1.5em;
    color: var(--muted);
    font-size: 14px;
    line-height: 1.7;
}

/* the heading block is inside .inner on these sections, so it needs no padding */
:is(#sets, .oz-grid) .section-heading,
#gallery .section-heading,
.categories-section .section-heading {
    padding-left: 0;
    padding-right: 0;
}

/* ------------------------------------------------------------- gallery */

/*
.shop's .gallery-grid: four across, 14px gap, square tiles at 11px radius with
object-fit: cover.

The markup reuses .posts and .post, the same classes as the product grid, so
these rules are scoped to #gallery -- otherwise they would fight the product
cards, which are the same selectors doing a different job.
*/
#gallery .posts {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
    max-width: 1180px;
    margin: 0 auto;
}

#gallery .posts .post {
    width: auto;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

#gallery .image-box,
#gallery .image.fit {
    display: block;
    height: 100%;
    margin: 0;
}

#gallery .posts .post img {
    display: block;
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 11px;
    background: #f3f5f6;
}

/* the first tile carries a "View Gallery" overlay chip */
#gallery .overlay .text.button {
    margin-bottom: 0 !important;
    min-height: 40px;
    padding: 0 18px;
    border-radius: 8px;
    background-color: var(--blue);
    color: var(--white);
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 800;
    line-height: 40px;
    text-transform: none;
}

@media screen and (max-width: 860px) {
    #gallery .posts {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* ------------------------------------------------------ .shine containment */

/*
PRE-EXISTING BUG, not introduced here -- it only became visible when the compat
partial (which uses `class="button green shine"`) started rendering inside the
finder card.

main.css:5170 gives .shine `position: relative; overflow: hidden` so its
::before sweep -- 45% wide, 220% tall, rotated 25deg -- is clipped to the
button. migrate.css then sets `overflow: visible` on a selector list that
includes .button. Both are single-class specificity (0,0,1,0), and migrate.css
loads AFTER main.css, so it wins: the sweep escapes and animates a 103px
diagonal highlight across whatever happens to sit near a 49px button.

Any `.button.shine` anywhere on the site has had this. Restored here because
this layer loads last, and scoped to .shine alone so migrate.css keeps its
overflow behaviour for every other button.
*/
.shine {
    overflow: hidden;
}

/*
Placeholder for a bundle with no photo yet. Replaces an <img> pointing at
/images/no-image.jpg, which does not exist on this server OR on live (it 404s
there), so those tiles were rendering a broken-image icon in production.
Modelled on .shop's .image-coming.
*/
:is(#sets, .oz-grid) .image-coming {
    display: grid;
    place-items: center;
    gap: 8px;
    width: 100%;
    height: 100%;
    color: var(--muted);
    font-family: var(--font-display);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .06em;
    text-transform: uppercase;
    background: #e8edf0;
}

:is(#sets, .oz-grid) .image-coming i {
    font-size: 26px;
    color: #b6c4ce;
}

@media screen and (max-width: 980px) {
    #spotlight > .inner {
        grid-template-columns: 1fr;
    }

    #spotlight .confidence-image {
        min-height: 320px;
    }
}

/*
WHY-OZROOFRACKS: children were not filling their grid tracks.

main.css sizes the two halves of a split wrapper with
`.wrapper.split > .inner > * { width: calc(50% - 3.75em) }` -- a flex/float
percentage. Inside a grid container that 50% resolves against the TRACK, not
the row, so a 431px column produced a 184px child. Measured: tracks
476/431px, children 206/184px.

Identical to the fault in the product grid, where products.css's
`width: calc(20% - 0.75em)` collapsed every card inside its track. Any
percentage width written for flex becomes a trap the moment its parent
becomes a grid -- worth remembering for the sections still to convert.

The track is the only thing that should decide the column width.
*/
#spotlight > .inner > * {
    width: auto;
    max-width: none;
    margin: 0;
    padding: 0;
}

/*
The heading was rendering at main.css's 20px -- .section-heading h2 and
.story-grid h2 were styled, but nothing matched #spotlight's own h2.
.shop's .confidence-copy h2 is clamp(31px, 3.8vw, 48px).
*/
#spotlight .confidence-copy h2 {
    margin: 0 0 20px;
    font-size: clamp(28px, 3.2vw, 44px);
    line-height: 1.14;
    letter-spacing: -.04em;
}

#spotlight .confidence-copy > p {
    margin: 0 0 26px;
}

/*
ONE CONTAINER FOR THE WHOLE #sets SECTION.

On /brackets the heading measured 650px starting at x=175 while the grid ran
1015px from x=-8 -- a centred heading floating above a wider, left-shifted
grid. Three different boxes: the heading was shrink-to-fitting to its own h2
max-width, the subtitle followed it, and the grid was picking up a negative
margin from main.css's .row.

Everything in the section now shares the same 1180px box with the same side
padding, so the heading, the subtitle, the filter tools and the grid all start
and end on the same two vertical lines.
*/
:is(#sets, .oz-grid) .section-heading,
:is(#sets, .oz-grid) .section-subtitle,
:is(#sets, .oz-grid) .catalogue-tools,
:is(#sets, .oz-grid) .catalogue-result-line,
:is(#sets, .oz-grid) .empty-catalogue,
:is(#sets, .oz-grid) .posts,
:is(#sets, .oz-grid) .category-group-heading {
    box-sizing: border-box;
    width: 100%;
    max-width: 1180px;
    margin-left: auto;
    margin-right: auto;
    padding-left: 1.5em;
    padding-right: 1.5em;
}

/* the heading block spans the container; only the h2 line is capped */
:is(#sets, .oz-grid) .section-heading {
    display: block;
}

:is(#sets, .oz-grid) .section-heading h2 {
    max-width: 760px;
}

:is(#sets, .oz-grid) .section-subtitle {
    max-width: 1180px;
}

/*
main.css gives .row a negative side margin as a gutter compensation for its
own column system. #sets renders its grid inside a .row, so the whole section
inherited an 8px leftward shift -- container measured -8 to 1007 in a 1038px
viewport, which is 19px off centre and 8px of overflow past the left edge.

Visible only as a slight asymmetry, which is exactly the sort of thing that
reads as "not quite right" without being obviously broken.
*/
:is(#sets, .oz-grid) > .inner > .row,
:is(#sets, .oz-grid) .row {
    margin-left: 0;
    margin-right: 0;
}

/*
THE FILTER WAS COUNTING CORRECTLY AND HIDING NOTHING.

catalogue-tools.php sets `card.hidden = true`, which relies on the user-agent
rule `[hidden] { display: none }`. That rule lives in the UA stylesheet, so it
loses to ANY author `display` declaration -- and both of these have one:

    #sets .post.products  ->  display: flex   (grids-shop.css, the card layout)
    #sets .posts          ->  display: grid   (grids-shop.css, the 3-up track)

So "For Awnings" correctly narrowed the set to 3 and wrote "3 active products",
then every card stayed on screen. Same for the search box. Verified in the
browser: setting .hidden on a card left getComputedStyle().display at "flex"
and offsetHeight at 461.

Using the attribute rather than a class keeps the JS as it is, and !important
is genuinely needed -- the rules it has to beat are plain author declarations
of equal specificity that happen to come later in the file.
*/
:is(#sets, .oz-grid) .post.products[hidden],
:is(#sets, .oz-grid) .posts[hidden],
:is(#sets, .oz-grid) .category-group-heading[hidden],
:is(#sets, .oz-grid) [hidden] {
    display: none !important;
}

/*
CARDS IN A ROW MUST BE THE SAME HEIGHT.

main.css sets align-items:start on .posts, so every card sat at its own
content height and the price/View row landed at a different y in each one --
obvious on the product page's "You may also need" strip, where the six names
run to one, two and three lines.

Two things are needed and neither works alone:
  - stretch, so the grid gives every card in a row the tallest one's height;
  - a growing element inside the card, so that extra height goes somewhere
    and `margin-top:auto` on the action row has free space to push into.
    Without it margin-top:auto resolves to 0 and the row stays put.

The homepage cards name the product in a <small>, already handled above. The
related strip uses .max for the same job.
*/
:is(#sets, .oz-grid) .posts {
    align-items: stretch;
}

:is(#sets, .oz-grid) .post.products .max {
    flex: 1 0 auto;
}

/*
THE COUNTERS BAND RAN THE FULL WINDOW.

Its .inner had max-width:none, so the six figures spread edge to edge while
every section above and below sat in the 1180px container -- the numbers
started 59px to the left of the product grid under them.

Matching .shop's .container exactly: `min(1180px, 100% - 40px)`, which keeps
a 20px gutter on a narrow window instead of a percentage that collapses.
*/
.counters > .inner,
.counters .container {
    width: min(1180px, 100% - 40px);
    max-width: none;
    margin-inline: auto;
}

/*
THE ALTERNATING SECTION BANDS, matched to the live .shop.

Measured there at 1440, top to bottom:

    hero              ink
    finder            transparent
    stats             ink
    benefits          transparent
    categories        #f5f8fa      <-- was transparent here
    bundles teaser    transparent
    catalogue         #f5f8fa
    confidence        transparent
    reviews           #f5f8fa
    gallery           transparent  <-- was #f4f7f9 here

Two faults on this side: categories had no band at all, so benefits ran
straight into it as one long white stretch; and gallery had one when it
should not, which put two banded sections back to back at the foot of the
page. Same for the share and self-serve bands, which were both grey.

All of them now use --band rather than --surface -- see tokens.css for why
those are different colours.
*/
.categories-section,
:is(#sets, .oz-grid),
.selfserve-band {
    background: var(--band);
}

#gallery,
.share-band,
.bundles-teaser-section {
    background: none;
}

/*
THE BUNDLES CARD TOUCHED THE SCREEN EDGE BELOW ~1220px.

.bundles-teaser is max-width:1180 centred, which gives it a gutter only while
the window is wider than 1180. Below that it took the full width and the dark
card ran flush into both edges -- no margin at all on a tablet or a phone,
where every other section still had its 1.5em.

The gutter goes on the SECTION, not the card: the card is the thing being
inset, so padding its container is what leaves the background of the band
visible around it.
*/
.bundles-teaser-section {
    padding-left: 1.5em;
    padding-right: 1.5em;
}

@media screen and (max-width: 860px) {
    .bundles-teaser-section {
        padding-left: 1.15em;
        padding-right: 1.15em;
    }
}

/*
ADD TO CART BESIDE THE STEPPER; 1-CLICK ON THE LINE BELOW.

    [ - ] [ 6 ] [ + ]  [ Add to Cart ]
    [        1-Click Checkout        ]

The 1-Click button only exists once an address is on file and a shipping
quote came back, so the row has four controls most of the time and five
sometimes. Five never fit: 34 + 44 + 34 + 117 + 143 and four gaps is 404px
in a 325px card, and at nowrap the last one hung 50px past the card edge.

I tried squeezing all five onto one line first -- narrower stepper, tighter
gaps, "1-Click" instead of "1-Click Checkout" -- and it did fit the card, but
only by crushing Add to Cart to 60px, which clipped its own label. Two lines
is the honest amount of room.

Two things have to be released or each button claims a line of its own:
main.css's `.fit` is width:100%, and a flex item with a 100% basis always
starts a new line. Add to Cart gives that up and sits with the stepper;
1-Click keeps `flex: 1 1 100%` and so is always the full width below --
predictable whether or not the quote came back.
*/
:is(#sets, .oz-grid) .product-card-actions {
    flex-wrap: wrap;
}

:is(#sets, .oz-grid) .product-card-actions .button.primary {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
}

/*
"1-ClickCheckout" ran together. The space lived INSIDE .btn-text-full, as
`<span class="btn-text-full"> Checkout</span>`, and a leading space at the
start of an inline element is collapsed away -- so the two words touched.

Moved outside the span in the markup, and a left margin here as well: when
that span is hidden on a narrow card the trailing space would otherwise sit
after "1-Click" doing nothing, and when it is shown the margin guarantees the
gap regardless of how the whitespace collapses.
*/
:is(#sets, .oz-grid) .product-card-actions .btn-text-full {
    margin-left: .28em;
}
