/* ============================================================
   BLOG INDEX
   The post card (`.es-post`) is declared once, because a card
   in the grid and the card that opens the page are the same
   object at two sizes, not two components.

   The page opens on one row rather than on a full-width banner:
   the latest post on one half and the free CRO audit offer on
   the other. Two full-width blocks stacked would have pushed
   the archive itself off the fold, so the post and the offer
   share a line and the grid starts underneath it.

   The box, the picture frame, the hover lift, the archive
   shell, the toolbar, the category rail, the search field, the
   count, the empty state and the closing row all come from
   es-collection.css. Nothing here restates them.
   ============================================================ */

/* -- Post card -------------------------------------------
   The picture frame is 19:9. That is not a design preference:
   159 of the 162 published featured images are drawn at
   1140x540 or 1920x910, both of which are 19:9 to three
   decimal places, and at the 16:10 the card shell defaults to,
   every one of them was losing a third of its width to the
   crop. The three that are not that ratio are cropped by
   `object-fit` instead of setting the shape of the whole page.

   Set on the card rather than on the frame, because
   `--es-media-ratio` is the frame's own hook in
   es-collection.css and a component is supposed to set it. */

.es-post { --es-media-ratio: 19 / 9; }

.es-post__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--gap-fluid-sm);
}

.es-post__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}

/* A dot between meta items, drawn rather than typed, so it is
   never read out as content. */
.es-post__meta > * + *::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 3px;
  margin-right: var(--space-3);
  border-radius: var(--radius-full);
  background: currentColor;
  vertical-align: middle;
}

.es-post__title {
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-card-title);
  font-weight: var(--font-extrabold);
  line-height: var(--lh-subhead);
  letter-spacing: var(--tracking-normal);
  transition: color var(--duration-normal) var(--ease-out);
}

/* Caps the card at a predictable height so a long headline does
   not push one card in a row taller than its neighbours. */
.es-post__title a {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
}

.es-card:hover .es-post__title { color: var(--brand-600); }

.es-post__excerpt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-small);
  line-height: var(--leading-normal);
}

.es-post__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: auto;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

.es-post__author {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}


/* The author's picture. A ring rather than a border, so a
   photograph on a light card has an edge without the card
   gaining a second hairline beside the one it already has. */
.es-post__avatar {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.es-post__avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The fallback for an author the site holds no picture for,
   painted with the eyebrow pairing so it reads as the same
   brand mark as the chip on the artwork above it. */
.es-post__avatar--initials {
  background: var(--eyebrow-bg);
  color: var(--eyebrow-ink);
  font-size: var(--fs-eyebrow);
  font-weight: var(--font-bold);
  letter-spacing: 0;
  box-shadow: none;
}

/* The one link inside the card that is not the card. It has to
   outrank the covering pseudo element, which sits at 1, or the
   card's own link eats the click and the author page is
   unreachable from here. `position` as well as `z-index`,
   because a static element has no stacking position for the
   index to apply to. */
.es-post__author-link {
  position: relative;
  z-index: 2;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease-out);
}

.es-post__author-link:hover,
.es-post__author-link:focus-visible {
  color: var(--brand-600);
  text-decoration: underline;
}

/* -- Grid ------------------------------------------------- */

.es-blog__grid {
  display: grid;
  gap: var(--gap-fluid-md);
  /* `min(100%, 300px)` rather than a bare 300px: below 300px of
     available width a bare floor makes the track wider than the
     grid and the row overflows the viewport. */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
}

/* -- Lead row --------------------------------------------
   The latest post beside the offer.

   Flex, not grid, and the reason is what happens when a filter
   empties the row: a lead post that is not rendered leaves a
   grid track standing empty and a hole in the line, whereas an
   absent flex item is not on the line at all and the offer
   simply spreads into the space.

   Equal basis and equal grow, so the two split the row evenly,
   on the grid's own track width, so the row breaks where the
   grid below it changes column count rather than at a width of
   its own.

   A zero basis looks like the obvious way to write that and is
   not: with `box-sizing: border-box` a flex base size of zero
   is floored by the item's own padding, which comes out as a
   padded offer wider than the card beside it. */

