/* features-grid — the ethos band: a responsive grid of short feature
 * entries, `columns`-wide on desktop (default 3) and collapsing to one
 * column on mobile. Each entry IS a lockup (an xs box stepping the title
 * down the ramp to h3), so the eyebrow marker, title, and desc all ride the
 * shared vocabulary — this sheet is layout geometry ONLY.
 *
 * Each entry's image is optional (an empty img hides for free) and rides
 * three more axes: `content-order` places it above or below the copy (a flex
 * `order` on the media wrapper), `image-crop` sets the aspect ratio on
 * `.sk-feature-media` — the img fills the wrapper via object-fit — and
 * `image-style` can read the media as an AVATAR (a small circular portrait)
 * instead of column-width photography. An imageless entry may carry a
 * `monogram` instead — authored initials in a hairline circle. The
 * `marker-placement` axis re-seats each entry's marker below the title as a
 * quiet qualifier line (a role under a name — the team-directory posture).
 * The `ordinals` axis numbers the entries with a design-owned counter in the
 * marker's slot — the how-it-works sequence posture. */

/* The avatar/monogram mark size and the ordinal numeral scale — ×-body per
 * the ratio rule, never raw px. */
.sk-features-grid {
  --sk-avatar-size: calc(var(--kit-type-base) * 5.5);
  --sk-ordinal-size: calc(var(--kit-type-base) * 2.5);
}

/* THE BODY VOICE — entryTitleStyle=body switches entry titles to the sans
 * body face (bold) while the entries otherwise keep the lockup vocabulary. */
.sk-features-grid[data-entry-title-style="body"] .sk-feature-entry .sk-lockup-title {
  font-family: var(--kit-font-body);
  font-style: var(--kit-slant-body);
  text-transform: var(--kit-caps-body);
  font-size: var(--kit-type-base);
  line-height: var(--kit-lead-body);
  font-weight: calc(var(--kit-weight-body) + 200);
  letter-spacing: calc(var(--kit-track-body) * 1em);
}

/* ── Marker placement — `below` re-seats the eyebrow marker UNDER the title
 * as a quiet qualifier line (a role under a name). The mechanism is the one
 * content-order already uses on the media: flex `order` on the box's
 * children. The title joins the media's -1 band — DOM order keeps media
 * before title within it — and the marker and body follow at 0. The
 * per-adjacency rhythm dials travel with the ADJACENCY, not the element:
 * the label gap now tightens title → marker, and the title gap opens
 * marker → body, so the dialed rhythm survives the re-seat. */
.sk-features-grid[data-marker-placement="below"]
  .sk-feature-entry
  .sk-lockup-box
  > .sk-lockup-title {
  order: -1;
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-label, 0.5) - 1));
}
.sk-features-grid[data-marker-placement="below"]
  .sk-feature-entry
  .sk-lockup-box
  > :is(.sk-eyebrow, .sk-detail) {
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-title, 1) - 1));
}

/* ── The ORDINALS axis — data-ordinals="true" numbers the entries in order.
 * Ported from the retired process draft: the numeral is DESIGN-OWNED — a CSS
 * counter in the title voice at the draft's ×-body scale, muted ink, leading
 * zeros keeping every ordinal one width so row tops stay steady from "01"
 * through "10" — never authored, so a sequence can never drift or misnumber.
 * The counter renders as the box's ::before, seating it in the marker's slot
 * with the marker's own label rhythm (the base.css eyebrow adjacency); any
 * AUTHORED marker rests while the toggle is on — design-owned means authored
 * markers lose. The numeral holds the classic ABOVE slot under BOTH
 * markerPlacement values — a sequence mark is an ordinal eyebrow, not a
 * qualifier line — so the below re-seat unwinds here and the manifest hides
 * the placement knob while ordinals is on (visibleWhen). */
.sk-features-grid[data-ordinals="true"] .sk-features-grid-entries {
  counter-reset: sk-ordinal;
}
.sk-features-grid[data-ordinals="true"] .sk-feature-entry .sk-lockup-box::before {
  counter-increment: sk-ordinal;
  content: counter(sk-ordinal, decimal-leading-zero);
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  font-size: var(--sk-ordinal-size);
  font-weight: var(--kit-weight-title);
  letter-spacing: calc(var(--kit-track-title) * 1em);
  line-height: 1;
  color: var(--sk-ink-muted);
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-label, 0.5) - 1));
}
.sk-features-grid[data-ordinals="true"] .sk-feature-entry .sk-eyebrow[data-sk-field="marker"] {
  display: none;
}
.sk-features-grid[data-ordinals="true"][data-marker-placement="below"]
  .sk-feature-entry
  .sk-lockup-box
  > .sk-lockup-title {
  order: 0;
  margin-bottom: calc(var(--sk-lockup-gap) * (var(--sk-rhythm-title, 1) - 1));
}

