/* ==========================================================================
   Rata — Card grid
   rvs_card_grid
   --------------------------------------------------------------------------
   Every selector is anchored to [data-rvs="card-grid"]. A bare [data-rvs]
   matches every other module in the family.

   The card itself lives in rvs-shared.css. Only the arrangement of cards, and
   the per-instance overrides of card presentation, belong here.

   Breakpoints match the rest of the family: 1000px for the tablet step,
   640px for mobile.
   ========================================================================== */

[data-rvs="card-grid"] {
  display: block;
  position: relative;
  background: var(--rvs-bg);
  color: var(--rvs-text);

  /* Card type sizes as tokens rather than as literals at the point of use.
     Two rules below need to know both the size AND the line height to reserve
     a fixed number of lines, and a reserve computed from one size while the
     text renders at another is worse than no reserve at all. */
  --rvs-card-title-fs: var(--rvs-heading-xs);
  --rvs-card-title-lh: var(--rvs-heading-xs-lh);
  --rvs-card-body-fs:  var(--rvs-body);
  --rvs-card-body-lh:  var(--rvs-body-lh);
}

[data-rvs="card-grid"][data-pad="none"]     { --rvs-grid-space: 0; }
[data-rvs="card-grid"][data-pad="tight"]    { --rvs-grid-space: var(--rvs-band-pad-tight); }
[data-rvs="card-grid"][data-pad="standard"] { --rvs-grid-space: var(--rvs-band-pad); }
[data-rvs="card-grid"][data-pad="generous"] { --rvs-grid-space: var(--rvs-band-pad-loose); }

[data-rvs="card-grid"] .rvs-grid__inner {
  padding-block: var(--rvs-grid-space, var(--rvs-band-pad));
}

/* ==========================================================================
   Section heading
   ========================================================================== */

[data-rvs="card-grid"] .rvs-grid__header {
  display: flex;
  flex-direction: column;
  gap: var(--rvs-gap-sm);
  margin-bottom: var(--rvs-gap-lg);
}

[data-rvs="card-grid"][data-header-align="centre"] .rvs-grid__header {
  align-items: center;
  text-align: center;
}

/* Scoped through the header wrapper deliberately. Once the eyebrow can take a
   heading style it carries .rvs-heading-style--* instead of .rvs__label, and
   that class sets colour at the same specificity as a bare
   [data-rvs="card-grid"] .rvs-grid__eyebrow would — leaving the winner down to
   stylesheet load order. The extra class in the chain settles it. */
[data-rvs="card-grid"] .rvs-grid__header .rvs-grid__eyebrow { color: var(--rvs-eyebrow); }
[data-rvs="card-grid"] .rvs-grid__heading { color: var(--rvs-heading); }
[data-rvs="card-grid"] .rvs-grid__blurb   { color: var(--rvs-text); }

[data-rvs="card-grid"] .rvs-grid__blurb > :first-child { margin-top: 0; }
[data-rvs="card-grid"] .rvs-grid__blurb > :last-child  { margin-bottom: 0; }

/* --- Section heading width -----------------------------------------------
   "Practical know-how from the field" sets as one 1000px line across a
   contained row, which is the full container and too long to read as a
   heading. Capping the heading box makes it wrap where the width says rather
   than where the editor guesses — so it stays on two lines at every desktop
   width instead of only at the one the break was typed for.

   A typed line break still works and still wins; the field now allows one.
   Use the break when the split point matters editorially, the cap when it
   just needs to stop being one long line.

   The header is a flex column, so a child sized by max-width centres itself
   under align-items:center with nothing further needed. */
[data-rvs="card-grid"][data-header-measure="wide"]   .rvs-grid__heading { max-width: 900px; }
[data-rvs="card-grid"][data-header-measure="medium"] .rvs-grid__heading { max-width: 720px; }
[data-rvs="card-grid"][data-header-measure="narrow"] .rvs-grid__heading { max-width: 560px; }

/* ==========================================================================
   Grid
   --------------------------------------------------------------------------
   Column counts arrive as plain numbers on the root so they can be used in
   calc() for the carousel track as well as in repeat().
   ========================================================================== */

[data-rvs="card-grid"] .rvs-grid__frame { position: relative; }

/* --- Full-width row -------------------------------------------------------
   Only the frame bleeds; the heading and the rule above it stay in the
   container, which is what the concept shows.

   margin pulls the frame out to the viewport, padding puts the content back
   where it was — so the first card still lines up with the container edge and
   the row runs OFF the screen rather than starting at it. On a carousel that
   padding is on the scroll container, which is what makes the leading card
   sit correctly at scroll position zero instead of jammed against the edge.

   overflow-x: clip on the section, not hidden: 100vw counts the scrollbar, so
   the bleed overshoots by its width on Windows and puts a horizontal scrollbar
   on the page. clip removes the overflow without making the section a scroll
   container, which hidden would do — and that would break the carousel's
   scroll-snap and strand the absolutely positioned arrows.                 */

[data-rvs="card-grid"][data-width="full"] { overflow-x: clip; }

[data-rvs="card-grid"][data-width="full"] .rvs-grid__frame {
  margin-inline: calc(50% - 50vw);
}