.es-blog__lead {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-fluid-md);
  margin-bottom: var(--gap-fluid-md);
}

/* The row is split by basis rather than by grow factor. The two items have
   different floors, and a grow factor divides what is left after those floors
   are met, which is not the same ratio. Halves and the gap add up to exactly
   100%, so nothing has to grow or shrink to land on them.

   `min()` on the floors rather than a bare pixel value: below a 300px row a
   floor wider than the row is a floor that overflows it. */
.es-blog__lead > [data-es-lead] > * {
  flex: 1 1 calc(50% - 18px);
  min-width: min(100%, 300px);
}

.es-blog__lead > .es-blog__offers {
  flex: 1 1 calc(50% - 18px);
  /* 500px is not a round number, it is the width below which the offers stop
     working side on: each one splits into two columns and a submit button
     needs about 230px to keep its label on one line. Rather than let the
     column squeeze past that, this floor makes the pair wrap to a line of
     their own, where they get the full container and more room than they had
     beside the card. */
  min-width: min(100%, 500px);
}

/* Past this width the card can take the larger share, and it is the picture
   that asks for it. The frame is 19:9, so the card's height follows its
   width, and the offers column is the taller of the two ends: the choice is
   between stretching the card to meet it, which opens a band of nothing above
   the byline, and widening the card until it gets there on its own. Below
   this width there is no spare room to give it, so the row goes back to
   halves and the offers keep enough to stay side on. */
@media (min-width: 1400px) {
  .es-blog__lead > [data-es-lead] > * { flex-basis: calc(56% - 18px); }
  .es-blog__lead > .es-blog__offers { flex-basis: calc(44% - 18px); }
}

/* The holder the script swaps the lead post in and out of. It
   is not a box: `display: contents` puts the article itself on
   the flex line, so the row is two items and not two items in
   a wrapper that eats the sizing above. */
.es-blog__lead > [data-es-lead] {
  display: contents;
}

/* The lead card keeps the ratio the grid cards keep, and the
   body takes whatever height the row has over it. That is the
   change the artwork asked for: this card used to be given a
   picture height and told to grow into the row, which on a
   19:9 image meant the row's height decided how much of the
   picture got cropped. A ratio crops nothing and the slack
   goes somewhere that can absorb it.

   The excerpt is what fills that slack, so the lead card shows
   more of the article than a grid card does, which is what a
   lead card is for. */
.es-blog__lead .es-post .es-post__body { flex: 1 1 auto; }

