/* ============================================================
   Feature cards — the row owns the grid and the gap; the cards carry no
   margin. Ported from the mockup's .pfeatures / .pfeature (styles/product.css).

   Both the row and the card are styled here, the way metrics/style.css holds
   .vt-metric: one sheet per component, so a card can never load without the
   grid it lives in.

   Deltas from the mockup, deliberate:
     · the default grid FITS the number of cards (auto-fit) instead of always
       cutting three tracks, so a two-card row fills the band instead of leaving
       a hole. The mockup's fixed three is still available as columns: 3;
     · the row is a <ul> and each card an <li>, not divs — see render.php.
   ============================================================ */
.vt-cards {
  list-style: none;
  display: grid;
  /* 280px, not the key figures' 160px: these cards hold a heading and a
     sentence or two, and three of them on the 1146px band resolve to the
     mockup's 370px tracks. */
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 18px;
  margin: 0;
  padding: 0;
  /* The row spans its column. Not optional: a section's content stack is a flex
     column with align-items: flex-start (that is how the eyebrow and title stay
     shrink-to-fit and follow --vt-align), so without this the grid inherits it
     and collapses to a single min-content track. See BLOCKS.md trap 9. */
  width: 100%;
}
/* "One — stacked" is the mockup's .pillars--stack: the ruled pillars beside a
   photo in a split section. auto-fit would often land on one track there too,
   but only by ~10px of column width — a stack an editor asked for must not
   become two columns because a gap resolved differently. */
.vt-cards--cols-1 { grid-template-columns: minmax(0, 1fr); }
.vt-cards--cols-2 { grid-template-columns: repeat(2, 1fr); }
.vt-cards--cols-3 { grid-template-columns: repeat(3, 1fr); }
.vt-cards--cols-4 { grid-template-columns: repeat(4, 1fr); }

/* ---- the card edge: the ROW decides, the card reads ----
   inc/card-border.php carries the full argument. The edge lives on an inherited
   custom property because the `Card border` toggle is on the ROW and the card
   is a DIFFERENT block (visitech/card) rendering its own wrapper — the row
   cannot put a class on it. :where() drops the dark restatements to their
   light counterparts' weight, which leaves both bare rules strictly higher and
   winning by specificity rather than by load order (trap 2). */
.vt-cards                             { --vt-card-edge: var(--color-border); }
:where(.vt-section.is-dark) .vt-cards { --vt-card-edge: color-mix(in srgb, #ffffff 14%, transparent); }
.vt-cards.vt-cards-bare               { --vt-card-edge: transparent; }
/* the hover is set ON THE CARD, so it beats the value coming down from the row.
   A bare row therefore has to state the hover case too, or the edge the editor
   switched off would come back under the pointer. */
.vt-cards.vt-cards-bare .vt-card:hover { --vt-card-edge: transparent; }

.vt-card {
  margin: 0;
  padding: 30px 28px;
  /* the fallback keeps a card rendered outside its row — a stray atom, a
     reusable block — on the mockup's hairline instead of no edge at all */
  border: 1px solid var(--vt-card-edge, var(--color-border));
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: 0 10px 28px color-mix(in srgb, var(--color-ink) 5%, transparent);
  transition:
    border-color var(--dur-base) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard);
}
.vt-card:hover {
  --vt-card-edge: var(--color-light-purple-40);
  box-shadow: 0 14px 34px color-mix(in srgb, var(--color-ink) 9%, transparent);
}

/* The icon tile. A real element around a real <svg> — the glyph is decorative
   (the heading right below it carries the meaning), so inc/icons.php's helper
   marks it aria-hidden and it is never announced twice. */
.vt-card__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 20px;
  border-radius: 14px;
  background: var(--gradient-button);
  color: #ffffff;
}
/* Beats the width/height attributes wp_get_icon() writes onto the <svg>. */
.vt-card__icon svg {
  width: 26px;
  height: 26px;
}

