/* ============================================================
   SINGLE CASE STUDY

   Built to case-study-preview.html, in this theme's vocabulary.

   The preview is written against its own palette (--orange,
   --ink, --muted, --border, a warm #F8F5EE panel). None of that
   is reproduced literally: every colour here is a semantic token,
   so the page follows the theme and is ready for dark mode. The
   mapping is one to one:

     #E85D20  ->  --brand-500        #121212  ->  --gray-900
     #666666  ->  --text-muted       #e8e4df  ->  --border-subtle
     #FFF5EF  ->  --brand-50         #F8F5EE  ->  --brand-50
     12px     ->  --radius-md        16px     ->  --radius-lg

   The block name is `es-study`, not the preview's `cs-`. There
   are ~140 stale `.cs-` rules still in style.css from the
   template this replaces, and reusing those names would mean
   inheriting whichever of them happened to still match.

   The media frame, chip, card, covering link and actions row
   come from es-collection.css, and the related cards at the foot
   are the same partial the index runs. Nothing here restates
   them.
   ============================================================ */

/* ------------------------------------------------------------
   Primitives this theme does not have yet. Here rather than in
   utilities.css because this is the only page using them so far;
   the day a second one does, they move up.
   ------------------------------------------------------------ */

.es-lede-lg {
  font-size: var(--fs-lede-lg);
  line-height: var(--lh-body);
}

/* ------------------------------------------------------------
   Shell
   ------------------------------------------------------------ */

.es-study {
  background: var(--bg-surface);
  color: var(--text-body);
}

/* Every band on the page is one of these two. The preview varies
   its section padding between 60 and 100px; the theme has one
   rhythm and this page uses it, so the case study reads like the
   rest of the site rather than like an import. */
.es-study__section {
  padding-block: var(--section-pad);
}

.es-study__section--tight {
  padding-block: var(--section-pad-sm);
}

.es-study__section--sunken {
  background: var(--bg-sunken);
}

/* `.es-btn--secondary` is painted `--bg-sunken` in utilities.css,
   so on a sunken band it is the same colour as the page and the
   "All case studies" pill at the foot of the study rendered as
   bare text with a chevron. The shared component is not
   repainted here, because it is the site-wide `.com-new-btn-white`
   twin and every migrated template reads it; the band this page
   puts it on gives it its edge back instead. The global fix is
   the owner's call. */
.es-study__section--sunken .es-btn--secondary {
  border-color: var(--border-default);
}

.es-study__section--sunken .es-btn--secondary:hover,
.es-study__section--sunken .es-btn--secondary:focus-visible {
  border-color: var(--brand-300);
}

/* Eyebrow, heading and optional lede. `.es-eyebrow` is the
   site-wide chip and is not redefined; only the gap under the
   pair belongs to this page. */
.es-study__head {
  margin-bottom: var(--section-head-gap);
}

.es-study__head h2 {
  max-width: 22ch;
  margin: var(--space-4) 0 0;
  color: var(--text-heading);
  font-size: var(--fs-section-title);
  font-weight: var(--font-extrabold);
  line-height: var(--lh-heading);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

.es-study__head .es-lede {
  margin: var(--space-4) 0 0;
  max-width: 60ch;
}

/* ------------------------------------------------------------
   Hero
   ------------------------------------------------------------ */

.es-study__hero {
  padding-top: var(--hero-pad-top);
}

/* -- Client mark and engagement bar ------------------------
   The client on the left, the engagement bar on the right, one
   row. The breadcrumb that used to sit above this is gone: its
   last item was the study's own title in grey, directly above
   the same title set as the page's h1. */

.es-study__topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-fluid-sm);
}

.es-study__client {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.es-study__client-tag {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: var(--font-semibold);
}

/* A near white plate with a hairline, not a dark one.

   Client marks arrive as whatever file the client had, and the
   sixteen in this library run from near black ink (measured
   luminance 14 and 28) to pale (161 and 174). The dark plate
   this replaces drew the black ones as an empty rectangle, which
   is what the Torque Dolly study shipped, and an opaque JPEG
   mark sat on it as a white box inside a black box. A light
   plate is the ground the majority of the corpus reads on and
   the one a JPEG's own white ground disappears into. */
.es-study__client-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 92px;
  min-height: 52px;
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
  color: var(--text-heading);
  font-size: var(--fs-small);
  font-weight: var(--font-bold);
}

.es-study__client-logo img {
  display: block;
  width: auto;
  max-width: 150px;
  height: 28px;
  object-fit: contain;
}

/* The bar: label over value. Every column is drawn from
   something the study stores, and a column with nothing behind
   it is dropped in PHP rather than drawn empty.

   A grid below the breakpoint, a divided row above it. Wrapping
   the flex row instead left the item that started the second
   line carrying the padding meant for a divider it no longer had
   on its left, so the second row sat indented under the first. */