.es-blog__lead .es-post .es-post__excerpt {
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

/* -- The offers column -----------------------------------
   Both offers in one line item, so the article keeps its share
   of the row rather than the three of them splitting it into
   thirds. The column is a flex item of the row and a flex
   column of its own; the two offers share its height between
   them.

   Its share of the row and its floor are set on the lead row
   above, with the article's, so the two halves of that ratio
   are written next to each other rather than one of them being
   answered here. */
.es-blog__offers {
  display: flex;
  flex-direction: column;
  gap: var(--gap-fluid-md);
}

/* The two share the column evenly, so the pair reads as one
   block rather than as a tall offer and a short one. */
.es-blog__offers > * { flex: 1; }

/* ============================================================
   THE OFFER
   The standing block on the lead row. Three things it
   deliberately is:

   - an `<aside>`, not an `<article>`: it is not a post, and a
     screen reader working through the archive by article
     should not have to read past it.
   - outside the grid the filter controls, so it is never
     hidden behind a category, counted in "showing 6 of 9", or
     held back behind load more.
   - a `.es-panel`, so the box and its padding hook come from
     es-collection.css rather than being drawn twice. It is the
     card shell's quiet counterpart, which is what lets it sit
     beside a real card without competing with it.

   It carries no picture and no colour of its own. The post
   beside it leads with a picture, and a saturated block in the
   row that opens the archive is louder than the archive it is
   introducing. What says this is an offer and not a post is
   where it sits, on the lead row rather than in the grid.
   ============================================================ */

/* ------------------------------------------------------------
   Built to the HTML/CSS the owner supplied on 7 Sep 2026, with
   four substitutions the owner asked for: the site's icon set in
   place of every inline SVG, the site's button in its outline
   variant in place of the filled pill, design tokens in place of
   every raw value, and one token for the ground.

   ── WHY THIS IS A CONTAINER QUERY AND NOT A MEDIA QUERY ─────
   The supplied code sizes the card off the VIEWPORT, which is
   right for the full-width section it was written as and wrong
   here, because the owner has kept the archive's grid: this card
   lives in the offers column, and that column's width is not a
   function of the viewport.

   Measured, the card's content box is 258px at a 320 viewport,
   313 at 375, 482 at 576, 662 at 768, **902 at 992**, then back
   down to 508 at 1280 and 519 at 1440. It is at its WIDEST on a
   992px screen, because that is where the offers take a full
   line of their own before the two-up lead row returns above
   1250. A viewport query would hand the roomiest card the
   narrowest layout and the 1440px card the widest one, which is
   exactly backwards.

   So the card is its own container and every arrangement below
   is asked of the card. The three states map onto the supplied
   code's three: under 420 is its mobile, 420-760 its tablet,
   760 up its desktop.
   ------------------------------------------------------------ */

/* Each card paints itself from four accent values and nothing
   else. Set here as the blue family and repainted by `--app`,
   which is what makes a third offer four values rather than a
   second copy of this block.

   `--eyebrow-*` and `--field-ring` are set from the same four
   because other components read them: the confirmation tick
   takes the eyebrow pair, and the shared field and confirmation
   panel both take the ring. Left alone the ring resolves to
   brand orange, which is not a colour on these cards.
   `--btn-outline-edge` is the same idea for the button. */
.es-promo {
  --promo-ground: var(--offer-blue-50);
  --promo-edge:   var(--offer-blue-200);
  --promo-chip:   var(--offer-blue-100);
  --promo-ink:    var(--offer-blue-700);

  --eyebrow-bg:  var(--promo-chip);
  --eyebrow-ink: var(--promo-ink);
  --field-ring:  var(--promo-ink);
  --btn-outline-edge: var(--promo-ink);

  container-type: inline-size;
  container-name: promo;

  position: relative;
  isolation: isolate;
  overflow: hidden;

  /* This one line is load-bearing and the reason is not obvious.
     `.es-blog__offers > *` is `flex: 1`, which is `flex-basis: 0`
     plus `flex-shrink: 1`, and what normally stops a flex item
     shrinking past its content is the automatic minimum size.
     That minimum only applies while `overflow` is `visible` --
     and `overflow: hidden` above, which is what clips the
     artwork to the card, turns it off. The result was a card
     shrunk to the column's share of the height with the submit
     button cut in half by the bottom edge.

     `fit-content` puts the floor back without giving up the
     clip: the card can still grow to fill the column, and can
     no longer be pushed under its own content. */
  min-height: fit-content;

  display: flex;
  flex-direction: column;
  gap: var(--space-4);

  /* The card's own padding and corner, not the supplied layout's.
     That layout is drawn on an 1160px card and its 48px inset is a
     proportion of it; on a 542px card in the offers column the same
     number is a third of the width, and the card the article beside
     it has to match is the one that was here. Everything in this
     block below is the same trade: the supplied DESIGN, at the
     card's own scale. */
  --panel-pad: var(--space-4);
  border-color: var(--promo-edge);
  background: var(--offer-card-bg-blue);
  box-shadow: var(--shadow-md), 0 1px 3px rgba(0, 0, 0, .06);
  transition: box-shadow .22s ease, transform .22s ease;
}

.es-promo--app {
  --promo-ground: var(--offer-green-50);
  --promo-edge:   var(--offer-green-200);
  --promo-chip:   var(--offer-green-100);
  --promo-ink:    var(--offer-green-700);

  background: var(--offer-card-bg-green);
}

.es-promo:hover {
  box-shadow: var(--shadow-lg), 0 2px 6px rgba(0, 0, 0, .07);
  transform: translateY(-2px);
}

/* The panel spaces its children with the owl, which would stack
   with the gap and space this box twice. Same specificity as the
   rule it answers and later in the cascade, so no `!important`. */
.es-promo > * + * { margin-top: 0; }


/* -- The copy column -------------------------------------
   Full width by default and a share of the card once there is
   room beside it, which is the supplied code's own arrangement
   read the other way up: it starts at 68% and falls back to
   100%, and the card here is narrow far more often than it is
   wide, so the fallback is the base and the share is the step.

   Layered above the artwork rather than beside it. The supplied
   code does the same, and it is what lets the copy keep its
   measure while the artwork keeps its size. */
.es-promo__body {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  width: 100%;
  min-width: 0;
}

/* The eyebrow as a bare label, not the site's chip. The pill is
   taken off on the owner's instruction (7 Sep 2026): on a card
   with a ground and a bloom of its own it was a third box inside
   a box, and the card reads quieter without it.

   Only the decoration goes. It is still the same element and the
   same class, still uppercase, still tracked, and still in the
   card's own ink, so it is one chip site-wide with a treatment
   here rather than a second chip.

   The margin is zeroed because the chip carries its own bottom
   margin site-wide and this stack already spaces its children;
   the padding, background and border go with the pill.

   Contrast improves rather than suffers: the ink was measured
   against the chip's fill at 8.67:1 (blue) and 6.54:1 (green),
   and against the card ground it is 11.9:1 and 8.6:1. */
.es-promo .es-eyebrow {
  margin-bottom: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--eyebrow-ink);
}