/* ---------------------------------------------------------------------------
   THE PICTURE CARD (2026-09-18)

   Same slot as the icon tile, different job. A product row wants the unit's
   photograph at card width, not a 52px badge — so `.vt-card__media` is a BAND
   across the top of the card rather than a tile inside its padding.

   That is why the pictured card zeroes its own top padding and the band pulls
   itself out to the card's edges with negative margins: the alternative is a
   second card class with a different box, and this way one `.vt-card` keeps one
   border, one radius and one hover.

   THE BAND IS SQUARE, AND THAT IS MEASURED, NOT CHOSEN. Every product render in
   the library is square — 993×1024 (ratio 0.97) for the whole LRS-MCx set, the
   µSLA, the motion stage and the LLS; 1122×1122 and 1357×1357 for the two EOL
   heads. A square band with `object-fit: contain` therefore fills 97–100% of
   itself with NO cropping, which is the only geometry that gets both.

   The first cut of this used `height: 180px` with 18px of padding, which
   letterboxed a square render into a wide short box and left it occupying about
   46% of the card's width, adrift in grey. If a non-square image ever lands
   here, `contain` still shows all of it — it just leaves margin, which is the
   safe failure.

   NO BACKGROUND BEHIND IT. The renders are a mix: four are RGBA with
   transparency, the rest carry their own white or dark backdrop. A tinted band
   under that mix shows as a grey frame around every white-backed render and a
   grey margin beside every transparent one. Letting the CARD's surface show
   through makes the white-backed ones seamless and the transparent ones sit on
   white, which is what the originals were composed against.

   `contain`, not `cover`: the ratios are near enough to square that `cover`
   would crop almost nothing today, but the moment a landscape render arrives it
   would cut through the middle of the machine. Not worth 3%.
   --------------------------------------------------------------------------- */
.vt-card--pictured {
  padding-top: 0;
  overflow: hidden;
}
.vt-card__media {
  display: block;
  /* out to the card's edges, undoing `.vt-card`'s 28px side padding */
  margin: 0 -28px 22px;
}
.vt-card__img {
  display: block;
  width: 100%;
  /* `height: auto` is required: wp_get_attachment_image() writes width and
     height ATTRIBUTES, and without this the intrinsic height wins over the
     aspect ratio and the box is not square at all. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: contain;
}

/* The `rule` preset has no card box at all — no border, no surface, no padding —
   so there is nothing to pull out of. */
.vt-cards--rule .vt-card--pictured { padding-top: 0; }
.vt-cards--rule .vt-card__media { margin-inline: 0; }

/* A picture inside a link card must not eat the pointer or the focus ring: the
   anchor is the whole card, and the image is its content. */
.vt-card--link .vt-card__media { pointer-events: none; }

/* The zoom-on-hover the mockup gives product tiles. Contained by the card's
   own `overflow: hidden` above. */
.vt-card--link.vt-card--pictured .vt-card__img {
  transition: transform var(--dur-base) var(--ease-standard);
}
.vt-card--link.vt-card--pictured:hover .vt-card__img {
  transform: scale(1.03);
}
@media (prefers-reduced-motion: reduce) {
  .vt-card--link.vt-card--pictured .vt-card__img,
  .vt-card--link.vt-card--pictured:hover .vt-card__img {
    transition: none;
    transform: none;
  }
}

.vt-card__title {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: 19px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--vt-fg, var(--color-text));
  text-wrap: pretty;
}
.vt-card__text {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--vt-fg-muted, var(--color-text-muted));
  text-wrap: pretty;
}

/* ============================================================
   THE LINK CARD (2026-08-27) — the mockup's `.offering-card` (styles/home.css),
   which is an <a> around the whole card. render.php wraps the icon, heading,
   paragraph and link line in one anchor and leaves the inner-block body outside
   it; the <li> keeps the panel, so a link card and an ordinary card are the same
   object with the same padding, border and radius.

   The deltas from `.offering-card` that remain are the ones the whole `card`
   preset carries (19px/15px type and the drop shadow, vs the mockup's 22/16 and
   none) — the recorded open call about the card look, not this feature's.
   ============================================================ */