/* `flex: 1 1 260px` so that when the row cannot hold the client
   mark and the bar side by side, the bar takes a line of its own
   at the container's full width. Left to size itself it wrapped
   onto a second line still only 207px wide, which put all four
   columns in a single stack. */
.es-study__bar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
  gap: var(--space-5) var(--gap-fluid-sm);
  flex: 1 1 260px;
  margin: 0;
}

.es-study__bar>div {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

@media (min-width: 992px) {
  .es-study__bar {
    display: flex;
    flex: 0 1 auto;
    align-items: stretch;
    gap: 0;
  }

  .es-study__bar>div {
    padding-inline: var(--gap-fluid-sm);
    border-right: 1px solid var(--border-subtle);
  }

  .es-study__bar>div:first-child {
    padding-left: 0;
  }

  .es-study__bar>div:last-child {
    padding-right: 0;
    border-right: 0;
  }
}

.es-study__bar dt {
  color: var(--text-muted);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.es-study__bar dd {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-small);
  font-weight: var(--font-bold);
}

/* -- Headline and headline figures -------------------------
   The figures track is `auto`, so on the fifteen studies that
   carry none it collapses to zero and the headline and its
   summary take the whole row rather than leaving half of it
   empty beside them. */

.es-study__hero-grid {
  display: grid;
  gap: var(--gap-fluid-md);
  padding-block: var(--space-5) var(--gap-fluid-sm);
}

/* Smaller than `--fs-hero`, which topped out at 67px and gave
   the longer titles in this library five lines before the
   mockup. The ladder has no step between `--fs-section-title`
   (47px) and the hero, and an h1 set at the same size as every
   h2 under it flattens the page, so the step is written here.
   This is the only page that needs it; the day a second one does
   it moves into the token file. */
.es-study__title {
  max-width: 22ch;
  margin: 0;
  color: var(--text-heading);
  font-size: clamp(32px, 3.4vw + 8px, 54px);
  font-weight: var(--font-extrabold);
  line-height: var(--lh-display);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

/* The accent inside a headline. Italic and brand, the same
   treatment `.es-hl` carries everywhere else on the site. */
.es-study__title em {
  color: var(--brand-500);
  font-style: italic;
}

.es-study__hero-lede {
  margin: var(--space-5) 0 0;
  max-width: 62ch;
  color: var(--text-body);
}

.es-study__kpis {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.es-study__kpis--overview {
  margin-top: var(--gap-fluid-md);
}

.es-study__kpi {
  flex: 1 1 140px;
  min-width: 140px;
  padding: var(--space-5) var(--space-6);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
  text-align: center;
}

.es-study__kpi-value {
  display: block;
  color: var(--brand-600);
  font-size: var(--fs-title-md);
  font-weight: var(--font-extrabold);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
}

.es-study__kpi-label {
  display: block;
  margin-top: var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

@media (min-width: 992px) {
  .es-study__hero-grid {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--gap-fluid-xl);
    align-items: end;
  }

  .es-study__kpis {
    flex-wrap: nowrap;
    flex-shrink: 0;
  }
}

/* -- Engagement facts --------------------------------------
   A ruled strip between the headline block and the mockup. */

.es-study__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  gap: var(--space-5) var(--gap-fluid-sm);
  margin: 0 0 var(--gap-fluid-lg);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

.es-study__meta>div {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.es-study__meta dt {
  color: var(--text-muted);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.es-study__meta dd {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-small);
  font-weight: var(--font-semibold);
}

/* -- Browser mockup ---------------------------------------
   The artwork is a full page screenshot, so it is shown in a
   window that scrolls rather than cropped to a band. The chrome
   is what says the window is a website and not a picture frame,
   and the address bar carries the client's own domain. */

.es-study__mockup {
  border: 1px solid var(--border-subtle);
  border-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--bg-surface);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.es-study__browser {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-sunken);
}

.es-study__dots {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

/* The traffic lights.

   These three are the only literal colours in this file. They
   are not theme colours and they must not follow the brand: they
   are a picture of a window control, the way the padlock beside
   the address is a picture of a padlock, and a reader reads them
   as "this is a browser" only because they are these values. */
.es-study__dots span {
  width: 11px;
  height: 11px;
  border-radius: var(--radius-full);
  background: var(--border-default);
}

.es-study__dots span:nth-child(1) {
  background: #FF5F57;
}

.es-study__dots span:nth-child(2) {
  background: #FFBD2E;
}

.es-study__dots span:nth-child(3) {
  background: #28C840;
}

.es-study__url {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 0;
  padding: 5px var(--space-4);
  border-radius: var(--radius-full);
  background: var(--bg-surface);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Two of the sixteen studies carry no domain. The pill goes
   rather than being filled with the post slug behind a padlock,
   and this keeps the row's proportions while it is away. */
.es-study__url--blank {
  background: none;
}

.es-study__url svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  color: var(--success-text);
}

.es-study__visit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  padding: 7px var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--brand-500);
  color: var(--gray-0);
  font-size: var(--fs-caption);
  font-weight: var(--font-bold);
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}

.es-study__visit:hover,
.es-study__visit:focus-visible {
  background: var(--brand-700);
  color: var(--gray-0);
  transform: translateY(-1px);
}

.es-study__visit svg {
  width: 12px;
  height: 12px;
}

/* The window itself. A fixed height with its own scroll: the
   picture is a whole page and the point is that a visitor can
   run down it without leaving this one.

   The bar is hidden rather than styled. style.css paints every
   scrollbar on the site with a brand-500 thumb, and inside a
   frame that is pretending to be a browser an orange rail down
   the right of the artwork reads as part of the artwork. The
   hint at the foot is what says the window scrolls. */
.es-study__window {
  position: relative;
  height: clamp(320px, 46vw, 620px);
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  background: var(--bg-surface);
  scrollbar-width: none;
}

.es-study__window::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

.es-study__window img {
  display: block;
  width: 100%;
  height: auto;
}

/* The hint sits over the foot of the window and is taken away by
   the script on the first scroll inside it. A white dissolve,
   not a dark one: the artwork behind it is a web page on a light
   ground, and a black scrim over the foot of it read as the
   picture being cut off rather than as the page continuing. */
.es-study__hint {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 96px;
  margin-top: -96px;
  padding-bottom: var(--space-4);
  background: linear-gradient(180deg, transparent 0%, var(--bg-surface) 82%);
  pointer-events: none;
  transition: opacity var(--duration-normal) var(--ease-out);
}

.es-study__hint span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-muted);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  animation: esStudyHintBounce 1.6s var(--ease-out) infinite;
}

