/* =========================================================================
   template-parts/sections/section-case-bento.php
   Moved out of the partial's inline <style id="es-case-bento-css"> on the owner's
   instruction, 10 Sep 2026. en_print_css() (inc/css-loader.php) prints this
   file's <link> where the <style> used to be, once per page, so every page
   that renders this section shares this one file.
   ========================================================================= */

/* ── Head ───────────────────────────────────────────────────
   Title and lede on the left, the supporting line and the
   archive link on the right, both bottom-aligned so they read
   as one band. The global head caps itself at 780px for a
   single readable column; a head that spans two columns has to
   give that up, so the cap moves onto the paragraphs. */
.es-section-head.es-cb__head {
  max-width: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--gap-fluid-sm);
  align-items: end;
  text-align: left;
}

.es-section-head.es-cb__head .es-lede { margin: 0; max-width: 46ch; }

@media (min-width: 992px) {
  .es-section-head.es-cb__head {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
    gap: var(--gap-fluid-xl);
  }
}

.es-cb__aside { display: flex; flex-direction: column; align-items: flex-start; gap: var(--gap-fluid-sm); }

.es-cb__aside p {
  margin: 0;
  max-width: 38ch;
  color: var(--text-body);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
}

/* ── The aside sits in the section's right corner ───────────
   Only where the head is actually two columns. Below 992px the
   head is a single column and the aside stacks under the title,
   so a button pushed to the right edge there would be a lone
   control floating away from the copy it belongs to. It stays
   left-aligned at those widths, which is why this lives in its
   own query rather than on the base rule.

   `flex-end` rather than `margin-left: auto` on the button: the
   aside can also carry a supporting line, and the two have to
   land on the same right edge or they stop reading as one
   lockup. Aligning the container does both at once.

   The right edge it lands on is the grid's last column, which
   ends at .es-container's inline padding. That is the section's
   right corner, not the viewport's, so the button keeps the
   same gutter as everything else on the page. */
@media (min-width: 992px) {
  .es-cb__aside { align-items: flex-end; }

  /* The line goes with it. Left-aligned copy hung on a
     right-aligned edge leaves its last line short of the button
     above it, which reads as a misalignment rather than as a
     ragged edge. It is one short sentence capped at 38ch, so at
     most two lines are set this way. */
  .es-cb__aside p { text-align: right; }
}

/* ── Button only: on the title's line ───────────────────────
   Owner's instruction, 14 Sep 2026. When the right column holds
   just the "view all" button (no supporting line), the button sits
   level with the title instead of in the bottom corner.

   The left wrapper steps out of the way with `display: contents`
   so the eyebrow, title and lede become rows of the head's own
   grid, and the button takes the title's row, centred on it. That
   holds when the title wraps, which a fixed offset would not. Row
   gap is 0 so the rhythm stays the eyebrow's own margin, exactly as
   it is in block flow. Below 992px the head is one column and none
   of this applies. */
@media (min-width: 992px) {
  .es-section-head.es-cb__head--solo {
    align-items: start;
    row-gap: 0;
  }

  .es-cb__head--solo > div:first-child { display: contents; }

  .es-cb__head--solo > div:first-child > * { grid-column: 1; }
  .es-cb__head--solo .es-eyebrow { grid-row: 1; }
  .es-cb__head--solo > div:first-child > h2,
  .es-cb__head--solo > div:first-child > h3 { grid-row: 2; }
  .es-cb__head--solo .es-lede { grid-row: 3; }

  .es-cb__head--solo .es-cb__aside--solo {
    grid-column: 2;
    grid-row: 2;
    align-self: center;
  }
}

/* ── Bento ──────────────────────────────────────────────────
   Mobile first: one column, every card the same. The mixed
   sizes only start where there is width to spend on them. */
.es-cb__grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .es-cb__grid { grid-template-columns: repeat(2, 1fr); }
  /* The hero keeps its emphasis at the tablet step by running
     the full width rather than by growing taller. */
  .es-cb__card--hero { grid-column: span 2; }
}

@media (min-width: 992px) {
  /* Hero two-by-two in the top left, two stacked beside it, three
     across the bottom. Rows are sized here rather than left to the
     content, so the wall keeps its shape whatever the titles do. */
  .es-cb__grid {
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: 220px;
    gap: var(--gap-fluid-sm);
  }

  .es-cb__card--hero {
    grid-column: span 2;
    grid-row: span 2;
  }
}

/* ── Card ───────────────────────────────────────────────────
   The whole card is the link. Its own stacking context, so the
   image, the scrims and the copy layer cannot be reordered by
   anything outside it. */
.es-cb__card {
  position: relative;
  isolation: isolate;
  display: block;
  overflow: hidden;
  min-height: 220px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-surface);
  text-decoration: none;
  transition: transform var(--duration-normal) var(--ease-out),
              box-shadow var(--duration-normal) var(--ease-out),
              border-color var(--duration-normal) var(--ease-out);
}

.es-cb__card:hover {
  transform: translateY(-4px);
  border-color: var(--brand-300);
}

.es-cb__card:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 3px;
}

.es-cb__img {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center top;
  background: var(--bg-surface);
}

/* Resting scrim, rendered only on a card that actually has a resting
   layer. The image is whatever the client's photography happens to be,
   so a logo sitting on it cannot depend on that image being dark, and
   this guarantees the bottom of the card carries the mark no matter
   what is behind it.

   It is not rendered on a card with nothing at rest, which today is all
   of them: there the scrim was dimming the project image to protect
   copy that was not there. The hover darkening is unaffected, since the
   overlay paints its own gradient. */