/* The supplied headline runs 48px on a 1160px card, 43 on a
   tablet and 34 on a phone. That ladder is a proportion of the
   card, so it is written as one: `cqi` is a percent of the
   container's inline size, and 6.2 of them reproduces all three
   of those numbers at the widths they were drawn for.

   The bounds are derived from `--fs-card-title` rather than
   typed, so the headline still moves if the card-title step
   ever does. */
.es-promo__title {
  max-width: 22ch;
  margin: 0;
  color: var(--text-heading);
  font-size: clamp(var(--fs-card-title), 2.2vw, calc(var(--fs-card-title) * 1.15));
  font-weight: var(--font-extrabold);
  line-height: var(--lh-subhead);
  letter-spacing: -0.01em;
}

.es-promo__desc {
  max-width: 28ch;
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-small);
  line-height: var(--leading-normal);
}

.es-promo__text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  max-width: 60ch;
  margin: 0;
  color: var(--text-body);
  font-size: var(--fs-small);
  line-height: var(--leading-normal);
}

/* The close takes the slack, so it sits on the floor of the box
   and lines up with the article card's own foot row beside it.
   Which block IS the close differs per offer, which is why this
   is three selectors and not one: the form is the whole of the
   audit's close, and the foot row is the whole of the app
   spotlight's. */
.es-promo--audit .es-promo__form,
.es-promo--audit .es-promo__done,
.es-promo--app .es-promo__lockup { margin-top: auto; }

.es-promo__note {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-caption);
}


/* -- The request -----------------------------------------
   Two fields and only two: the storefront and somewhere to send
   the recording. Every field is a reason not to start.

   Fields, status line and submit in one grid, and the submit is
   `justify-self: start` rather than full width, because a submit
   as wide as the panel reads as a banner. */
.es-promo__form {
  position: relative;
  z-index: 2;
  display: grid;
  align-content: start;
  gap: var(--space-3);
}

.es-promo__form[hidden],
.es-promo__done[hidden] { display: none; }

.es-promo__form .es-btn { justify-self: start; }

/* The variant's resting hairline is `--border-default`, a warm
   beige that is the right neutral everywhere else on the site and
   the wrong one on a blue or green ground. The hover colour needs
   no rule: the variant reads `--btn-outline-edge`, which the card
   sets to its own ink at the top of this file. */
.es-promo .es-btn--outline { border-color: var(--promo-edge); }

/* -- Audit card submit CTA --------------------------------
   The audit card's submit sits in a <button> but is styled
   identically to the app card's .es-promo__waitlist <a>.
   A button carries browser UA padding, borders and background
   by default; strip them so the element is invisible to CSS
   and the shared class takes over without any specificity
   fights. */
button.es-promo__waitlist {
  padding: 0;
  margin: 0;
  border: none;
  background: none;
  font-family: inherit;
  cursor: pointer;
}

/* The honeypot. Off the page rather than `display: none`, so a
   bot filling in every field it can read still finds it. */