.es-study__hint svg {
  width: 14px;
  height: 14px;
  color: var(--brand-500);
}

@keyframes esStudyHintBounce {

  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(4px);
  }
}

/* ------------------------------------------------------------
   Project overview

   Two panels of the same kind: the story on one, the technology
   on the other.

   The story used to be a brand-gradient card. The orange is gone
   on the owner's instruction (7 Sep 2026), and what replaced it
   is the panel already standing beside it rather than a second
   treatment invented for the occasion. Same box, same border,
   same corner, same shadow, same heading.

   It stays a panel rather than going back to bare prose. The
   prose used to run as body text in a 1.35fr column with the
   technology panel floating in the fraction next to it, and six
   of the sixteen studies split their technology line into a
   single badge, so that panel was 531px wide around one chip.
   Two panels give the row two objects of comparable weight; a
   paragraph beside a box gives it one object and one orphan.
   ------------------------------------------------------------ */

.es-study__overview {
  display: grid;
  gap: var(--gap-fluid-md);
  align-items: start;
}

/* The story panel. Every value here is the technology panel's,
   stated again rather than shared, because the two are separate
   objects that happen to agree and a change to one is not
   automatically a change to the other.

   The three token re-points this used to carry are gone with the
   gradient. They existed to paint the prose white on orange; on
   a surface panel the prose reads the page's own ink, which is
   also what makes the heading levels inside `about` come out
   right whatever the editor wrote, without the panel having to
   hold an opinion about them. */
.es-study__brief {
  padding: var(--gap-fluid-md);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  box-shadow: var(--shadow-md);
}

.es-study__brief>h3 {
  margin: 0 0 var(--space-5);
  color: var(--text-heading);
  font-size: var(--fs-card-title);
  font-weight: var(--font-bold);
}

.es-study__prose {
  color: var(--text-body);
}

.es-study__prose>*+* {
  margin-top: var(--space-4);
}

.es-study__prose h3 {
  margin-top: var(--gap-fluid-sm);
  color: var(--text-heading);
  font-size: var(--fs-card-title);
  font-weight: var(--font-bold);
  line-height: var(--lh-subhead);
}

.es-study__prose p {
  line-height: var(--leading-relaxed);
}

.es-study__prose strong {
  color: var(--text-heading);
  font-weight: var(--font-semibold);
}

/* `:not([class])`, because this block also holds lists that are
   components rather than prose. A classed list in here is
   somebody else's object and this rule leaves it alone. */
.es-study__prose :is(ul, ol):not([class]) {
  padding-left: var(--space-6);
  line-height: var(--leading-relaxed);
}