.vt-card__a {
  display: block;
  color: inherit;
  text-decoration: none;
}
/* THE HOVER UNDERLINE, and why this rule has to exist (Yoann, 2026-09-16, on
   the 404's six cards).

   base.css ends with `a:hover { text-decoration: underline }`. That is (0,1,1)
   and `.vt-card__a` above is (0,1,0), so on hover the anchor won — and because
   text-decoration PROPAGATES to descendants, the heading and the paragraph
   were underlined too. A whole card of underlined prose; nothing in the mockup
   does that, and the home page's own offering cards (which are an ordinary
   card with a link inside its text) never did.

   So the anchor keeps its word on hover and on keyboard focus, and the
   underline is given back to the one part that reads as a link. Both rules are
   (0,1,1) or higher and sit after the base sheet's, which is the same cascade
   the rest of this file relies on. */
.vt-card__a:hover,
.vt-card__a:focus,
.vt-card__a:focus-visible {
  text-decoration: none;
}
.vt-card--link:hover .vt-card__linktext,
.vt-card__a:focus-visible .vt-card__linktext {
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* The heading picks up a quiet brand shift on hover, the mockup's own signal
   that the card is a link. (0,2,0) on the row's `--link` class, so it never
   fires on an ordinary card. */
.vt-card--link .vt-card__title {
  transition: color var(--dur-base) var(--ease-standard);
}
.vt-card--link:hover .vt-card__title { color: var(--color-dark-purple); }

/* The mockup's `.offering-card__link`: 15px medium in the deep purple, with the
   arrow set apart. inline-flex and not inline, so the glyph cannot drop to its
   own line at a narrow container width. */
.vt-card__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: var(--fw-medium);
  line-height: 1.4;
  color: var(--color-dark-purple);
  transition: color var(--dur-base) var(--ease-standard);
}
.vt-card--link:hover .vt-card__link { color: var(--color-link-hover); }
.vt-card__link .arrow { transition: transform var(--dur-base) var(--ease-standard); }
.vt-card--link:hover .vt-card__link .arrow { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .vt-card--link:hover .vt-card__link .arrow { transform: none; }
}
/* On a dark band the deep purple disappears into the ink, the same problem
   `.btn--ghost` solves the same way (components.css). (0,3,0) beats the base. */