/* Entry title links — quiet underline on hover, glyph trails the label. */
.sk-features-grid .sk-feature-entry-link {
  color: inherit;
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: left bottom;
  background-size: 0% 1px;
  padding-bottom: 2px;
  transition: background-size 0.35s var(--sk-ease-out);
}
.sk-features-grid .sk-feature-entry-link:hover {
  background-size: 100% 1px;
}
.sk-features-grid .sk-feature-entry-glyph {
  margin-inline-start: 0.2em;
}

/* The entries ride the shared CELL GRID (base.css — the count-aware fit
 * ladder): --sk-columns is stamped inline per-instance (features-grid.html)
 * from the `columns` prop, --sk-cell-count from the features list length at
 * compose time; features-grid.js re-mints --sk-active-columns as the same
 * min(count, dial, fit) for live pages (repeat() takes an <integer> — the
 * script's literal is the authoritative path, the sheet's min() the no-JS
 * fallback). The ladder itself — the rungs, the gap, the three caps — is
 * base.css vocabulary now; this sheet carries only what is features-grid's
 * own. */

/* ── The card surface — the opt-in `card` prop. The skeleton itself — fill,
 * hairline, radius, inset — lives in base.css's CARD SURFACE home, where the
 * arm's :has() chain scopes the chrome to text+image lockup entries only.
 *
 * Automatic card scope:
 *   text+image lockup → entry-level card (copy and media inside the surface)
 *   text-only         → no card chrome
 *   imageFill         → no card chrome (image-only tile)
 *   media-only        → no card chrome */

/* ── The entry image wrapper — order (content-order) and crop (image-crop).
 * The wrapper owns POSITION and clipping only; the ratio rides the image
 * atom's own --sk-image-aspect var, set here and inherited down to the media
 * element (img or the video-media pass's <video> twin), so base.css's
 * :is(img, video).sk-image keeps owning width / object-fit / radius. That
 * is the atom's documented seam ("layout owns the ratio, the image may
 * override, no specificity fight") — authoring aspect-ratio on the img here
 * instead would win over an image that declares its own data-aspect. */
.sk-features-grid .sk-feature-media {
  display: block;
  width: 100%;
  overflow: hidden;
}
/* The photo pane is a .sk-media BOX (true focal cropping — the img inside
 * is an absolute fill, so the pane must be extrinsically sized): the ratio
 * is the same --sk-image-aspect var the crop rules below already set,
 * falling back to the crop axis's own 3:2 default. The monogram pane is
 * not a media box and keeps sizing from its mark rules. */
.sk-features-grid .sk-media.sk-feature-media {
  aspect-ratio: var(--sk-image-aspect, 3 / 2);
}
.sk-features-grid .sk-feature-media:has(.sk-image[src=""]) {
  display: none;
}

/* ── COORDINATED LAYOUTS keep ROW-CONSISTENT media heights. Under a drawn
 * grid layout (data-grid) cells in one row have DIFFERENT widths, and the
 * aspect-ratio path above sizes each photo from its own cell — a spanning
 * cell's photo grows taller and the row's baselines break. So while a spec
 * is armed, every photo's height is computed from the ONE-TRACK measure
 * instead: the grid becomes the measuring container (cq units; the .sk-media
 * boxes inside are size containers of their own, so their focal-crop math is
 * untouched), and height = track width / the entry's own crop ratio —
 * identical across the row whatever each cell spans. --sk-grid-cols is
 * stamped by base.js's coordinated-grid pass (job 2e); the fallback rides
 * the uniform ladder's resolved count so a scripts-off render (where no
 * spec ever arms) computes the same height the aspect path would. Gated to
 * the same ≥900px rung as the spec itself — below it the fit ladder owns
 * the collapse and cells are uniform again. Avatar media (a fixed-size
 * mark) and imageFill tiles (which fill their cell by design) keep their
 * own sizing. */
@media (min-width: 900px) {
  .sk-features-grid .sk-features-grid-entries[data-grid] {
    container-type: inline-size;
  }
  .sk-features-grid:not([data-image-style="avatar"])
    .sk-features-grid-entries[data-grid]
    .sk-feature-entry:not([data-image-fill="true"])
    .sk-media.sk-feature-media {
    height: calc(
      (100cqw - (var(--sk-grid-cols, var(--sk-active-columns, 1)) - 1) * var(--kit-space-4)) /
        var(--sk-grid-cols, var(--sk-active-columns, 1)) / (var(--sk-image-aspect, 3 / 2))
    );
  }
}