.es-study__prose :is(ul, ol):not([class]) li+li {
  margin-top: var(--space-3);
}

.es-study__prose :is(ul, ol):not([class]) li::marker {
  color: var(--brand-500);
  font-weight: var(--font-bold);
}

/* -- The technology panel --------------------------------- */

.es-study__panel {
  align-self: start;
  padding: var(--gap-fluid-md);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  box-shadow: var(--shadow-md);
}

.es-study__panel h3 {
  margin: 0 0 var(--space-5);
  color: var(--text-heading);
  font-size: var(--fs-card-title);
  font-weight: var(--font-bold);
}

.es-study__stack {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.es-study__stack li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  color: var(--text-heading);
  font-size: var(--fs-caption);
  font-weight: var(--font-semibold);
  transition: border-color var(--duration-normal) var(--ease-out),
    color var(--duration-normal) var(--ease-out),
    transform var(--duration-normal) var(--ease-out),
    box-shadow var(--duration-normal) var(--ease-out);
}

.es-study__stack li:hover {
  border-color: var(--brand-500);
  color: var(--brand-700);
  transform: translateY(-1px);
  box-shadow: 0 4px 10px -4px rgba(232, 93, 32, .35);
}

.es-study__stack li::before {
  content: "";
  width: 6px;
  height: 6px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--brand-500);
}

@media (min-width: 992px) {

  /* 1.15 / 0.85, the preview's split. Both tracks are fractions,
     so the panel is never wider than the story it annotates and
     the story keeps a reading measure. */
  .es-study__overview {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: var(--gap-fluid-md);
  }
}

/* ------------------------------------------------------------
   The challenge, and the work

   Built to the reference page the owner supplied on 7 Sep 2026
   (a Midoffice Data case study), in the theme's own palette and
   on the theme's own scale. Its `.cards` block is this band's
   requirements and its `.steps` block is the work band.

   Every value below is that page's value mapped onto the nearest
   token, so the layout is theirs and the paint is ours:

     ref 22px card heading   -> --fs-card-title  (20-22px)
     ref 24px step heading   -> --fs-title-sm    (23-31px)
     ref 16/17px body        -> --fs-body-sm     (17px)
     ref 1.6 line-height     -> --leading-normal (1.6)
     ref 22px radius         -> --radius-xl      (24px)
     ref 28px padding        -> --space-6        (32px)
     ref 18px gap            -> --space-4        (16px)
     ref #e6f7fe / #0071c1   -> --brand-50 / --brand-700
     ref 900px breakpoint    -> kept as-is, it is the reference's
                                own and matches nothing here

   What went with the old build: `.es-study__solutions-list`,
   `.es-study__part` and its 200px sticky side, `.es-study__accent`,
   `.es-study__cards` and `.es-study__steps`. The two halves are
   separate bands now, each with its own chip and heading, so
   there is nothing left for a label column to label.

   Both blocks are `<ol>`s and both print their ordinal in the
   markup rather than drawing it with a CSS counter. A counter
   would put the only copy of the number in a stylesheet, where
   it cannot be read out, searched or translated.
   ------------------------------------------------------------ */

/* -- The requirements ------------------------------------
   Two columns, which is the reference's `.cards` grid exactly,
   collapsing to one below 900px as it does.

   Not `auto-fit`. That was the first build and it opened to
   three and then four tracks as the container grew, which is a
   different design at every width; the reference commits to two
   and the cards are the better for having a fixed measure. */
.es-study__reqs {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 900px) {
  .es-study__reqs { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* An odd count leaves the last card alone on its row, and left
     in the first column it reads as a card that lost its pair.
     Centred, it reads as the end of the list.

     `:last-child:nth-child(odd)` is the whole test: the last card
     is only an odd-numbered child when the total is odd. Five
     cards match, four do not, and nothing has to count them.

     It spans both tracks and then takes one track's width back,
     which is why the width is written as the arithmetic rather
     than as 50%: half of the grid is half of the gap too, and a
     card that wide is wider than the ones above it. */
  .es-study__reqs > .es-study__req:last-child:nth-child(odd) {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc((100% - var(--space-4)) / 2);
  }
}

/* No `min-height`. The reference sets a 220px floor because its
   requirement runs to four lines; ours run to two, so the floor
   was buying about seventy pixels of nothing under every card.
   Grid still levels the cards across a row, so the row is even
   without a number being written down. */
.es-study__req {
  display: grid;
  align-content: start;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--duration-normal) var(--ease-out),
              transform var(--duration-normal) var(--ease-out),
              box-shadow var(--duration-normal) var(--ease-out);
}