.vt-section.is-dark .vt-card__link { color: #ffffff; }
.vt-section.is-dark .vt-card--link:hover .vt-card__link { color: rgba(255, 255, 255, 0.78); }
.vt-section.is-dark .vt-card--link:hover .vt-card__title { color: #ffffff; }

/* The card's inner-block stack (2026-08-23) — eyebrow, text, checklist after
   the attribute fields. A flex column with its own gap, because the atoms
   carry no margins; the container owns the spacing. 14px is the mockup's own
   label-to-checklist distance (.luria__capslabel margin-bottom). An inner
   visitech/text wears the card's 15px, the way the card's own paragraph does —
   the container imposes the type its atoms wear. */
.vt-card__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  --vt-text-size: 15px;
}
/* :not(:first-child) lifts this to (0,2,0) — no cascade neighbour to fight,
   it only exists to keep a body directly under an icon/title/text apart. */
.vt-card__body:not(:first-child) { margin-top: 14px; }

/* On a dark section the white panel becomes a whisper of white, the same way
   the key figures do. `.vt-section.is-dark .vt-card` (0,3,0) out-specifies
   `.vt-card` (0,1,0), so it wins whatever order WordPress inlines the sheets
   in — block CSS lands BEFORE the theme's. See BLOCKS.md trap 2.

   The RESTING edge is not here: it comes down from the row, so one toggle
   governs both bands. The dark HOVER edge is, and it is wrapped in :where() to
   sit at the light hover's weight — the bare row's hover rule above is then
   strictly higher and cannot lose a specificity tie to it. */
.vt-section.is-dark .vt-card {
  background: color-mix(in srgb, #ffffff 4%, transparent);
  box-shadow: none;
}
:where(.vt-section.is-dark) .vt-card:hover {
  --vt-card-edge: color-mix(in srgb, #ffffff 28%, transparent);
}
/* the shadow keeps its full (0,3,0): only the EDGE needed lowering, and
   flattening this one too would leave it beating the light hover's shadow on
   source order alone. */
.vt-section.is-dark .vt-card:hover {
  box-shadow: none;
}

/* One column below 860px, whatever was asked for — the mockup's own call, and
   these cards hold prose, so two 170px tracks would be unreadable. Last, so it
   beats the --cols-* modifiers on equal specificity. */
@container vt-section (max-width: 860px) {
  .vt-cards,
  .vt-cards--cols-2,
  .vt-cards--cols-3,
  .vt-cards--cols-4 { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   The `rule` preset — the mockup's OTHER row of three: `.pillars`
   (styles/lithography.css, "Where LLS1000 fits" and "Why Visitech").
   A purple hairline over each column instead of a bordered, filled, shadowed
   panel, and a bigger heading, because without a card around it the type is
   the only thing giving the column its weight.

   These selectors are (0,2,0) — `.vt-cards--rule .vt-card` — so they beat the
   `.vt-card` base above them (0,1,0) whatever order WordPress inlines this
   sheet in, and the `.vt-section.is-dark .vt-card` override further down (also
   0,3,0 there) does not fight them: the dark rules only paint a border, a fill
   and a shadow, and every one of those is reset to `none` here.
   ============================================================ */
.vt-cards--rule { gap: 28px; }

.vt-cards--rule .vt-card {
  padding: 24px 0 0;
  border: 0;
  border-top: 1px solid var(--color-light-purple);
  border-radius: 0;
  background: none;
  box-shadow: none;
}
/* No hover lift: there is no card to lift. The mockup's `.pillar` has no hover
   state at all, and a moving hairline reads as a fault rather than an
   affordance. */
.vt-cards--rule .vt-card:hover {
  border-top-color: var(--color-light-purple);
  box-shadow: none;
}
/* The dark-section rules above paint the panel; on a ruled row there is none.
   Stated rather than inherited, because `.vt-section.is-dark .vt-card` (0,3,0)
   out-specifies `.vt-cards--rule .vt-card` (0,2,0) and would otherwise put the
   4%-white fill and its border back. */
.vt-section.is-dark .vt-cards--rule .vt-card,
.vt-section.is-dark .vt-cards--rule .vt-card:hover {
  border: 0;
  border-top: 1px solid var(--color-light-purple);
  background: none;
  box-shadow: none;
}

/* ============================================================
   The `panel` preset — the mockup's THIRD card look: `.luria-grid__caps`
   (styles/photoheads.css, the "Key capabilities" checklist beside the LURIA
   copy). The card's border and radius, but on the faint tinted fill with no
   shadow and no hover lift: this panel holds information standing beside a
   section's copy, and a card that lifts under the pointer promises a click
   this one never delivers. Same (0,2,0) weight and the same cascade argument
   as the `rule` preset above.
   ============================================================ */
.vt-cards--panel .vt-card {
  padding: 22px 26px;
  background: var(--color-bg-subtle);
  box-shadow: none;
}
/* A static panel must not react to the pointer, on any band. `unset` on an
   inherited custom property means INHERIT, so the card falls back to whatever
   edge the row decided — which is exactly "no reaction", in the light band, on
   a dark section and in a bare row alike. One rule replaces the pair this used
   to need (a light undo plus a (0,4,1) dark one), because there is no longer a
   colour to restate. */
.vt-cards--panel .vt-card:hover {
  --vt-card-edge: unset;
  box-shadow: none;
}

.vt-cards--rule .vt-card__title {
  margin-bottom: 12px;
  font-size: 22px;
}
.vt-cards--rule .vt-card__text { font-size: 16px; }

/* An icon tile inside a ruled column would sit on the hairline with nothing
   holding it, so the gradient square is dropped to a bare glyph in the brand
   purple. The row is meant to be used WITHOUT icons — the mockup's pillars have
   none — but a block must not break because somebody picked one. */
.vt-cards--rule .vt-card__icon {
  width: auto;
  height: auto;
  margin-bottom: 14px;
  border-radius: 0;
  background: none;
  color: var(--color-light-purple);
}