/* Gutter only. An earlier version put the container's own inset back on here,
   which aligned the first card to the container edge and made the row appear
   to run off the side of the screen. That is a real pattern, but it is not
   this one: Scott has the quote cards cut off at BOTH edges, so the row starts
   at the viewport. Restoring the container inset just rebuilt the contained
   layout inside a bled frame.

   It also quietly shrank the cards. In carousel mode the column width is
   calc() against 100% of the scroll container's CONTENT box, so every pixel of
   padding here comes straight off the cards. */
[data-rvs="card-grid"][data-width="full"] .rvs-grid__items {
  padding-inline: var(--rvs-gutter);
  /* Keeps a snapped card off the screen edge rather than flush against it. */
  scroll-padding-inline: var(--rvs-gutter);
}

@media (max-width: 640px) {
  [data-rvs="card-grid"][data-width="full"] .rvs-grid__items {
    padding-inline: var(--rvs-gutter-mobile);
    scroll-padding-inline: var(--rvs-gutter-mobile);
  }
}

[data-rvs="card-grid"] .rvs-grid__items {
  display: grid;
  grid-template-columns: repeat(var(--rvs-cols-d, 4), minmax(0, 1fr));
  gap: var(--rvs-grid-gap, var(--rvs-gap-md));
  align-items: stretch;
}