.es-study__req:hover {
  border-color: var(--brand-200);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

/* A disc, as the reference draws it, not the rounded square the
   first build used.

   `--brand-50` behind `--brand-700` is the pair the site chip is
   built on and measures 6.09:1. The reference puts its mid-blue
   on its lightest tint; the same relationship in our ramp is
   `--brand-500` on `--brand-50`, which measures 2.6:1 and fails
   at 14px, so the ink is the -700 step. */
.es-study__req-num {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  margin-bottom: var(--space-1);
  border-radius: var(--radius-full);
  background: var(--brand-50);
  color: var(--brand-700);
  font-size: var(--fs-caption);
  font-weight: var(--font-bold);
  font-variant-numeric: tabular-nums;
}

.es-study__req h3 {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-card-title);
  font-weight: var(--font-bold);
  line-height: var(--lh-subhead);
  letter-spacing: var(--tracking-tight);
}

.es-study__req p {
  margin: 0;
  color: var(--text-body);
  font-size: var(--fs-body-sm);
  line-height: var(--leading-normal);
}

/* A card with no title closes the gap the title would have sat
   in, so a prose-built card is not a disc, a hole, and a line.
   See inc/case-study-view.php on why most cards have no title. */
.es-study__req-num:last-child { margin-bottom: 0; }
.es-study__req-num + p { margin-top: calc(var(--space-3) * -1 + var(--space-2)); }

/* -- The work ---------------------------------------------
   The reference's `.steps`: a rule above the list, a rule under
   every row, the label in a 140px column and the step in the
   rest. Below 900px the row stacks and the label sits above its
   step on a tighter gap, which is the reference's own fallback. */
.es-study__work {
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border-subtle);
  list-style: none;
}

.es-study__work > li {
  display: grid;
  align-items: start;
  gap: var(--space-2);
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--border-subtle);
}

@media (min-width: 900px) {
  .es-study__work > li {
    grid-template-columns: 140px minmax(0, 1fr);
    gap: var(--space-5);
  }
}

/* The 6px lead the reference gives this, so the label's cap
   height sits on the heading's rather than on its ascender. */
/* OWNER RULING, 7 Sep 2026: `--brand-500`, asked for over the
   `--brand-600` this carried.

   Recording the cost, because it is measured and it is a
   regression: on the white band this label is 3.50:1 against its
   ground, where -600 was 4.71:1. WCAG AA wants 4.5:1 for text
   this size, and 13px bold does not reach the large-text
   exemption, which starts at 18.66px bold. On a `--sunken` band
   it is worse again, at 3.29:1.

   No size on this scale rescues -500 here: `--fs-body` is 18px
   and still under the exemption. The choice is the colour or the
   contrast. Switch this one declaration back to `--brand-600` and
   the band passes. */
.es-study__work-step {
  margin: 0;
  padding-top: var(--space-1);
  color: var(--brand-500);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-wider);
  line-height: var(--lh-subhead);
  text-transform: uppercase;
}

.es-study__work-body { min-width: 0; }

/* The reference sets its step heading at 24px, one notch above
   its 22px card heading, and that gap is the point: the step is
   the more prominent of the two. `--fs-title-sm` is the scale's
   next step up and tops out at 31px, which overshoots it by
   seven; derived from the card step instead, this lands at 24
   and keeps the relationship the reference draws. */
.es-study__work-body h3 {
  margin: 0;
  color: var(--text-heading);
  font-size: calc(var(--fs-card-title) * 1.1);
  font-weight: var(--font-bold);
  line-height: var(--lh-subhead);
  letter-spacing: var(--tracking-tight);
}

.es-study__work-body p {
  max-width: 90ch;
  margin: var(--space-2) 0 0;
  color: var(--text-body);
  font-size: var(--fs-body-sm);
  line-height: var(--leading-normal);
}

.es-study__work-body > p:only-child { margin-top: 0; }

/* ------------------------------------------------------------
   Screenshots
   ------------------------------------------------------------ */

/* A row of three, not a masonry column.

   Every one of the sixteen studies carries exactly three
   figures, so `columns: 3` put one item in each column and the
   balancing it exists for never ran. What it produced was three
   cards of 759, 829 and 757px ending at three different heights,
   with the ragged white under the short two reading as a layout
   fault rather than as a masonry.

   Each card is capped to a window on the top of the page and
   fades out at the foot instead. The crop costs nothing: the
   whole page is what the viewer opens, and the fade is what says
   there is more of it to open. */