.es-promo__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* One column until the card can hold two labelled controls on a
   line, which is a property of the CARD and not of the page. */
.es-promo__fields {
  display: grid;
  gap: var(--space-3);
}


/* -- The controls ----------------------------------------
   The supplied field: a bordered box with the mark inside it and
   the input filling what is left, rather than a mark laid over a
   padded input. That is the better construction and not just the
   drawn one, because the box owns the focus ring, so the ring
   goes round the mark and the text together.

   Scoped to `.es-promo` even though the audit offer is today the
   only thing using `.es-field`. The component lives in
   es-collection.css and is written to be reused; the next
   template to reach for it should get that file's control, not
   this card's paint. */
.es-promo .es-field { gap: var(--space-2); }

.es-promo .es-field > label {
  color: var(--text-heading);
  font-size: var(--fs-caption);
  font-weight: var(--font-semibold);
  line-height: 1;
}

.es-promo .es-field__control {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* The height the field had before the rebuild, not the supplied
     72px: that control is drawn on a card twice this wide. The BOX
     is the supplied design and stays; only its size is the card's
     own. 48px on a 20px line box still clears the 44px touch
     target floor. */
  min-height: 48px;
  padding: 0 var(--space-4);
  border: 1.5px solid var(--promo-edge);
  border-radius: var(--radius-lg);
  /* Opaque, not the supplied 78% white. That card has nothing
     behind its fields; this one has the drawing behind them once
     the copy takes the full width, and a translucent control let
     the chart show through the middle of an input. */
  background: var(--bg-surface);
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}

/* The ring is on the BOX, so it encloses the mark and the text.
   `:focus-within` rather than `:focus`, because the thing that
   takes focus is the input inside it. */
.es-promo .es-field__control:focus-within {
  border-color: var(--promo-ink);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--promo-ink) 12%, transparent);
}

.es-promo .es-field__control:has(input[aria-invalid="true"]) {
  border-color: var(--error-text);
}

.es-promo .es-field__icon {
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  color: var(--text-muted);
}

/* The input is the box's filling, with no box of its own. The
   outline is dropped because the wrapper draws the ring; there
   is no state where focus is unmarked. */
.es-promo .es-field input {
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text-heading);
  font: inherit;
  font-size: var(--fs-small);
}


/* -- App spotlight ----------------------------------------
   The foot row: the app on the left, the way in on the right.
   It wraps rather than shrinking, because the tile has a floor
   of its own and the link's label runs to about 150px. */
.es-promo__lockup {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--gap-fluid-sm);
  min-width: 0;
}

/* The identity tile: the mark, the name, and what kind of thing
   it is, in the order a store owner meets them in the App Store
   listing. The box is what makes the group read as the app
   rather than as a picture sitting on a card. */
.es-promo__ident {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--promo-edge);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
}

/* The mark keeps its own square. `flex-shrink: 0` because it is
   artwork with a fixed ratio and a flex row is free to squash
   anything that does not say otherwise. */
.es-promo__mark {
  display: block;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
}

.es-promo__ident-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Set in the site's own type rather than arriving as a wordmark,
   so the name is text a reader, a crawler and a translator all
   get. */
.es-promo__name {
  color: var(--text-heading);
  font-size: var(--fs-body);
  font-weight: var(--font-bold);
  line-height: var(--lh-subhead);
}

.es-promo__kind {
  color: var(--text-muted);
  font-size: var(--fs-caption);
  line-height: var(--leading-normal);
}

/* The waitlist link, as the supplied layout draws it: the label,
   the site's single arrow, and no rule under either. The audit
   card carries the button; this one is the quieter of the two
   asks and is set to read that way. */
.es-promo__waitlist {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--promo-ink);
  font-size: var(--fs-small);
  font-weight: var(--font-semibold);
  text-decoration: none;
  white-space: nowrap;
}

.es-promo__waitlist-icon {
  flex-shrink: 0;
  width: 16px;
  height: 14px;
  transition: transform var(--duration-normal) var(--ease-out);
}

.es-promo__waitlist:hover .es-promo__waitlist-icon,
.es-promo__waitlist:focus-visible .es-promo__waitlist-icon {
  transform: translateX(4px);
}