@media (max-width: 1000px) {
  [data-rvs="card-grid"] .rvs-grid__items {
    grid-template-columns: repeat(var(--rvs-cols-t, 2), minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  [data-rvs="card-grid"] .rvs-grid__items {
    grid-template-columns: repeat(var(--rvs-cols-m, 1), minmax(0, 1fr));
  }
}

/* ==========================================================================
   Centred last row
   --------------------------------------------------------------------------
   Five cards in a three-up row leaves two on the bottom row, and grid places
   them in columns 1 and 2 — hard left, with a column-shaped hole on the
   right. York asked for those two centred.

   This cannot be done in grid, and not for want of a property. The orphans
   would have to start half a column in, and grid lines are integers: three
   columns carrying two orphans needs an offset of (3 - 2) / 2 = 0.5 columns.
   The usual dodge is to double the track count and span each card across two,
   but gap is applied between every track, so a six-track grid puts five gaps
   inside the row where three cards should see two — the cards come out the
   wrong width. Either the gap goes or the alignment does.

   So the row switches to flex when this is on. The column width is the same
   calc() the carousel already uses, which is what keeps a full row identical
   to the grid version — flex-grow is 0, so cards never stretch to fill a
   short row, and justify-content puts the slack on the outside instead.
   A full row has no slack, so nothing moves on rows that are already complete
   and this is a last-row treatment in practice.

   Excluded from carousel mode, which needs grid-auto-flow:column and a real
   scroll container. The field says as much, since it cannot be hidden from
   here — display_mode lives in another group and controlling_field only
   reaches siblings.

   min-width:0 restores what minmax(0, 1fr) was doing in grid. A flex item
   defaults to min-width:auto and will refuse to shrink below its longest
   unbreakable word, so without it one long product name pushes the row wider
   than the container.
   ========================================================================== */

[data-rvs="card-grid"][data-row-align="centre"]:not([data-mode="carousel"]) .rvs-grid__items {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
}

[data-rvs="card-grid"][data-row-align="centre"]:not([data-mode="carousel"]) .rvs-grid__items > * {
  flex: 0 1 calc(
    (100% - (var(--rvs-cols-d, 4) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
    / var(--rvs-cols-d, 4)
  );
  max-width: calc(
    (100% - (var(--rvs-cols-d, 4) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
    / var(--rvs-cols-d, 4)
  );
  min-width: 0;
}

@media (max-width: 1000px) {
  [data-rvs="card-grid"][data-row-align="centre"]:not([data-mode="carousel"]) .rvs-grid__items > * {
    flex-basis: calc(
      (100% - (var(--rvs-cols-t, 2) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
      / var(--rvs-cols-t, 2)
    );
    max-width: calc(
      (100% - (var(--rvs-cols-t, 2) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
      / var(--rvs-cols-t, 2)
    );
  }
}

@media (max-width: 640px) {
  [data-rvs="card-grid"][data-row-align="centre"]:not([data-mode="carousel"]) .rvs-grid__items > * {
    flex-basis: calc(
      (100% - (var(--rvs-cols-m, 1) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
      / var(--rvs-cols-m, 1)
    );
    max-width: calc(
      (100% - (var(--rvs-cols-m, 1) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
      / var(--rvs-cols-m, 1)
    );
  }
}

/* ==========================================================================
   Card presentation overrides
   --------------------------------------------------------------------------
   These reach into the shared card because they are properties of the row,
   not of the card: one grid is filled cards on cream, the next is bare icon
   cards on charcoal, and the card partial is the same in both.

   Specificity note: the shared rules are [data-rvs] .rvs__card (0,2,0). The
   attribute pair below makes these 0,3,0, so they win without !important.
   ========================================================================== */

[data-rvs="card-grid"][data-card-surface="plain"] .rvs__card {
  background: transparent;
}

[data-rvs="card-grid"][data-card-surface="plain"] .rvs__card-body {
  padding-inline: 0;
}

[data-rvs="card-grid"][data-card-surface="outlined"] .rvs__card {
  background: var(--rvs-surface);
  border: 1px solid var(--rvs-rule);
}

/* --- Card colour ----------------------------------------------------------
   The scheme sets --rvs-surface for the whole band, which is right when the
   card is a tint of the background and wrong when the design gives the card a
   colour of its own — cream cards on a light-cream band, olive cards on cream.

   Colour is set on .rvs__card rather than on the grid, so the custom property
   overrides land on the card element and cascade to its children. The card
   partial already reads --rvs-heading, --rvs-text, --rvs-link, --rvs-eyebrow
   and --rvs-rule, so a dark card gets legible text without the partial
   knowing anything about this field.

   BOTH ends redeclare their text tokens. The light ones used to inherit the
   band's, on the reasoning that a cream card on a cream band still wants
   charcoal — true, but it only holds while the band is light. Put the same
   card on an olive or charcoal band and it inherits light-cream type onto a
   light fill and the copy all but vanishes. That is what the sage testimonial
   cards on the olive band were doing.                                      */

[data-rvs="card-grid"][data-card-colour="white"] .rvs__card,
[data-rvs="card-grid"][data-card-colour="light-cream"] .rvs__card,
[data-rvs="card-grid"][data-card-colour="cream"] .rvs__card,
[data-rvs="card-grid"][data-card-colour="clay"] .rvs__card,
[data-rvs="card-grid"][data-card-colour="sage"] .rvs__card {
  --rvs-heading: var(--rvs-charcoal);
  --rvs-text:    var(--rvs-charcoal);
  --rvs-link:    var(--rvs-red);
  --rvs-eyebrow: var(--rvs-charcoal);
  --rvs-rule:    rgba(34, 34, 34, .18);
  color:         var(--rvs-charcoal);
}

[data-rvs="card-grid"][data-card-colour="white"]       .rvs__card { background: var(--rvs-white); }
[data-rvs="card-grid"][data-card-colour="light-cream"] .rvs__card { background: var(--rvs-light-cream); }
[data-rvs="card-grid"][data-card-colour="cream"]       .rvs__card { background: var(--rvs-cream); }
[data-rvs="card-grid"][data-card-colour="clay"]        .rvs__card { background: var(--rvs-clay); }
[data-rvs="card-grid"][data-card-colour="sage"]        .rvs__card { background: var(--rvs-sage); }

[data-rvs="card-grid"][data-card-colour="olive"] .rvs__card,
[data-rvs="card-grid"][data-card-colour="charcoal"] .rvs__card,
[data-rvs="card-grid"][data-card-colour="red"] .rvs__card {
  --rvs-heading: var(--rvs-light-cream);
  --rvs-text:    var(--rvs-light-cream);
  --rvs-link:    var(--rvs-light-cream);
  --rvs-eyebrow: var(--rvs-light-cream);
  --rvs-rule:    rgba(248, 247, 242, .28);
  color:         var(--rvs-light-cream);
}

[data-rvs="card-grid"][data-card-colour="olive"]    .rvs__card { background: var(--rvs-olive); }
[data-rvs="card-grid"][data-card-colour="charcoal"] .rvs__card { background: var(--rvs-charcoal); }
[data-rvs="card-grid"][data-card-colour="red"]      .rvs__card { background: var(--rvs-red); }

/* A coloured card with no image needs its own breathing room — without the
   media block above it the fill otherwise hugs the first line of type. */
[data-rvs="card-grid"][data-media="none"][data-card-colour]:not([data-card-colour="default"]) .rvs__card-body {
  padding-block: var(--rvs-gap-md);
}

/* --- Card eyebrow colour --------------------------------------------------
   Targets the eyebrow element rather than overriding --rvs-eyebrow on the
   card, because that token is also read by the spec chips, the corner badge
   and the product tile label — all of which should keep following the scheme.

   Sits above the dark-card text overrides on purpose: setting colour directly
   beats a rule that reads a variable, so an explicit choice still wins on an
   olive or charcoal card.                                                   */

[data-rvs="card-grid"][data-card-eyebrow="red"]         .rvs__card-eyebrow { color: var(--rvs-red); }
[data-rvs="card-grid"][data-card-eyebrow="clay"]        .rvs__card-eyebrow { color: var(--rvs-clay); }
[data-rvs="card-grid"][data-card-eyebrow="cream"]       .rvs__card-eyebrow { color: var(--rvs-cream); }
[data-rvs="card-grid"][data-card-eyebrow="light-cream"] .rvs__card-eyebrow { color: var(--rvs-light-cream); }
[data-rvs="card-grid"][data-card-eyebrow="white"]       .rvs__card-eyebrow { color: var(--rvs-white); }
[data-rvs="card-grid"][data-card-eyebrow="charcoal"]    .rvs__card-eyebrow { color: var(--rvs-charcoal); }
[data-rvs="card-grid"][data-card-eyebrow="olive"]       .rvs__card-eyebrow { color: var(--rvs-olive); }

/* --- Card eyebrow case ----------------------------------------------------
   The capitals come from .rvs__label-s in the token sheet, not from the text,
   so turning them off is a matter of releasing text-transform rather than
   rewriting anything.

   Letter-spacing goes with it. The .04em on .rvs__label-s exists because
   capitals at 13px set as a solid block and need prising apart; lowercase has
   ascenders and descenders doing that work already, and the same tracking on
   it just reads as loose.

   Worth being clear about what "sentence case" can and cannot do here: this
   only stops the stylesheet shouting. It shows the label as written at
   source. A blog tag saved as "HOW-TO GUIDE" will still render as capitals,
   because CSS has no way to know where the sentence began. That is a data
   fix, not a display one.

   One rule covers both render sites — the shared partial and the tiles card
   build their eyebrow separately but both land on .rvs__card-eyebrow.

   The section header eyebrow is deliberately untouched. It is a different
   element (.rvs-grid__eyebrow) on a different primitive, and York asked about
   the cards. */
[data-rvs="card-grid"][data-card-eyebrow-case="uppercase"] .rvs__card-eyebrow {
  text-transform: uppercase;
}

[data-rvs="card-grid"][data-card-eyebrow-case="sentence"] .rvs__card-eyebrow {
  text-transform: none;
  letter-spacing: 0;
}

/* --- Card text size -------------------------------------------------------
   One step down from the default, for rows where the card is carrying a lot
   of other furniture — the job page equipment guide, where a title, a
   description and a row of product tiles all have to sit inside one card
   without it running to the bottom of the screen.

   Overlay cards are excluded. Their title is the display face at display-l
   by design, and that decision belongs to the tile treatment rather than to
   this control — shrinking it here would quietly change the homepage sector
   tiles along with the job page.

   The :not() is also what keeps this out of a specificity fight with
   .rvs__card--overlay .rvs__card-title in the shared sheet, which is 0,3,0 —
   the same as this rule would otherwise be, leaving the winner down to which
   stylesheet the browser loaded last. */

[data-rvs="card-grid"][data-card-text="small"] {
  --rvs-card-title-fs: 1.125rem;  /* 18 */
  --rvs-card-title-lh: 1.333;
  --rvs-card-body-fs:  1rem;      /* 16 */
  --rvs-card-body-lh:  1.5;
}

[data-rvs="card-grid"][data-card-text="small"] .rvs__card:not(.rvs__card--overlay) .rvs__card-title {
  font-size:   var(--rvs-card-title-fs);
  line-height: var(--rvs-card-title-lh);
}

[data-rvs="card-grid"][data-card-text="small"] .rvs__card:not(.rvs__card--overlay) .rvs__card-text {
  font-size:   var(--rvs-card-body-fs);
  line-height: var(--rvs-card-body-lh);
}

/* Small description (build 50): the description alone at 13px, title
   untouched. For longer copy such as the people bios. */
[data-rvs="card-grid"][data-card-text="xsmall"] .rvs__card:not(.rvs__card--overlay) .rvs__card-text {
  font-size:   .8125rem;   /* 13 */
  line-height: 1.55;
}

/* Build 50.1: the size above was set on the description's wrapper, but the
   description is rich text, so the words sit inside <p> (and sometimes
   <span>) tags — and POWER gives every <p> its own font-size, which beats
   anything inherited from the wrapper. So the setting changed nothing
   visible. Both reduced sizes now push the size down into the rich text.
   !important on font-size only, and only inside these two options: rich
   text pasted from Word or another site often carries inline
   style="font-size:…" on its spans, which nothing else can override.
   The Default size is left alone so existing grids don't shift. */
[data-rvs="card-grid"][data-card-text="small"] .rvs__card:not(.rvs__card--overlay) .rvs__card-text :is(p, li, span, div, a, strong, b, em, i),
[data-rvs="card-grid"][data-card-text="xsmall"] .rvs__card:not(.rvs__card--overlay) .rvs__card-text :is(p, li, span, div, a, strong, b, em, i) {
  font-size: inherit !important;
  line-height: inherit;
}

/* --- Contact details (build 50) -------------------------------------------
   The people cards. margin-top:auto pins the list to the foot of the card
   body, so phone and email sit at the same height across a row however long
   each bio runs — the card body is already a flex column that fills the
   card. If a CTA follows, it drops its own auto margin so the two stay
   together at the bottom instead of splitting the free space.

   Roboto Mono, per York. Each line gets a small solid square — the
   Wingdings 2 square bullet in her reference — drawn in CSS rather than
   set in Wingdings, which isn't a web font and would fall back to a letter
   on most machines. */
[data-rvs="card-grid"] .rvs__card-contact {
  list-style: none;
  margin: auto 0 0;
  padding: 12px 0 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  font-family: var(--rvs-font-mono);
  font-weight: 400;
  font-size: .8125rem;     /* 13 */
  line-height: 1.5;
  letter-spacing: .01em;
}
[data-rvs="card-grid"][data-card-align="centre"] .rvs__card-contact { align-items: center; }

/* POWER styles list items; reset the margin and any marker it adds. */
[data-rvs="card-grid"] .rvs__card-contact li {
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
[data-rvs="card-grid"] .rvs__card-contact li::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  background: currentColor;
  position: static;
  left: auto;
}
[data-rvs="card-grid"] .rvs__card-contact li { color: var(--rvs-link); }
[data-rvs="card-grid"] .rvs__card-contact a {
  color: inherit;
  text-decoration: none;
  word-break: break-word;
}
[data-rvs="card-grid"] .rvs__card-contact a:hover,
[data-rvs="card-grid"] .rvs__card-contact a:focus-visible { text-decoration: underline; }

[data-rvs="card-grid"] .rvs__card-contact + .rvs__card-cta { margin-top: 0; }

/* --- Levelling the text across a row --------------------------------------
   A grid row already makes every card the same HEIGHT, and the CTA already
   pins to the bottom with margin-top:auto. What neither does is line up the
   blocks BETWEEN those two points: a one-line title lets everything under it
   ride up, so on the resources row the middle card's description starts a
   line higher than its neighbours' and the row reads as misaligned even
   though the cards are identical heights. York's note, 24 Sep.

   Reserving a fixed number of lines on the title is the fix, and it has to be
   min-height rather than height — a title that genuinely runs to three lines
   must still be allowed to.

   Computed from the tokens above, so a row set to the small text size
   reserves the space the small text actually occupies.

   Overlay cards are excluded again: their body is absolutely positioned over
   the image and centred, so a reserve on the title moves the whole group off
   centre for no gain. */

[data-rvs="card-grid"][data-title-lines="2"] .rvs__card:not(.rvs__card--overlay) .rvs__card-title {
  min-height: calc(2 * var(--rvs-card-title-lh) * var(--rvs-card-title-fs));
}

[data-rvs="card-grid"][data-title-lines="3"] .rvs__card:not(.rvs__card--overlay) .rvs__card-title {
  min-height: calc(3 * var(--rvs-card-title-lh) * var(--rvs-card-title-fs));
}

/* --- Description length ---------------------------------------------------
   Caps the preview at a fixed number of lines and reserves that same space,
   so a two-line description and a five-line one occupy an identical block.
   Together with the title reserve this is what actually lines a row up.

   Clamping is -webkit-line-clamp, which needs display:-webkit-box. That is
   not the legacy prefix it looks like — it is the only implemented syntax and
   every current browser supports it. The unprefixed line-clamp is shipping
   but is not yet safe on its own, so both are declared.

   Paragraph margins are flattened inside the clamp. A rich text field usually
   returns one <p>, but if an editor leaves a trailing empty paragraph the
   margin counts toward the clamped height and eats a line of real copy. */

[data-rvs="card-grid"][data-body-lines]:not([data-body-lines="none"]) .rvs__card-text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

[data-rvs="card-grid"][data-body-lines]:not([data-body-lines="none"]) .rvs__card-text > * {
  margin-block: 0;
}

[data-rvs="card-grid"][data-body-lines="2"] .rvs__card-text {
  -webkit-line-clamp: 2;
  line-clamp: 2;
  min-height: calc(2 * var(--rvs-card-body-lh) * var(--rvs-card-body-fs));
}

[data-rvs="card-grid"][data-body-lines="3"] .rvs__card-text {
  -webkit-line-clamp: 3;
  line-clamp: 3;
  min-height: calc(3 * var(--rvs-card-body-lh) * var(--rvs-card-body-fs));
}

[data-rvs="card-grid"][data-body-lines="4"] .rvs__card-text {
  -webkit-line-clamp: 4;
  line-clamp: 4;
  min-height: calc(4 * var(--rvs-card-body-lh) * var(--rvs-card-body-fs));
}

/* On a phone every card is its own row, so there is nothing to line up with —
   and a reserve there is just a gap between the title and the description.
   The clamp stays: it is there to keep the preview short, which matters more
   on a small screen, not less. */
/* The :not() is carried over here too, and not for the reason it exists
   above. The desktop reserve is 0,5,0 because of it; a plain rule here would
   be 0,4,0 and lose outright, media query or not. Matching the count and
   sitting later in the file is what makes the reset land. */
@media (max-width: 640px) {
  [data-rvs="card-grid"][data-title-lines] .rvs__card:not(.rvs__card--overlay) .rvs__card-title,
  [data-rvs="card-grid"][data-body-lines]  .rvs__card:not(.rvs__card--overlay) .rvs__card-text { min-height: 0; }
}

/* Centred card text. The shared sheet has an --icon-centred variant, but it
   is applied per card; this is a decision about the whole row. */
[data-rvs="card-grid"][data-card-align="centre"] .rvs__card-body {
  text-align: center;
  align-items: center;
}

[data-rvs="card-grid"][data-card-align="centre"] .rvs__card-meta {
  justify-content: center;
}

/* Cut-out product shots sit inside the frame with air around them; photography
   fills it. The frame keeps its aspect either way so rows stay level.

   The padding moved off the <img> and onto the frame. On the image it depended
   on box-sizing being border-box — the RVS stylesheets never set that, so it
   was inherited from whatever POWER happens to declare. Under content-box the
   img is width:100% PLUS 24px, which overflows the frame's overflow:hidden and
   crops the very edges the padding exists to protect: the padding appears to do
   nothing, and does it inconsistently depending on the image's proportions.

   On the frame it is unambiguous. box-sizing is declared here rather than
   assumed, so aspect-ratio keeps measuring the border box and rows stay level
   whatever the inset is set to; the img then fills the content box inside it.
   Background sits on the frame, so the inset shows as surround rather than as a
   gap in the card. */
[data-rvs="card-grid"][data-fit="contain"] .rvs__card-media {
  box-sizing: border-box;
  background: var(--rvs-white);
  padding: var(--rvs-image-inset, 12px);
}

[data-rvs="card-grid"][data-fit="contain"] .rvs__card-media img {
  object-fit: contain;
}

/* Icon rows read better with the icon carrying some weight. */
[data-rvs="card-grid"][data-media="icon"] .rvs__card-icon svg {
  width: 32px;
  height: 32px;
}

/* ==========================================================================
   Card with nested product tiles
   --------------------------------------------------------------------------
   Job page, "Find the right gear for the job". The tile itself is shared —
   only the grid it sits in is local.
   ========================================================================== */

[data-rvs="card-grid"] .rvs__card--tiles .rvs__card-body {
  gap: 10px;
}

[data-rvs="card-grid"] .rvs-grid__tiles {
  display: grid;
  grid-template-columns: repeat(var(--rvs-tile-cols, 3), minmax(0, 1fr));
  gap: 8px;
  width: 100%;
  margin-top: 6px;
  /* The hover label sits above the tile and must not be clipped by the card,
     which has overflow:hidden for the image corners. */
  padding-top: 18px;
}

/* overflow:visible lost to the shared [data-rvs] .rvs__card { overflow:
   hidden } on load order — same specificity, and the shared sheet can load
   after this one — so the hover label was cut off at the card's edge
   (build 49.1). Extra classes make this win regardless of order. */
[data-rvs="card-grid"] .rvs-grid__items > .rvs__card.rvs__card--tiles {
  overflow: visible;
}

/* Edge tiles anchor their label to the outer side, so a long product name
   runs inward over the card rather than out past it. Middle tiles stay
   centred. Matches the tile-column setting; mobile is always 3 across. */
@media (min-width: 641px) {
  [data-rvs="card-grid"][data-tile-cols="2"] .rvs-grid__tiles > .rvs__tile:nth-child(2n+1) .rvs__tile-label,
  [data-rvs="card-grid"][data-tile-cols="3"] .rvs-grid__tiles > .rvs__tile:nth-child(3n+1) .rvs__tile-label,
  [data-rvs="card-grid"][data-tile-cols="4"] .rvs-grid__tiles > .rvs__tile:nth-child(4n+1) .rvs__tile-label {
    left: -1px; right: auto; transform: none;
  }
  [data-rvs="card-grid"][data-tile-cols="2"] .rvs-grid__tiles > .rvs__tile:nth-child(2n) .rvs__tile-label,
  [data-rvs="card-grid"][data-tile-cols="3"] .rvs-grid__tiles > .rvs__tile:nth-child(3n) .rvs__tile-label,
  [data-rvs="card-grid"][data-tile-cols="4"] .rvs-grid__tiles > .rvs__tile:nth-child(4n) .rvs__tile-label {
    left: auto; right: -1px; transform: none;
  }
}
@media (max-width: 640px) {
  [data-rvs="card-grid"] .rvs-grid__tiles > .rvs__tile:nth-child(3n+1) .rvs__tile-label { left: -1px; right: auto; transform: none; }
  [data-rvs="card-grid"] .rvs-grid__tiles > .rvs__tile:nth-child(3n) .rvs__tile-label { left: auto; right: -1px; transform: none; }
}

/* Label above everything else in the grid, including the next card. */
[data-rvs="card-grid"] .rvs-grid__tiles .rvs__tile:hover,
[data-rvs="card-grid"] .rvs-grid__tiles .rvs__tile:focus-visible { z-index: 4; }

/* A product with no image: its name in the tile instead of a broken icon. */
[data-rvs="card-grid"] .rvs__tile-name {
  font-size: 12px;
  line-height: 1.3;
  text-align: center;
  color: var(--rvs-text);
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
}

[data-rvs="card-grid"][data-card-align="centre"] .rvs-grid__tiles {
  justify-items: center;
}

@media (max-width: 640px) {
  [data-rvs="card-grid"] .rvs-grid__tiles {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* ==========================================================================
   Carousel
   --------------------------------------------------------------------------
   A scroll-snapping row rather than a transform track, so it swipes natively
   on touch, keeps keyboard scrolling, and needs no JS to be usable — JS only
   adds the arrows and their disabled states.
   ========================================================================== */

[data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items {
  grid-auto-flow: column;
  grid-template-columns: none;
  grid-auto-columns: calc(
    (100% - (var(--rvs-cols-d, 4) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
    / var(--rvs-cols-d, 4)
  );
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 0;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Room for the focus ring, which would otherwise be clipped by overflow. */
  padding-block: 4px;
}

[data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items::-webkit-scrollbar {
  display: none;
}

/* The grab cursor is the only thing telling a mouse user the row can be
   dragged, now that the arrows are optional. Pointer-precision only — a touch
   device has no cursor and the hint would be dead weight in the stylesheet. */
@media (hover: hover) and (pointer: fine) {
  [data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items { cursor: grab; }
  /* Build 64: belt and braces with the dragstart block in module.js. */
  [data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items :is(a, img) { -webkit-user-drag: none; }
  [data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items[data-dragging] {
    cursor: grabbing;
    /* Without this a drag across cards selects their text, which looks broken
       and leaves a highlight behind when the pointer is released. */
    user-select: none;
  }
}

[data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items > * {
  scroll-snap-align: start;
}

@media (max-width: 1000px) {
  [data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items {
    grid-auto-columns: calc(
      (100% - (var(--rvs-cols-t, 2) - 1) * var(--rvs-grid-gap, var(--rvs-gap-md)))
      / var(--rvs-cols-t, 2)
    );
  }
}

@media (max-width: 640px) {
  [data-rvs="card-grid"][data-mode="carousel"] .rvs-grid__items {
    /* A sliver of the next card is the affordance that replaces the arrows. */
    grid-auto-columns: 82%;
  }
}

/* --- Arrows --------------------------------------------------------------
   Red circle, light chevron, taken from the button tokens rather than the raw
   palette so the red scheme does not put red on red. */

[data-rvs="card-grid"] .rvs-grid__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 4;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--rvs-btn-bg);
  color: var(--rvs-btn-fg);
  cursor: pointer;
  transition: background-color var(--rvs-transition), color var(--rvs-transition), opacity var(--rvs-transition);
}

[data-rvs="card-grid"] .rvs-grid__arrow svg { width: 16px; height: 16px; }

[data-rvs="card-grid"] .rvs-grid__arrow:hover {
  background: var(--rvs-btn-bg-hover);
  color: var(--rvs-btn-fg-hover, var(--rvs-btn-fg));
}

[data-rvs="card-grid"] .rvs-grid__arrow[disabled] {
  opacity: .3;
  cursor: default;
  pointer-events: none;
}

/* Outside the cards, in the container gutter — the homepage concept. */
[data-rvs="card-grid"][data-arrows="edge"] .rvs-grid__arrow--prev { left: -17px; }
[data-rvs="card-grid"][data-arrows="edge"] .rvs-grid__arrow--next { right: -17px; }

/* Overlapping — safer whenever the container has no room either side. */
[data-rvs="card-grid"][data-arrows="overlap"] .rvs-grid__arrow--prev { left: 8px; }
[data-rvs="card-grid"][data-arrows="overlap"] .rvs-grid__arrow--next { right: 8px; }

/* Below the tablet step the row swipes, so the arrows come off entirely
   rather than sitting over the cards on a small screen. */
@media (max-width: 1000px) {
  [data-rvs="card-grid"] .rvs-grid__arrow { display: none; }
}

/* ==========================================================================
   Quote card
   --------------------------------------------------------------------------
   A figure, not a headed card. Everything here resets what POWER does to a
   bare blockquote — the theme gives it an indent, a rule down the left and an
   italic, none of which belong on a testimonial tile sitting in its own
   coloured box.
   ========================================================================== */

[data-rvs="card-grid"] .rvs__card--quote {
  margin: 0;
}

[data-rvs="card-grid"] .rvs__card--quote .rvs__quote {
  margin: 0;
  padding: 0;
  border: 0;
  quotes: none;
  font-style: normal;
  color: var(--rvs-heading);
}

[data-rvs="card-grid"] .rvs__card--quote .rvs__quote::before,
[data-rvs="card-grid"] .rvs__card--quote .rvs__quote::after { content: none; }

[data-rvs="card-grid"] .rvs__card--quote .rvs__quote > :first-child { margin-top: 0; }
[data-rvs="card-grid"] .rvs__card--quote .rvs__quote > :last-child  { margin-bottom: 0; }

/* Composed only. A table-backed quote is plain text in its own column, so the
   stylesheet owns its weight and size. A hand-written one is rich text holding
   the quote AND the attribution in a single field, and sizing that blockquote
   would bold the name and the role along with the words — the editor's own
   formatting is the hierarchy there, which is the trade that came with putting
   it all in one field. */
[data-rvs="card-grid"] .rvs__card--quote[data-quote="composed"] .rvs__quote {
  font-weight: 700;
  font-size: clamp(17px, 1.15vw, 20px);
  line-height: 1.45;
}

[data-rvs="card-grid"] .rvs__card--quote .rvs__quote-cite {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: var(--rvs-gap-sm);
  font-style: normal;
  color: var(--rvs-text);
}

[data-rvs="card-grid"] .rvs__card--quote .rvs__quote-role { opacity: .8; }

/* Left, not centred — the play button is an action on the quote, not a
   decoration centred over it, and the copy above it is left aligned. */
[data-rvs="card-grid"] .rvs__card--quote .rvs__quote-play {
  align-self: flex-start;
  margin-top: var(--rvs-gap-sm);
}

/* The portrait is optional and sits above the words. Follows the card's own
   alignment rather than forcing centre, so a left-aligned row stays left. */
[data-rvs="card-grid"] .rvs__card--quote .rvs__card-avatar { padding-top: var(--rvs-gap-md); }

/* ==========================================================================
   Show more
   --------------------------------------------------------------------------
   The collapsed height is set inline by JS, measured from the top of the first
   card in the first hidden row. It cannot be done in CSS: rows are auto-height
   and the count per row changes at every breakpoint.

   Every card is in the DOM at all times. This is a visual clip, not a content
   gate — it stays crawlable and Ctrl+F still finds the hidden rows.
   ========================================================================== */

[data-rvs="card-grid"] .rvs-grid__clip {
  transition: max-height 240ms ease;
}

[data-rvs="card-grid"][data-mode="show_more"][data-clipped="true"] .rvs-grid__clip {
  overflow: hidden;
}

/* The fade sits on the frame rather than the clip, because the clip is the
   element carrying overflow:hidden — a gradient inside it would be cut off by
   the very edge it is there to soften.

   It fades to --rvs-bg, which the colour scheme sets on the section, so the
   gradient follows whatever background the band is on rather than assuming
   cream. Height is tied to the peek value so a flush clip gets a shallow fade
   and a deep peek gets a deep one.

   pointer-events:none matters: the fade covers the top of the part-visible
   row, and those cards are real links. */
[data-rvs="card-grid"][data-mode="show_more"][data-clipped="true"][data-fade="true"] .rvs-grid__frame::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: clamp(72px, calc(var(--rvs-peek, 36px) * 2.5), 180px);
  background: linear-gradient(to bottom,
              transparent,
              var(--rvs-bg, var(--rvs-cream)));
  pointer-events: none;
  z-index: 2;
}

[data-rvs="card-grid"] .rvs-grid__more {
  display: flex;
  justify-content: center;
  margin-top: var(--rvs-gap-md);
}

/* ==========================================================================
   Footer button
   ========================================================================== */

[data-rvs="card-grid"] .rvs-grid__footer {
  display: flex;
  margin-top: var(--rvs-gap-lg);
}

[data-rvs="card-grid"] .rvs-grid__footer[data-align="centre"] { justify-content: center; }
[data-rvs="card-grid"] .rvs-grid__footer[data-align="left"]   { justify-content: flex-start; }

/* A show-more button immediately above a footer button is two stacked pills,
   which reads as a mistake. Tighten the pair. */
[data-rvs="card-grid"] .rvs-grid__more + .rvs-grid__footer { margin-top: var(--rvs-gap-md); }

/* ==========================================================================
   Reduced motion
   --------------------------------------------------------------------------
   The token sheet already flattens transitions. Smooth scrolling is set by JS
   rather than CSS, so it is handled there too.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  [data-rvs="card-grid"] .rvs-grid__clip { transition: none; }
}

/* ==========================================================================
   Build 67 — red link arrow (York, 21 Oct)
   --------------------------------------------------------------------------
   Matches the "Already own this" tiles on product pages: link text in the
   scheme's link colour, arrow always red. On light schemes the link is red
   already, so this only changes dark bands. Left alone where red would
   vanish or clash: the red scheme, red cards, and image-overlay cards
   (cream on a photo).
   ========================================================================== */

[data-rvs="card-grid"] .rvs__card-cta::after { color: var(--rvs-red); }

[data-rvs="card-grid"][data-scheme="red"] .rvs__card-cta::after,
[data-rvs="card-grid"][data-card-colour="red"] .rvs__card-cta::after,
[data-rvs="card-grid"] .rvs__card--overlay .rvs__card-cta::after { color: inherit; }

/* Build 67.3: anchor targets land below a sticky header (POWER's on the
   public site, the portal bar on the portal). */
[data-rvs="card-grid"][id] { scroll-margin-top: calc(var(--lo-header-height, 85px) + 16px); }


/* ==========================================================================
   Build 72 — Overlay title size
   --------------------------------------------------------------------------
   The overlay title is fixed at the display size (rvs-shared.css, 0,3,0) and
   Card text size deliberately leaves it alone, so the homepage sector tiles
   can't shrink by accident. This is a separate choice that only overlay
   cards read. Default sets nothing, so existing grids are unchanged.

   The extra [data-rvs="card-grid"][data-overlay-title] attributes put these
   at 0,5,0, well above the shared rule whatever the load order.

   Fit to card: the card is its own size container and the title scales with
   its width (cqi), capped at the display size. At 8cqi a ten-letter word in
   Owners Wide Black (QUEENSLAND, NORTHLAND) fits inside the card padding.
   Browsers without container units ignore the declaration and keep Default.
   ========================================================================== */

[data-rvs="card-grid"][data-overlay-title="medium"] .rvs__card--overlay .rvs__card-title {
  font-size: clamp(1.25rem, 1rem + .8vw, 1.5rem);     /* 20 -> 24 */
  line-height: 1.12;
  text-wrap: balance;
}

[data-rvs="card-grid"][data-overlay-title="small"] .rvs__card--overlay .rvs__card-title {
  font-size: clamp(1.0625rem, .95rem + .4vw, 1.25rem); /* 17 -> 20 */
  line-height: 1.15;
  text-wrap: balance;
}

@supports (width: 1cqi) {
  [data-rvs="card-grid"][data-overlay-title="fit"] .rvs__card--overlay { container-type: inline-size; }
  [data-rvs="card-grid"][data-overlay-title="fit"] .rvs__card--overlay .rvs__card-title {
    font-size: clamp(1rem, 8cqi, var(--rvs-display-l));
    line-height: 1.1;
    text-wrap: balance;
  }
}