.es-study__shots {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.es-study__shots li {
  display: flex;
}

.es-study__shot {
  position: relative;
  display: block;
  width: 100%;
  height: clamp(300px, 32vw, 460px);
  padding: 0;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  overflow: hidden;
  cursor: zoom-in;
  transition: border-color var(--duration-normal) var(--ease-out),
    transform var(--duration-normal) var(--ease-out),
    box-shadow var(--duration-normal) var(--ease-out);
}

.es-study__shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* The foot of the crop, dissolved into the card's own ground so
   the cut reads as a page continuing rather than as a picture
   ending. Painted to `--bg-surface` and not to the band, because
   the band the gallery lands on alternates with the study. */
.es-study__shot::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 96px;
  background: linear-gradient(180deg, transparent 0%, var(--bg-surface) 100%);
  pointer-events: none;
}

.es-study__shot:hover,
.es-study__shot:focus-visible {
  border-color: var(--brand-300);
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
}

.es-study__shot-label {
  position: absolute;
  z-index: 1;
  left: var(--space-4);
  bottom: var(--space-4);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--gray-900) 72%, transparent);
  backdrop-filter: blur(6px);
  color: var(--gray-0);
  font-size: var(--fs-caption);
  font-weight: var(--font-semibold);
}

@media (max-width: 991px) {
  .es-study__shots {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 559px) {
  .es-study__shots {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* -- Lightbox ---------------------------------------------
   Opened by the script; `hidden` until then, so a page without
   JavaScript carries a closed dialog and the screenshots stay
   ordinary links to the file. */

.es-study__lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  padding: var(--gap-fluid-sm);
  background: color-mix(in srgb, var(--gray-900) 88%, transparent);
}

.es-study__lightbox[hidden] {
  display: none;
}

.es-study__lightbox-inner {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(100%, 1000px);
  max-height: 100%;
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  overflow: hidden;
}

.es-study__lightbox-img {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-sunken);
}

.es-study__lightbox-img img {
  display: block;
  width: 100%;
  height: auto;
}

.es-study__lightbox-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border-subtle);
  color: var(--text-body);
  font-size: var(--fs-small);
  font-weight: var(--font-semibold);
}

.es-study__lightbox-count {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.es-study__lb-btn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  background: var(--bg-surface);
  color: var(--text-heading);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.es-study__lb-btn:hover:not(:disabled) {
  background: var(--brand-50);
  color: var(--brand-700);
}

.es-study__lb-btn:disabled {
  opacity: .35;
  cursor: default;
}

.es-study__lb-btn svg {
  width: 18px;
  height: 18px;
}

.es-study__lb-btn--prev svg {
  transform: rotate(180deg);
}

.es-study__lb-btn--close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  z-index: 1;
  background: color-mix(in srgb, var(--gray-900) 72%, transparent);
  border-color: transparent;
  color: var(--gray-0);
}

.es-study__lb-nav {
  display: flex;
  gap: var(--space-2);
}

/* ------------------------------------------------------------
   Core features
   ------------------------------------------------------------ */

.es-study__features {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: es-feat;
}

.es-study__features li {
  padding: var(--space-6) var(--space-5);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
  counter-increment: es-feat;
  transition: border-color var(--duration-normal) var(--ease-out),
    transform var(--duration-normal) var(--ease-out);
}

.es-study__features li:hover {
  border-color: var(--brand-200);
  transform: translateY(-3px);
}

.es-study__features li::before {
  content: counter(es-feat, decimal-leading-zero);
  display: block;
  margin-bottom: var(--space-3);
  color: var(--brand-500);
  font-size: var(--fs-caption);
  font-weight: var(--font-extrabold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-wider);
}

.es-study__features h4 {
  margin: 0 0 var(--space-3);
  color: var(--text-heading);
  font-size: var(--fs-body);
  font-weight: var(--font-bold);
  line-height: var(--lh-subhead);
}

.es-study__features p {
  margin: 0;
  color: var(--text-body);
  font-size: var(--fs-small);
  line-height: var(--leading-relaxed);
}

/* ------------------------------------------------------------
   The numbers
   ------------------------------------------------------------ */

.es-study__numbers {
  display: grid;
  gap: var(--gap-fluid-lg);
  padding: var(--gap-fluid-xl) var(--gap-fluid-lg);
  border-radius: var(--radius-lg);
  background: var(--brand-50);
}

/* Heading beside the figures, not above them.

   Stacked, the capped 220px tracks below left roughly 850px of
   bare brand-50 to the right of a two figure study, and two is
   the only shape this band ever renders for. Side by side, the
   heading takes that width back.

   Both tracks are fractions. An `auto` figures track sizes to
   its own max-content, and `auto-fit` inside an indefinite track
   resolves to a single column, so `1fr auto` collapsed the
   figures into one narrow stack and wrapped the heading onto
   four lines. */
@media (min-width: 900px) {
  .es-study__numbers {
    grid-template-columns: minmax(260px, 1fr) minmax(0, 1.15fr);
    align-items: center;
    gap: var(--gap-fluid-xl);
  }
}