.es-cb__veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to top,
    rgba(22, 20, 16, 0.92) 0%,
    rgba(22, 20, 16, 0.72) 26%,
    rgba(22, 20, 16, 0.30) 56%,
    rgba(22, 20, 16, 0.16) 100%
  );
}

/* ── Resting state ──────────────────────────────────────────
   Client name, or the logo once one is set, plus the chip. */
.es-cb__rest {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5);
  transition: opacity var(--duration-normal) var(--ease-out),
              transform var(--duration-normal) var(--ease-out);
}

.es-cb__logo { display: block; width: auto; max-width: 148px; height: 30px; object-fit: contain; }

/* The resting label that used to sit here is gone: the card rests as the
   logo alone, and the title now lives only in the overlay. */

.es-cb__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px var(--space-4);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(6px);
  color: var(--text-inverse);
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: 1.4px;
  text-transform: uppercase;
}

/* ── Overlay ────────────────────────────────────────────────
   Shown by default. Only a device with a real pointer hides it
   and brings it back on hover, so touch and keyboard users are
   never left with content they cannot reach. */
.es-cb__overlay {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* Without this the column stretches every child to the card's full
     width, which turns the chip and the "View case study" row into
     800px-wide bars instead of shrink-to-fit pills. */
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5);

  /* Lightened to match the industries wall, on the owner's instruction. It
     used to run 0.72 at the top of the card to 0.95 at the bottom, which is
     not a scrim over a photograph, it is a near-solid panel that happens to
     be sitting on one: on hover the project image effectively disappeared
     and the card read as a block of text.

     The curve now follows section-industries.php's veil: heavy at the
     bottom where the copy sits, falling away fast so the top two thirds of
     the image stay readable. It does not fall to zero the way that one
     does, and that difference is deliberate rather than an oversight. An
     industries tile carries a single label on its bottom edge, so its scrim
     can end at 78 percent. This overlay carries a chip, a title of up to
     three lines, the KPI row and the "View case study" cue, and on a 220px
     card that stack reaches roughly 70 percent of the card height. A scrim
     that had already run out by then would leave the chip as white text on
     whatever the client's photography happens to be, which is the one thing
     a scrim exists to prevent. The 0.24 floor is what keeps the top of the
     stack legible on a light image while still showing the image. */
  background: linear-gradient(
    to top,
    rgba(22, 20, 16, 0.88) 0%,
    rgba(22, 20, 16, 0.70) 26%,
    rgba(22, 20, 16, 0.46) 58%,
    rgba(22, 20, 16, 0.24) 100%
  );
  transition: opacity var(--duration-normal) var(--ease-out);
}

/* The chip and the cue shrink to fit, but the title is the one child that
   should still wrap against the full card width, so it opts back in. */
.es-cb__title {
  align-self: stretch;
  margin: 0;
  color: var(--text-inverse);
  font-size: var(--fs-body);
  font-weight: var(--font-bold);
  line-height: var(--lh-subhead);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.es-cb__card--hero .es-cb__title { font-size: var(--fs-title-sm); -webkit-line-clamp: 4; }

.es-cb__kpis { display: flex; flex-wrap: wrap; gap: var(--gap-fluid-sm); margin: 0; padding: 0; list-style: none; }

.es-cb__kpi-value {
  display: block;
  color: var(--brand-300);
  font-size: var(--fs-title-sm);
  font-weight: var(--font-extrabold);
  line-height: 1;
}

.es-cb__kpi-label {
  display: block;
  margin-top: 2px;
  color: var(--gray-300);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-subhead);
}

.es-cb__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--brand-300);
  font-size: var(--fs-body-sm);
  font-weight: var(--font-semibold);
}

.es-cb__go svg { transition: transform var(--duration-normal) var(--ease-out); }

.es-cb__card:hover .es-cb__go svg { transform: translateX(4px); }

/* Pointer devices only: rest closed, open on hover or when the card
   takes focus. Everywhere else the overlay simply stays open. */
@media (hover: hover) and (pointer: fine) {
  .es-cb__overlay { opacity: 0; }

  /* Split into one rule per state rather than a single comma-separated
     list, deliberately. A selector list is dropped in full if any one of
     its selectors fails to parse, so listing :focus-visible alongside the
     others would mean an engine that does not know it silently loses the
     hover and focus-within reveals as well, and the detail would become
     unreachable rather than merely un-animated. Separated, each state
     stands or falls on its own. */
  .es-cb__card:hover .es-cb__overlay { opacity: 1; }
  .es-cb__card:focus-within .es-cb__overlay { opacity: 1; }
  .es-cb__card:focus-visible .es-cb__overlay { opacity: 1; }

  .es-cb__card:hover .es-cb__rest { opacity: 0; transform: translateY(6px); }
  .es-cb__card:focus-within .es-cb__rest { opacity: 0; transform: translateY(6px); }
  .es-cb__card:focus-visible .es-cb__rest { opacity: 0; transform: translateY(6px); }
}

/* With the overlay always open, the resting layer underneath is
   duplicate information, so it is dropped rather than stacked. */
@media not all and (hover: hover) {
  .es-cb__rest { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .es-cb__card,
  .es-cb__img,
  .es-cb__rest,
  .es-cb__overlay,
  .es-cb__go svg { transition: none; }
  .es-cb__card:hover { transform: none; }
  .es-cb__card:hover .es-cb__img { transform: none; }
}