.es-promo__waitlist:focus-visible {
  outline: 2px solid var(--promo-ink);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}


/* ============================================================
   THE ARTWORK
   The supplied illustration, rebuilt out of boxes and the site's
   own icons. There is no image and no bespoke SVG in it: the
   browser, the panel, the disc and the checklist are boxes, and
   every mark inside them comes from inc/icons.php.

   ── ONE KNOB ────────────────────────────────────────────────
   Every dimension below is in `em`, and the only thing that sets
   a size is the `font-size` on the cluster. That is deliberate:
   the supplied artwork is a drawing with about thirty
   coordinates in it, and thirty numbers that have to stay in
   proportion are one number and thirty ratios. At `1em = 10px`
   the values below ARE the supplied pixels divided by ten, so
   the drawing can be checked against the original by reading it.

   These are artwork coordinates, not layout. Nothing on the card
   is measured against them and nothing in them is a spacing
   decision, which is why they are not on the spacing scale.
   ============================================================ */

.es-promo__visual {
  position: absolute;
  z-index: 0;
  top: 4.7em;
  right: 4.6em;
  width: 36.5em;
  height: 39em;
  /* The whole cluster is 36.5em by 39em, so this one value is
     what decides whether the drawing fits the card. It has to,
     because `overflow: hidden` does not make an oversized drawing
     smaller, it makes it a drawing with its edges cut off, which
     is what was on the card before this: the browser panel ran
     past the right edge and the badge was sliced in half.

     6px caps it at 219 by 234, which clears the bottom of the
     shortest card this component draws with the 4.7em top inset
     below. The supplied artwork's own scale is 10px; that is for
     a card 635px tall, and this one is about 330. */
  font-size: clamp(3px, 1.05cqi, 6px);
  pointer-events: none;
}

.es-promo__glow {
  position: absolute;
  top: -1em;
  right: -5em;
  width: 31em;
  height: 39em;
  border-radius: var(--radius-full);
  background: radial-gradient(circle,
    color-mix(in srgb, var(--promo-chip) 72%, transparent),
    transparent 70%);
  filter: blur(3px);
}

/* -- The browser ----------------------------------------- */
.es-promo__browser {
  position: absolute;
  top: 1.5em;
  left: 1em;
  width: 30em;
  height: 24.5em;
  padding: 2.3em 2.5em;
  border: 1px solid color-mix(in srgb, var(--promo-edge) 70%, transparent);
  border-radius: 2.4em;
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--bg-surface) 95%, transparent),
    color-mix(in srgb, var(--promo-ground) 92%, transparent));
  box-shadow: 0 2em 4.5em color-mix(in srgb, var(--promo-ink) 10%, transparent);
  transform: rotate(0.5deg);
}

.es-promo__dots {
  display: flex;
  gap: .8em;
}

.es-promo__dots i {
  display: block;
  width: 1.2em;
  height: 1.2em;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--promo-ink) 30%, transparent);
}

.es-promo__bar {
  display: block;
  height: 1.1em;
  margin-top: 2.5em;
  border-radius: 2em;
  background: color-mix(in srgb, var(--promo-ink) 20%, transparent);
}

.es-promo__bar--lg { width: 14.5em; }

.es-promo__bar--sm {
  width: 9.2em;
  margin-top: 1.3em;
}

.es-promo__panel {
  display: flex;
  align-items: center;
  gap: 2.2em;
  margin-top: 2.6em;
  padding: 2em;
  border-radius: 1.8em;
  background: color-mix(in srgb, var(--promo-chip) 70%, transparent);
}

.es-promo__chart {
  display: flex;
  align-items: flex-end;
  gap: .5em;
  height: 5.5em;
}

.es-promo__chart i {
  display: block;
  width: 1.1em;
  border-radius: .5em .5em .2em .2em;
  background: color-mix(in srgb, var(--promo-ink) 32%, transparent);
}

.es-promo__chart i:nth-child(1) { height: 2.2em; }
.es-promo__chart i:nth-child(2) { height: 3.4em; }
.es-promo__chart i:nth-child(3) { height: 4.5em; }
.es-promo__chart i:nth-child(4) { height: 2.9em; }

.es-promo__lines {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 1.3em;
}