.es-study__numbers-head {
  margin-bottom: var(--gap-fluid-lg);
}

@media (min-width: 900px) {
  .es-study__numbers-head {
    margin-bottom: 0;
  }
}

.es-study__numbers-head h2 {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-section-title);
  font-weight: var(--font-extrabold);
  line-height: var(--lh-heading);
  letter-spacing: var(--tracking-tight);
}

.es-study__numbers-head p {
  margin: var(--space-3) 0 0;
  color: var(--text-muted);
  max-width: 60ch;
}

/* Free tracks now, because the figures have an edge.

   The 220px cap was there because a bare figure floating in a
   588px slice of brand-50 read as adrift, which it did. Giving
   each figure the same white card the hero's headline figures
   carry solves that at the root: a 330px card is a card, and the
   row divides evenly instead of hugging the left with the panel
   empty beside it. */
.es-study__stats {
  display: grid;
  gap: var(--gap-fluid-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.es-study__stats li {
  padding: var(--gap-fluid-sm);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
}

/* brand-50, not --bg-surface. The figure now sits on a white
   card, so a white disc on it is invisible; the panel's own
   ground is what makes the disc read. */
.es-study__stat-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-bottom: var(--space-4);
  border-radius: var(--radius-full);
  background: var(--brand-50);
  color: var(--brand-700);
}

.es-study__stat-icon svg {
  width: 20px;
  height: 20px;
}

.es-study__stat-value {
  display: block;
  color: var(--brand-600);
  font-size: var(--fs-stat);
  font-weight: var(--font-extrabold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
}

.es-study__stat-label {
  display: block;
  margin-top: var(--space-3);
  color: var(--text-muted);
  font-size: var(--fs-caption);
  font-weight: var(--font-semibold);
}

/* ------------------------------------------------------------
   Client testimonial
   ------------------------------------------------------------ */

.es-study__testimonial {
  display: grid;
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

/* The author panel. Dark in both themes, so like `.es-bg-dark`
   it re-points the tokens its children read rather than
   restating a colour on each one.

   Centred, not spread: `space-between` assumed two children, and
   fifteen of the sixteen studies carry no figures, so the
   results list never renders and the panel shipped as 152px of
   content above 221px of dead black. */
.es-study__author {
  --text-heading: var(--gray-0);
  --text-body: rgba(255, 255, 255, .72);
  --text-muted: rgba(255, 255, 255, .55);
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--gap-fluid-md);
  padding: var(--gap-fluid-md);
  background: var(--gray-900);
  color: var(--text-body);
  overflow: hidden;
}

/* The brand rule across the head of the panel. */
.es-study__author::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--brand-500), var(--brand-700));
}

/* A dot field, at 6% white. Enough to stop a 320px column of
   flat black reading as a hole in the card, not enough to
   compete with the name sitting on it. */
.es-study__author::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(255, 255, 255, .06) 1px, transparent 1px);
  background-size: 20px 20px;
  pointer-events: none;
}

.es-study__author-id {
  position: relative;
  z-index: 1;
}

.es-study__avatar {
  display: grid;
  place-items: center;
  width: 80px;
  height: 80px;
  margin-bottom: var(--space-5);
  overflow: hidden;
  border: 3px solid rgba(255, 255, 255, .15);
  border-radius: var(--radius-full);
  background: linear-gradient(135deg, var(--brand-500) 0%, var(--brand-700) 100%);
  color: var(--gray-0);
  font-size: var(--fs-title-sm);
  font-weight: var(--font-extrabold);
}

.es-study__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.es-study__author-name {
  display: block;
  color: var(--gray-0);
  font-size: var(--fs-card-title);
  font-weight: var(--font-bold);
}

.es-study__author-role {
  display: block;
  margin-top: var(--space-2);
  color: rgba(255, 255, 255, .6);
  font-size: var(--fs-small);
}

/* The client badge. The domain and nothing else: a country here
   would break the location neutral rule the same way one in a
   heading does. */
.es-study__author-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
  padding: var(--space-2) var(--space-4);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .08);
  color: rgba(255, 255, 255, .68);
  font-size: var(--fs-caption);
  font-weight: var(--font-bold);
}

.es-study__author-dot {
  width: 8px;
  height: 8px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--brand-500);
}

.es-study__author-results {
  position: relative;
  z-index: 1;
  margin: 0;
}