.sk-features-grid[data-content-order="media-first"] .sk-feature-media {
  order: -1; /* image above the copy */
}
/* Content-first: pin the image to the BOTTOM of the box so images line up
 * across a row even when entries carry different amounts of copy. The entry
 * (a grid item, stretched to the row's tallest) becomes a flex column so its
 * box fills that height; the image's auto top-margin then drops it to the
 * bottom edge. */
.sk-features-grid[data-content-order="content-first"] .sk-feature-entry {
  display: flex;
  flex-direction: column;
}
.sk-features-grid[data-content-order="content-first"] .sk-feature-entry .sk-lockup-box {
  flex: 1; /* box fills the stretched entry height */
}
.sk-features-grid[data-content-order="content-first"] .sk-feature-entry .sk-feature-media {
  order: 1;
  margin-top: auto; /* push the image to the bottom of the box */
}
/* The crop's ratio comes from base.css's ASPECT TOKEN TABLE (--sk-aspect,
 * resolved on the root data-image-crop rides). Same rank as the old
 * per-value arms, still BEFORE the avatar rule below, whose source-order
 * win over a stale sidecar crop is load-bearing. */
.sk-features-grid[data-image-crop] .sk-feature-media {
  --sk-image-aspect: var(--sk-aspect);
}

/* ── Image style — `avatar` reads the entry media as a MARK, not content
 * photography (the image atom's documented mark exemption): a small circular
 * portrait instead of a column-width photo. The manifest pins imageCrop to
 * 1:1 through `forces`; the aspect here is the render backstop, after the
 * crop stamps in source order so a stale sidecar crop can't out-draw the
 * pin. The circle rides the atom's own radius var — no specificity fight
 * with base.css — and the size rides the macro type dial. */
.sk-features-grid[data-image-style="avatar"] .sk-feature-media {
  --sk-image-aspect: 1 / 1;
  --sk-media-radius: 50%;
  width: var(--sk-avatar-size);
}

/* ── The monogram — the imageless entry's mark: authored initials in a
 * hairline circle (the repeater renders it only when the row has no image).
 * It rides .sk-feature-media, so content-order and the avatar rules place
 * it exactly like a photo; as a mark it is ALWAYS avatar-scale, whatever
 * the section's image style. Title voice at body scale, muted ink — the
 * name beside it keeps the full register. */
.sk-features-grid .sk-feature-monogram {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--sk-avatar-size);
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  border: var(--sk-border-width) solid var(--sk-stroke);
  font-family: var(--kit-font-title);
  font-style: var(--kit-slant-title);
  font-size: calc(var(--kit-type-base) * 1.15);
  font-weight: var(--kit-weight-title);
  letter-spacing: calc(var(--kit-track-title) * 1em + 0.04em);
  color: var(--sk-ink-muted);
}

/* ── Image fill (imageFill) — image-only entries fill their grid cell; never
 * receive card chrome (see automatic card scope above). */
.sk-features-grid .sk-feature-entry[data-image-fill="true"] {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.sk-features-grid .sk-feature-entry[data-image-fill="true"] .sk-lockup-box {
  height: 100%;
  gap: 0;
}
.sk-features-grid .sk-feature-entry[data-image-fill="true"] .sk-feature-media {
  height: 100%;
  flex: 1;
}
/* Fill drops the crop rather than re-authoring the img: clearing the atom's
 * ratio var lets the photo take the cell's height, and object-fit: cover is
 * already the atom's own default — no need to restate it. */
.sk-features-grid .sk-feature-entry[data-image-fill="true"] .sk-feature-media {
  --sk-image-aspect: auto;
}
.sk-features-grid .sk-feature-entry[data-image-fill="true"] .sk-feature-media .sk-image {
  height: 100%;
}

/* The ALIGN axis — the symmetrical stance is a stance for the whole band.
 * The entry is a lockup, so centering its box centers the flex items with it,
 * and the copy's measure re-centers instead of hanging left. */
.sk-features-grid[data-align="center"] .sk-feature-entry {
  text-align: center;
}
.sk-features-grid[data-align="center"] .sk-feature-entry .sk-lockup-box {
  align-items: center;
}
.sk-features-grid[data-align="center"] .sk-feature-entry .sk-lockup-desc {
  margin-inline: auto;
}