.es-promo__lines i {
  display: block;
  height: 1em;
  border-radius: 2em;
  background: color-mix(in srgb, var(--promo-ink) 26%, transparent);
}

.es-promo__lines i:first-child { width: 100%; }
.es-promo__lines i:last-child  { width: 70%; }

/* -- The disc --------------------------------------------
   The supplied analytics badge. Its mark is `trend-up` from the
   shared set rather than the three bars the reference draws by
   hand, which is the same statement in the site's own hand. */
.es-promo__disc {
  position: absolute;
  top: 11.5em;
  right: .8em;
  display: grid;
  place-items: center;
  width: 10.2em;
  height: 10.2em;
  border: .6em solid color-mix(in srgb, var(--bg-surface) 80%, transparent);
  border-radius: var(--radius-full);
  background: linear-gradient(145deg, var(--bg-surface), var(--promo-ground));
  box-shadow: 0 1.5em 3.5em color-mix(in srgb, var(--promo-ink) 12%, transparent);
  color: var(--promo-ink);
}

.es-promo__disc svg {
  width: 4.8em;
  height: 4.8em;
}

/* -- The app card's artwork ------------------------------
   The supplied bag and checklist. The bag is `bag` from the
   shared set on a disc, not the clip-path bag with a serif S the
   reference draws: that one is a redrawing of somebody else's
   trademark, and the instruction was to use ours. */
.es-promo__visual--app {
  top: 4.7em;
  right: 4.6em;
  width: 34em;
  height: 35em;
}

.es-promo__visual--app .es-promo__glow {
  top: 0;
  right: 0;
  width: 27.5em;
  height: 27.5em;
}

.es-promo__disc--bag {
  top: 3.2em;
  right: 3em;
  width: 11em;
  height: 11em;
}

.es-promo__disc--bag svg {
  width: 5.4em;
  height: 5.4em;
}

.es-promo__checklist {
  position: absolute;
  right: 6.5em;
  bottom: 2.2em;
  width: 19em;
  padding: 2.2em 1.8em;
  border-radius: 1.9em;
  background: var(--bg-surface);
  box-shadow: 0 1.5em 3.5em color-mix(in srgb, var(--promo-ink) 12%, transparent);
}

.es-promo__check {
  display: flex;
  align-items: center;
  gap: 1.5em;
  height: 3.3em;
}

.es-promo__check i {
  display: grid;
  flex: 0 0 2.5em;
  place-items: center;
  width: 2.5em;
  height: 2.5em;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--promo-ink) 45%, transparent);
  color: var(--bg-surface);
}

.es-promo__check i svg {
  width: 1.4em;
  height: 1.4em;
}

.es-promo__check b {
  display: block;
  width: 10em;
  height: 1em;
  border-radius: 2em;
  background: var(--border-subtle);
}


/* ============================================================
   THE THREE STATES
   The supplied code's mobile, tablet and desktop, asked of the
   CARD rather than of the window. See the note at the top of
   this region for why.
   ============================================================ */

/* -- Under 420: the phone --------------------------------
   One field per row and no artwork. The artwork goes rather than
   being faded further: at this width the copy runs the full
   card, so there is no corner left for it to occupy, and it is
   decoration with nothing in it a reader needs. Nothing reflows
   when it goes, because it was never in the flow. */
@container promo (max-width: 419px) {
  .es-promo__visual { display: none; }
  .es-promo__form .es-btn { justify-self: stretch; }

  /* The supplied breaks are drawn for a card wide enough to hold
     them. On a phone the line they force is shorter than the one
     wrapping would give, so they go, exactly as the supplied code
     drops them at its own narrow end. */
  .es-promo__title br,
  .es-promo__desc br { display: none; }
}