.es-study__author-results dt {
  margin-bottom: var(--space-3);
  color: rgba(255, 255, 255, .45);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.es-study__author-results dd {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
}

.es-study__author-results b {
  color: var(--brand-400);
  font-size: var(--fs-title-sm);
  font-weight: var(--font-extrabold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
}

.es-study__author-results span {
  color: rgba(255, 255, 255, .6);
  font-size: var(--fs-caption);
  font-weight: var(--font-semibold);
}

/* The brand edge between the dark panel and the quote, which is
   what ties the two halves together rather than letting them
   read as two cards that happen to touch. */
.es-study__quote {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-5);
  margin: 0;
  padding: var(--gap-fluid-md);
  border-top: 4px solid var(--brand-500);
  background: var(--bg-surface);
}

.es-study__stars {
  display: flex;
  gap: 4px;
  color: var(--star-color);
}

.es-study__stars svg {
  width: 20px;
  height: 20px;
}

.es-study__quote-mark {
  width: 48px;
  height: 48px;
  color: var(--brand-500);
  opacity: .25;
}

.es-study__quote p {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-lede-lg);
  font-weight: var(--font-semibold);
  line-height: var(--leading-relaxed);
}

.es-study__quote em {
  color: var(--brand-600);
  font-style: italic;
}

/* Outcome chips. Drawn from `cs_testi_highlights`, so a study
   whose editor has written none simply does not get the row. */
.es-study__marks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}

.es-study__marks li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--brand-100);
  border-radius: var(--radius-sm);
  background: var(--brand-50);
  color: var(--brand-700);
  font-size: var(--fs-caption);
  font-weight: var(--font-bold);
}

.es-study__marks svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

@media (min-width: 900px) {
  .es-study__testimonial {
    grid-template-columns: 320px minmax(0, 1fr);
  }

  .es-study__author {
    padding: var(--gap-fluid-lg) var(--gap-fluid-md);
  }

  .es-study__quote {
    padding: var(--gap-fluid-lg);
    border-top: 0;
    border-left: 4px solid var(--brand-500);
  }
}

/* ------------------------------------------------------------
   Closing offer
   ------------------------------------------------------------ */

/* Dark in both themes for the same reason the author panel is,
   and the token re-points are what make the button legible on it
   without a second button variant. */
/* `minmax(0, 1fr)`, not the implicit `auto` track.

   An `auto` column takes the min-content width of whatever is in
   it, and the button below is `white-space: nowrap`. A study
   whose CTA label reads "Hire Dedicated WordPress Developers"
   made that 416px, the column stretched to it, and the panel ran
   62px past a 390px viewport with a horizontal scrollbar on the
   whole page. Two of the sixteen studies carry a label that
   long. */
.es-study__cta {
  --text-heading: var(--gray-0);
  --text-body: var(--brand-100);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-fluid-md);
  padding: var(--gap-fluid-lg) var(--gap-fluid-md);
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--brand-500) 0%, var(--brand-700) 100%);
  color: var(--text-body);
}

.es-study__cta h2 {
  max-width: 22ch;
  margin: 0 0 var(--space-4);
  color: var(--text-heading);
  font-size: var(--fs-title-md);
  font-weight: var(--font-extrabold);
  line-height: var(--lh-heading);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

.es-study__cta p {
  max-width: 60ch;
  margin: 0;
  color: var(--text-body);
  line-height: var(--leading-relaxed);
}

/* A white pill on the brand ground. `.es-btn--secondary` reads
   its ground from --bg-sunken, which on this panel resolves to a
   light grey that measures 1.12:1 against white ink, so the
   button is drawn here instead of re-pointing four tokens to
   fake it. */
.es-study__cta-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  align-self: center;
  justify-self: start;
  padding: 14px 28px;
  border-radius: var(--radius-full);
  background: var(--gray-0);
  color: var(--brand-600);
  font-size: var(--fs-body);
  font-weight: var(--font-semibold);
  text-decoration: none;
  box-shadow: 0 10px 25px -10px rgba(0, 0, 0, .45);
  transition: transform var(--duration-normal) var(--ease-out),
    box-shadow var(--duration-normal) var(--ease-out);
}

.es-study__cta-btn:hover,
.es-study__cta-btn:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 16px 30px -12px rgba(0, 0, 0, .5);
}

.es-study__cta-btn .arrow-icon {
  width: 25px;
  height: 24px;
}

@media (min-width: 900px) {
  .es-study__cta {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--gap-fluid-lg);
    padding: var(--gap-fluid-xl);
  }

  /* One line only where the row is wide enough to hold it. Below
     this the label wraps inside the pill rather than pushing the
     panel off the side of the phone. */
  .es-study__cta-btn {
    white-space: nowrap;
  }
}

/* ------------------------------------------------------------
   Motion
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {

  .es-study__req,
  .es-study__features li,
  .es-study__shot {
    transition: none;
  }

  .es-study__req:hover,
  .es-study__features li:hover,
  .es-study__shot:hover {
    transform: none;
  }

  .es-study__window,
  .es-study__lightbox-img {
    scroll-behavior: auto;
  }
}