/* -- 420 and up: the two fields share a line -------------- */
@container promo (min-width: 420px) {
  .es-promo__fields { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* -- 420 to 760: the tablet ------------------------------
   The copy keeps the full card and the artwork drops behind it,
   pushed off the right edge. This is the supplied code's own
   answer for a card too narrow to stand the two side by side,
   and it is the state the card is in at 576, 768, 1280 and 1440. */
@container promo (min-width: 420px) and (max-width: 759px) {
  .es-promo__visual,
  .es-promo__visual--app {
    /* Inside the card, not hanging off it. The supplied code
       pushes the drawing off the right edge at this width and
       lets the card clip it; the owner asked for it to fit, so
       it keeps the card's own inset and drops in opacity
       instead. Opacity is the whole of what changes here. */
    right: 4.6em;
    opacity: .28;
  }
}

/* -- 760 and up: the desktop -----------------------------
   The copy takes its share of the card and the artwork stands
   beside it at full strength, which is the arrangement the
   supplied layout is drawn in. On this page that is the 992px
   viewport, where the offers take a line of their own and the
   card is about 900px wide.

   760 and not the 640 this started at. The drawing is 36.5em
   wide and sits 4.6em off the right edge, so it needs about
   410px of card before its left edge clears a 22ch headline. At
   640 the 768px viewport qualified with 662px of card and the
   headline ran 30px into the artwork at full opacity. 760 is the
   narrowest card where the two stand side by side without
   touching. */
@container promo (min-width: 760px) {
  .es-promo--audit .es-promo__body,
  .es-promo--audit .es-promo__form { width: 68%; }

  .es-promo--app .es-promo__body,
  .es-promo--app .es-promo__lockup { width: 72%; }
}

@media (prefers-reduced-motion: reduce) {
  .es-promo,
  .es-promo__waitlist-icon,
  .es-promo__cta,
  .es-promo__cta .arrow-icon path,
  button.esn-panefoot__cta,
  button.esn-panefoot__cta .arrow-icon path { transition: none; animation: none; }
  .es-promo:hover { transform: none; }
}

/* -- Confirmation -----------------------------------------
   What the form is replaced by, in the same card, because that
   is where the request was made and because leaving a filled
   form standing under a thank-you invites a second send.

   It arrives with motion, and the motion is doing a job rather
   than decorating: something the visitor was looking at has
   just been taken out of the card and something else has taken
   its place. A cut reads as a glitch; half a second of settle
   reads as an answer.

   Only `opacity`, `transform` and `stroke-dashoffset` are
   animated, so none of it touches layout.

   No `forwards` and no `both` on any of the animations below,
   and the resting style of every element is its end state
   rather than its start. `both` parks an element on the `from`
   frame before the animation runs, and anywhere the animation
   does not run that leaves the panel at `opacity: 0` and the
   visitor told nothing. */

.es-promo__done {
  animation: promoDone var(--duration-slow) var(--ease-out);
}

.es-promo__done > * + * { margin-top: var(--space-3); }

.es-promo__done:focus-visible {
  outline: 2px solid var(--field-ring, var(--brand-500));
  outline-offset: 6px;
}

/* `from` only. The unstated `to` is the element's own computed
   style, which is the visible resting state. */
@keyframes promoDone {
  from { opacity: 0; transform: translateY(10px); }
}

.es-promo__done-title {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin: 0;
  color: var(--text-heading);
  font-size: var(--fs-card-title);
  font-weight: var(--font-extrabold);
  line-height: var(--lh-subhead);
}

/* The disc the tick is drawn inside. `aria-hidden` in the
   markup, because the heading beside it already says what it
   means. */
.es-promo__done-mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--eyebrow-bg);
  color: var(--eyebrow-ink);
  animation: promoMark var(--duration-slow) var(--ease-spring);
}

/* One ring, out and gone. Drawn on a pseudo element so that
   nothing in the layout has to make room for it. */
.es-promo__done-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--eyebrow-ink);
  /* The one element here whose end state is the resting one:
     a ring that has gone. */
  opacity: 0;
  animation: promoRing .9s var(--ease-out) .1s;
}

.es-promo__done-mark svg {
  width: 20px;
  height: 20px;
}

@keyframes promoMark {
  from { opacity: 0; transform: scale(.5); }
}

@keyframes promoRing {
  from { opacity: .55; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.7); }
}

/* Everything above is an entrance, so the reduced-motion answer
   is the end state rather than a shorter version of the
   journey. */
@media (prefers-reduced-motion: reduce) {
  .es-promo__done,
  .es-promo__done-mark,
  .es-promo__done-mark::after { animation: none; }
}
