/* ==========================================================================
   The single post — Elementor template 3149, "Blog detail"

   Reference design: prototypes/post.html
   Plan: docs/superpowers/plans/2026-09-02-single-post.md
   Checker: tools/post.py (four fixtures: many-h2, no-h2, one-h2, longest)

   An article is a /blog card opened up, so it keeps that page's skeleton:
   mist hero, white body, the global ink CTA, the global mist footer.

   WHAT LIVES HERE, AND WHAT DOES NOT. Every element on this page except one
   is an Elementor widget with a real Style panel, and its colour and type
   belong in _elementor_data as Global Kit references — that is Rule 1, and it
   is why the hero rules below are deliberately written to LOSE to a control.
   The exception is the prose block, and it is a stated exception: the Post
   Content widget renders whatever HTML the editor wrote, and its ENTIRE Style
   section is `align`, `text_color` and one `typography` group (read from
   modules/theme-builder/widgets/post-content.php). There is no control for
   "h2 inside the body" or "a inside a paragraph", so those rules have nowhere
   else to live. Same class of exception as /blog's categories sidebar and the
   mega-menu.

   ------------------------------------------------------------------------
   THERE IS ALREADY A COMPETING ARTICLE STYLESHEET, AND IT IS NOT IN ANY
   FILE. A WPCode snippet titled "Coveredly Blog Post Styles" — 11,015 bytes,
   45 selectors, stored in the database and printed inline into <head> as
   <style class="wpcode-css-snippet"> — restyles this page end to end: the
   post title, the featured image, the post-info meta, the related cards and
   the whole prose block. It is where today's defects actually come from:

     .elementor-location-single … .elementor-widget-container a
         { color:#0092D5; text-decoration:none; font-weight:600 }   <- 3.46,
       and no non-colour affordance. THE defect this redesign exists to fix,
       and it is not in _elementor_data, so reading the data would never
       have found it.
     … .elementor-widget-container      { color:var(--global--color-secondary) }  #42423E
     … .elementor-widget-container h2/h3/h4 { font-family:var(--global--font-primary) }  Inter
     … .elementor-widget-theme-post-title/-featured-image/-post-content
         { max-width:56rem; margin-inline:auto; padding:… 3rem … }   <- the
       THREE different left edges the design has to collapse into one. Each
       widget is separately centred in its own max-width, so no two of them
       start at the same x.

   TWO CONSEQUENCES FOR THIS FILE:

   1. EVERY SELECTOR IS SCOPED TO `.elementor-location-single`, which is
      FRONT-END ONLY — theme-document.php adds it solely when the Locations
      Manager rendered the document, so it is absent in the editor. The
      canvas and the live page therefore disagree about the entire article.
      Nothing in this file is scoped that way (translation 2 of the plan);
      footer.css was written like that once and every rule in it was
      invisible while editing.
   2. Its prose rules land at (0,0,3,1), so a component rule at (0,0,1,1)
      loses on the front end and wins in the editor — the worst of both. The
      prose block below therefore carries an ARMOURED PREFIX:

          .elementor .elementor-element.elementor-widget.bnly-post__body

      four classes, so every prose rule sits at (0,0,4,x) and beats the
      snippet deterministically rather than by load order, which the editor
      reverses. `.elementor` is guaranteed: Elementor wraps the template in
      `<div class="elementor elementor-3149">`.

   THE SNIPPET SHOULD BE RETIRED, not out-specified forever. It is another
   brand's stylesheet ("Coveredly"), it carries `!important` on the post
   title's font-size, weight, letter-spacing and colour — which no
   specificity in this file can beat, so the h1 will keep rendering 2.25rem
   Inter until the snippet is disabled — and it makes the editor lie about a
   page it fully restyles. Disabling it is a database action and belongs with
   the template rebuild, not here.

   ------------------------------------------------------------------------
   THE SPECIFICITY LADDER, measured from the live post-3149.css rather than
   assumed. Every tier below was read out of that file:

     (0,0,2,0)  Elementor's WIDGET DEFAULT
                `.elementor-widget-heading .elementor-heading-title{color:…}`
     (0,0,3,0)  a per-element control that writes to {{WRAPPER}}
                `.elementor-3149 .elementor-element.elementor-element-54a15a9
                 {color:#757575;font-family:"Poppins";font-size:16px}`
     (0,0,4,0)  a per-element control that writes to a DESCENDANT
                `… .elementor-element-3c7a6a4 .elementor-heading-title{…}`

   So the hero rules sit at (0,0,3,0): ABOVE the widget default, so an
   unconfigured widget still looks right, and BELOW the per-element control,
   so every Style panel still wins and never lies. That is deliberate, and it
   is why those selectors are longer than they look like they need to be.

   The prose block breaks that on purpose at (0,0,4,x) — partly for the
   snippet above, partly because widget 54a15a9 carries a baked
   `color:#757575`, which is the documented --body trap (4.24 on mist,
   failing) and one of the two colours tools/post.py bans by name. A baked
   value permanently defeats the kit, so the stylesheet has to win until that
   key is cleared in the data.

   ------------------------------------------------------------------------
   FOUR TRANSLATIONS FROM THE PROTOTYPE, each a trap this project has paid
   for already:

   1. THE KIT EMITS NO `navy` AND NO `brand` VARIABLE. Navy is
      `--e-global-color-secondary`; brand is `--e-global-color-primary`. The
      prototype declares its own :root with the friendly names, so porting
      `var( …-navy )` verbatim looks right in the browser tab it was designed
      in and fails here. The failure is worse than a wrong colour: an
      unresolvable var() is invalid at computed-value time, so the WHOLE
      declaration falls back to initial — `outline:2px solid var(…-navy)`
      computes to style none, width medium, colour currentColor. Not a
      mis-coloured focus ring; no ring at all. It cost three of them on /blog.
      Elementor writes these WITH SPACES inside the parens, mirrored here.
   2. Nothing is scoped to `.elementor-location-*` — see above.
   3. The prototype's `:root`, `.wrap`, `body`, `*{box-sizing}` and
      `img{max-width:100%}` do NOT port. The kit declares the colours, the
      section's boxed container is the 1200 rail, and Elementor normalises the
      rest. A `.wrap` rule here would match nothing at all.
   4. `.rcard*` does NOT port. Those are the related cards, and they are the
      Pro Posts widget with a real Style panel: frame, radius, padding,
      colour and type all live in the data (plan Task 6 Step 6). Restating
      them here would make that panel lie — the CTA band's mistake.

   --benely-headerh is declared once, on .bnly-post, so the sticky offset and
   every heading's scroll-margin-top read from ONE place. The site header is
   position:fixed and 120px tall at every width, measured 1440 down to 390.
   ========================================================================== */

/* ==========================================================================
   ONE SYSTEM: THE BANDS' PADDING IS A CONTROL, NOT A DECLARATION

   `.bnly-posthero`, `.bnly-post` and the two `.bnly-related` sections used to
   carry `padding-block:clamp(40px,4.4vw,64px)` here while the INLINE gutter
   came from a boxed 1340 container plus 35px of column padding. Two half
   systems for one property, and the article paid for it: its rail started at
   85 (50 + 35) at 1440 and 15 at 390, while /blog and every category archive
   start at 120 and 20. Four fixtures, both viewports, all red.

   Splitting a property between a stylesheet and an Elementor control does not
   MERGE the two halves, it lets the control silently delete the stylesheet's:
   Elementor writes `.elementor-3149 .elementor-element.elementor-element-<id>
   {padding:...}` at (0,0,3,0) and emits it in EVERY media query, while a bare
   component class is (0,0,1,0). A shorthand `padding` zeroes the axis you were
   not thinking about. This project has now hit that failure four times - the
   /resources insights band's `padding-block` was dead for its whole life and
   84 green assertions never noticed, because they all asserted that two bands
   TOUCH and none asserted that anything below them had room.

   So the whole property is in ONE system, and it is the CONTROL - which is
   what an editor reaches for, and which outranks a component class anyway:

     section    content_width   padding (top/right/bottom/left)
     3ab6fc5    boxed 1200      clamp(40px,4.4vw,64px) / 20 / same / 20
     f802b88    boxed 1200      clamp(40px,4.4vw,64px) / 20 / same / 20
     b12eae4    boxed 1200      clamp(40px,4.4vw,64px) / 20 / 0 / 20
     291c0c6    boxed 1200      0 / 20 / clamp(40px,4.4vw,64px) / 20

   with 40px gutters at tablet, 20 at mobile, every breakpoint set EXPLICITLY,
   and the four columns' padding zeroed - the 35px that produced the 85.

   The top/bottom values are the custom unit, so the clamp is stored verbatim
   (`{"unit":"custom","top":"clamp(40px,4.4vw,64px)",...}`) and the rhythm is
   byte-identical to what this file used to emit: measured 63.4px at 1440 and
   40px at 390, before and after. base-units.php drops the unit suffix for
   `custom`, and DIMENSIONS lists it in `size_units`, so the value stays fluid
   AND stays in the panel - Rule 1 intact.

   The last two `.bnly-related` rules went with it: `+ .bnly-related
   {padding-top:0}` and `:has(.bnly-related__head){padding-bottom:0}` existed
   only to stop the band's rhythm being paid twice where the heading section
   meets the cards section. That is now expressed by the two sections' own
   controls (bottom 0 / top 0), so leaving the CSS would be a second system
   again - and a dead one, at (0,0,2,0) under a control at (0,0,3,0).
   ========================================================================== */

/* ==========================================================================
   THE HERO — mist, exactly as /blog and the archives

   No cover in here. The cover is a body element, below the title, so the
   hero stays a block of type and the article's first screen is words.
   ========================================================================== */
/* PADDING IS NOT HERE, AND THAT IS THE POINT. The whole property - block AND
   inline - lives in the section's Padding control, set to
   `clamp(40px,4.4vw,64px)` top and bottom (the custom unit, so the fluid value
   survives) and a 20/40/20 gutter. See ONE SYSTEM below. */
.bnly-posthero{
  background:var( --e-global-color-mist );
}

/* --- back-link ------------------------------------------------------------
   A heading widget carrying a link, so the DOM is
   div.bnly-posthero__back > div.elementor-widget-container >
   div.elementor-heading-title > a. Both the block box and the inner text box
   are listed, so the rule holds whichever element ends up wearing the class.

   THE ARROW CANNOT SIT ON THE WIDGET DIV. .elementor-widget-container is
   display:block, so a ::before on its parent would be an inline box followed
   by a block box and the arrow would land on a line of its own. It goes on
   the innermost text element instead.

   Navy on mist is 9.06. The glyph is a real character, so nothing has to
   load for it. `font-weight` is restated because the WPCode snippet sets
   `font-weight:600` on every <a> inside the single template. */
.bnly-posthero__back{
  display:inline-block;
  margin:0 0 18px;
}
.bnly-posthero .bnly-posthero__back,
.bnly-posthero .bnly-posthero__back .elementor-heading-title,
.bnly-posthero .bnly-posthero__back a{
  font:600 13px/1 'Poppins',system-ui,sans-serif;
  letter-spacing:.02em;
  color:var( --e-global-color-secondary );
  text-decoration:none;
}
.bnly-posthero__back .elementor-heading-title::before,
a.bnly-posthero__back::before{
  content:"\2190";
  margin-right:8px;
}
/* The shorthand is deliberate: the parent theme carries a bare
   `a:hover{text-decoration-style:dotted}` at (0,0,1,1), so setting only the
   line here would leave the underline dotted. */
.bnly-posthero .bnly-posthero__back:hover,
.bnly-posthero .bnly-posthero__back a:hover{
  text-decoration:underline solid;
}
.bnly-posthero .bnly-posthero__back:focus-visible,
.bnly-posthero .bnly-posthero__back a:focus-visible{
  outline:2px solid var( --e-global-color-secondary );
  outline-offset:3px;
  border-radius:2px;
}

/* --- the eyebrow ----------------------------------------------------------
   The hairline the other heroes carry. `flex:1` needs a parent that is not
   shrink-wrapped — on the archive that rule collapsed to 0px because the
   container stretched its children — and tools/post.py measures the rendered
   ::after width for exactly that reason, so the flex context must sit on the
   element carrying the class, not on anything inside it. */
.bnly-posthero__kicker{
  display:flex;
  align-items:center;
  gap:14px;
  margin:0 0 12px;
}
.bnly-posthero .bnly-posthero__kicker,
.bnly-posthero .bnly-posthero__kicker .elementor-heading-title{
  font:700 11.5px/1.2 'Poppins',system-ui,sans-serif;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var( --e-global-color-secondary );
}
.bnly-posthero__kicker::after{
  content:"";
  flex:1;
  max-width:96px;
  height:1px;
  background:var( --e-global-color-edge );
  opacity:.5;
}

/* --- the H1 ---------------------------------------------------------------
   THE WRAPPER RESET IS NOT COSMETIC. The WPCode snippet gives this widget
   `max-width:56rem; margin-inline:auto; padding:4rem 3rem 3rem`, and the
   cover and the prose each get a DIFFERENT max-width and padding — which is
   precisely how the page ends up with three left edges at 290, 274 and 338,
   none of them the rail. Releasing the wrapper is what collapses them to
   one; tools/post.py's `align` group measures the three against EACH OTHER
   rather than against a constant, because a stagger is invisible to any
   assertion that measures one element.

   `ch` resolves against the ELEMENT'S OWN font, so the measure cap sits on
   the heading and never on the wrapper: on /blog a 20ch cap on a widget
   wrapper computed to 249.7px, because the wrapper inherits the page's 20px
   sans while the heading is Newsreader 48px.

   Two things still outrank everything here, and both are data, not CSS: the
   widget's baked `font-family:"Poppins";font-size:32px;color:#333333` at
   (0,0,4,0), and the snippet's `font-size:2.25rem !important`. The rebuild
   has to clear the first and retire the second. */
.elementor .elementor-element.elementor-widget.bnly-posthero__title{
  max-width:none;
  margin-inline:0;
  padding:0;
}
.bnly-posthero .bnly-posthero__title,
.bnly-posthero .bnly-posthero__title .elementor-heading-title{
  margin-block:0;
  font:500 clamp(30px,3.8vw,48px)/1.14 'Poppins',sans-serif;
  letter-spacing:-.015em;
  color:var( --e-global-color-ink );
  max-width:24ch;
}

/* --- date + reading time --------------------------------------------------
   The only two meta fields that exist on all 347 posts: `post_author` is 1 on
   170 of them and user 1 does not exist, so a byline would render blank on
   half the site. The separator is a real middot in the content, not a
   pseudo-element, so it copies with the text.

   The <p> is listed because the parent theme's bare `p{line-height:…}`
   matches it directly, and a rule that matches beats an inherited value
   whatever the specificity. */
.elementor .elementor-element.elementor-widget.bnly-posthero__meta{
  max-width:none;
  margin-inline:0;
  padding:0;
}
.bnly-posthero .bnly-posthero__meta,
.bnly-posthero .bnly-posthero__meta p{
  font:600 13px/1.4 'Poppins',system-ui,sans-serif;
  letter-spacing:.02em;
  color:var( --e-global-color-secondary );
}
.bnly-posthero .bnly-posthero__meta{ margin:16px 0 0 }
.bnly-posthero .bnly-posthero__meta p{ margin:0 }

/* ==========================================================================
   THE BODY — cover, then prose beside a sticky index
   ========================================================================== */
.bnly-post{
  /* the site header is position:fixed and 120px tall at EVERY width,
     measured 1440 down to 390. Anything sticky must clear it, and so must
     every heading an index jump lands on. ONE declaration, read by the
     sticky `top` and by every scroll-margin-top below. */
  --benely-headerh:120px;
  /* No padding here - the whole property is section f802b88's Padding
     control. See ONE SYSTEM above: a control at (0,0,3,0), emitted in every
     media query, would silently delete a half written at (0,0,1,0). */
}

/* --- the cover ------------------------------------------------------------
   16:9 with the cards' hairline, for the reason recorded on /blog: these
   images are 67-77% near-white (measured across the 12 most recent, all
   1672x941), so without a line the cover's edge dissolves into the page.

   The wrapper reset is the same story as the h1: the snippet centres this
   widget in a 64rem box with 3rem of side padding, which is one of the three
   left edges. The snippet ALSO puts `aspect-ratio:2/1` and a 1.5rem radius
   on .elementor-widget-container at (0,0,3,0), so the ratio has to be
   cancelled there or two ratio boxes fight and the image letterboxes.

   THE !important IS NOT A CASCADE FIGHT, it is an inline style. The parent
   theme filters wp_get_attachment_image_attributes and prepends
   `width:100%;height:56.28%;max-width:<natural>px` onto every attachment
   image, and an inline style beats any stylesheet rule that lacks it. A
   plain `height:100%` loses silently and the image collapses inside the
   ratio box. Verify with getComputedStyle(img).height, never by reading the
   generated CSS — the rule exists either way.

   The widget's own Border and Border Radius controls emit at (0,0,4,0) and
   will outrank this if an editor sets them. That is correct. */
.elementor .elementor-element.elementor-widget.bnly-post__cover{
  max-width:none;
  margin:0 0 clamp(28px,3vw,44px);
  padding:0;
}
.bnly-post__cover{
  margin:0 0 clamp(28px,3vw,44px);
  border:1px solid rgba(124,136,148,.28);
  border-radius:var(--benely-radius,8px);
  background:var( --e-global-color-mist );
  aspect-ratio:16/9;
  overflow:hidden;
}
.elementor .elementor-element.elementor-widget.bnly-post__cover > .elementor-widget-container,
.bnly-post__cover figure,
.bnly-post__cover a{
  display:block;
  height:100%;
  aspect-ratio:auto;
  border-radius:inherit;
  overflow:hidden;
}
.bnly-post__cover img{
  width:100%!important;
  height:100%!important;
  max-width:none!important;
  object-fit:cover;
  border:0;
  border-radius:0;
  margin:0;
}

/* --- the two-column grid --------------------------------------------------
   In the Elementor build the class sits on a SECTION, and a section's
   children are not the columns — `.elementor-container` is. So the grid is
   declared on that inner element, with the bare form kept for the case where
   the class lands on the element that directly holds the two children.

   align-items:start, NOT stretch: a stretched grid item is exactly as tall
   as its area, and a sticky box inside one has zero room to travel, which is
   inert and looks precisely like a page that simply scrolls. */
/* THE PROSE AND THE INDEX FILL THE RAIL, ending level with the cover.

   `1fr` gives the prose every pixel the index does not - 864px at 1440 - so
   prose + gap + index is exactly the 1200 rail and the index's right edge
   lands on 1320, the same number the cover ends on. The three blocks read as
   one column of content rather than a text column with furniture floating
   beside it.

   THE COST, MEASURED, so nobody has to rediscover it: with the prose filling
   864px the first line holds 93-103 characters, against the 65-75 that reads
   comfortably. The measure cap that used to hold it at 66 is gone
   DELIBERATELY - it is what left 299px of the track dead and pushed the index
   355px from the text. Restoring the cap re-opens that gap; the two cannot
   both be had at this rail width with a 280px index. If the line length
   matters more than the alignment, widen the INDEX rather than re-capping the
   prose: at 520px the prose falls to about 75 characters and the pair still
   fills the rail.

   align-items:start, NOT stretch: a stretched grid item is exactly as tall
   as its area, and a sticky box inside one has zero room to travel. */
.bnly-post__grid > .elementor-container,
.bnly-post__grid > .e-con-inner,
.bnly-post__grid:not(:has(> .elementor-container)):not(:has(> .e-con-inner)){
  display:grid;
  grid-template-columns:minmax(0,1fr) 280px;
  gap:clamp(32px,4vw,56px);
  align-items:start;
}

/* NO INDEX, NO TRACK.

   `[benely_post_index]` returns the empty string below three headings - 100
   of the 347 published posts - so there is no box to see. But the Elementor
   COLUMN that held the shortcode renders either way, and the track it sat in
   was still being paid for. Measured on
   /navigating-u-s-healthcare-trends-in-2025-…/ before this rule:

     @1440  a 280x1px column plus a 56px gap. Invisible, because the prose is
            measure-capped at 50ch and the right of the rail is empty either
            way - but the grid was describing a layout that was not there.
     @390   the grid is already one column, so the empty column STACKED BELOW
            the prose: a 32px row gap and a 1px box appended after the
            article, ahead of the related band. That one is visible, and it
            was on every post under the threshold.

   `:has()` is the right tool and not a flourish: the column is byte-identical
   whether or not the shortcode produced output, so "did it render anything?"
   is the only signal available, and PHP cannot reach across to a sibling
   column to answer it.

   The second rule is scoped `:not(:has(.bnly-post__body))` rather than to
   `:last-child` alone. A bare `:last-child` would hide the PROSE on any
   future layout that has one column instead of two - a rule that deletes the
   article is not worth the four characters it saves. */
.bnly-post__grid > .elementor-container:not(:has(.bnly-post__index)),
.bnly-post__grid > .e-con-inner:not(:has(.bnly-post__index)),
.bnly-post__grid:not(:has(> .elementor-container)):not(:has(> .e-con-inner)):not(:has(.bnly-post__index)){
  grid-template-columns:minmax(0,1fr);
}
.bnly-post__grid > .elementor-container:not(:has(.bnly-post__index))
  > .elementor-column:not(:has(.bnly-post__body)),
.bnly-post__grid > .e-con-inner:not(:has(.bnly-post__index))
  > .e-con:not(:has(.bnly-post__body)){
  display:none;
}

/* The grid owns both track widths, so a column's own width must not also
   apply. Elementor writes `_inline_size` as `width:<n>%` on the column at
   (0,0,3,0), and inside a grid track a percentage is a percentage OF THE
   TRACK — a 72% column would sit at 72% of its 1fr track and the article
   would lose the single left edge the `align` group asserts. Five classes
   clears the per-element rule deterministically rather than by load order,
   which the editor reverses.

   NOTE for the rebuild, which this file deliberately does not fix: a
   section's Columns Gap control writes `padding:10px` onto
   .elementor-element-populated, and that inset would stagger the prose 10px
   inboard of the hero — the defect that hid behind 296 green assertions on
   the archive. It belongs in the data (gap none / padding 0), because a rule
   for it here would shadow a real control. */
.bnly-post .bnly-post__grid > .elementor-container > .elementor-element.elementor-column{
  width:auto;
}

/* --- the prose ------------------------------------------------------------
   RULE 1 NOTE, and it is the reason this block is large. Everything else on
   this page is an Elementor element with a Style panel. This is not: the
   Post Content widget renders arbitrary editor HTML — h2/h3/p/ul/ol/table/
   blockquote/figure — and has no per-tag controls, so these rules have
   nowhere else to live.

   THE ARMOURED PREFIX (see the header) puts every rule below at (0,0,4,x),
   above the WPCode snippet's (0,0,3,x) and above the widget's own baked
   `color:#757575` at (0,0,3,0). It is a deliberate shadow of `text_color`
   and `typography`, and THE CORRECT FOLLOW-UP is to clear those two keys on
   widget 54a15a9 (to "" plus a __globals__ reference to `deep`) and retire
   the snippet, after which this prefix can come back down to the bare class.

   The wrapper reset comes first for the same reason as the h1's: the snippet
   centres this widget in a 56rem box with 3rem of side padding, which is the
   third of the three left edges.

   THE MEASURE CAP IS 50ch, NOT 68ch, AND THAT IS DELIBERATE. `ch` is the
   width of the "0" glyph and Poppins' average character is much narrower
   than its digit, so a cap in ch renders far more characters than its number
   suggests: 68ch measured 769px and fitted 95 characters on the first line.
   50ch lands in the 65-75 band that actually reads well. Measure the
   characters, never trust the unit — which is what tools/post.py does. */
.bnly-post__body,
.elementor .elementor-element.elementor-widget.bnly-post__body{
  margin-inline:0;
  padding:0;
  font:400 18px/1.75 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );   /* 8.86 on white. NOT #42423E, which
     the snippet sets, and NOT the `text` token #757575, which the widget
     bakes — tools/post.py bans both by name. */
}
/* The snippet paints the INNER container, a closer ancestor than the widget
   div, and a nearer rule beats a farther one for an inherited value however
   the specificity falls. So the block's face and colour have to be restated
   on the same element it targets. */
.elementor .elementor-element.elementor-widget.bnly-post__body > .elementor-widget-container{
  font:400 18px/1.75 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );
}

/* HALF THE ARTICLES ARE THEMSELVES ELEMENTOR DOCUMENTS, and the rule above
   never reaches their prose. Measured 2026-09-02: 171 of 347 published posts
   carry `_elementor_edit_mode = builder`, so theme-post-content renders a
   NESTED `<div class="elementor elementor-<postid>">` and the whole article
   sits inside a single Text Editor widget one document deeper.

   Nothing is baked in their data — grep found `#757575` in only 3 of the 171.
   The colour and size are Elementor's own GLOBAL DEFAULTS for an unconfigured
   Text Editor: `text_color` defaults to COLOR_TEXT and `typography` to
   TYPOGRAPHY_TEXT, which this kit sets to #757575 and Poppins 16px. Elementor
   emits them per-element, `.elementor-12233 .elementor-element-3acd5826
   {color:#757575;font-size:16px}` at (0,0,3,0) — a NEARER element than the
   .bnly-post__body widget, so it wins the inherited value outright. Measured
   on /navigating-u-s-healthcare-trends-in-2025/: every ancestor above that
   widget computed deep 18px and the <p> computed rgb(117,117,117) 16px. That
   is the documented --body trap, and it is one of the two colours
   tools/post.py bans by name.

   Only the block-level face and colour are restated. The headings, links,
   tables and quotes inside are raw tags in the editor's HTML and the rules
   above already match them at the same tier.

   RULE 1: this shadows a Style panel — but the panel is showing Elementor's
   own default, not an editor's choice, and correcting it in the data would
   mean writing 171 posts and would still leave the 172nd wrong. Recorded here
   rather than hidden: the fix belongs in the kit's `text` token, which is
   #757575 site-wide and fails on mist. */
.elementor .elementor-element.elementor-widget.bnly-post__body .elementor-widget-text-editor,
.elementor .elementor-element.elementor-widget.bnly-post__body
  .elementor-widget-text-editor > .elementor-widget-container{
  font:400 18px/1.75 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );
}

/* Both forms of the child axis: with Elementor's inner wrapper present the
   prose is a grandchild; without it (the e_optimized_markup experiment, and
   the prototype's own markup) it is a child. */
.bnly-post__body > *,
.elementor .elementor-element.elementor-widget.bnly-post__body > .elementor-widget-container > *{
  margin-block:0 1.25em;
}
.bnly-post__body > *:last-child,
.elementor .elementor-element.elementor-widget.bnly-post__body > .elementor-widget-container > *:last-child{
  margin-bottom:0;
}

/* Body headings in the DISPLAY face. Today these are Inter, a family in no
   part of the design system — it arrives from the snippet as
   `var(--global--font-primary)`, so nothing in the DATA would ever reveal
   it. Assert the COMPUTED family, which is what the `type` group does. */
.elementor .elementor-element.elementor-widget.bnly-post__body h2{
  margin-block:1.9em .5em;
  font:500 clamp(24px,2.2vw,30px)/1.22 'Poppins',sans-serif;
  letter-spacing:-.012em;
  color:var( --e-global-color-ink );
  /* so an index jump does not land the heading behind the fixed header */
  scroll-margin-top:calc(var(--benely-headerh,120px) + 24px);
}
.elementor .elementor-element.elementor-widget.bnly-post__body h2:first-child,
.elementor .elementor-element.elementor-widget.bnly-post__body
  > .elementor-widget-container > h2:first-child{
  margin-top:0;
}
.elementor .elementor-element.elementor-widget.bnly-post__body h3{
  margin-block:1.6em .4em;
  font:500 clamp(19px,1.6vw,22px)/1.3 'Poppins',sans-serif;
  letter-spacing:normal;
  color:var( --e-global-color-ink );      /* was #757575 */
  scroll-margin-top:calc(var(--benely-headerh,120px) + 24px);
}

/* ANY jump target in the prose, not just the headings.

   Putting scroll-margin-top on h2/h3 alone assumed the heading IS the target.
   It often is not: many of these posts carry their own in-content table of
   contents and place an empty anchor immediately before each heading -
   `<a id="why-childcare-…"></a>` - and that anchor is what a fragment
   resolves to. It is not an h2, so it never matched the rules above, and
   every one of those jumps landed the section flush under the 120px fixed
   header. Measured -120px before this rule.

   `[id]` covers the author's anchors, the headings, and anything else a
   future post links to, which is the point: the target is chosen by whoever
   wrote the content, not by this stylesheet. */
.elementor .elementor-element.elementor-widget.bnly-post__body [id]{
  scroll-margin-top:calc(var(--benely-headerh,120px) + 24px);
}
.elementor .elementor-element.elementor-widget.bnly-post__body h4{
  margin-block:1.4em .35em;
  font:500 16px/1.35 'Poppins',system-ui,sans-serif;
  letter-spacing:.01em;
  color:var( --e-global-color-ink );
}

/* A rule that matches beats an inherited value, whatever the specificity:
   the parent theme carries a bare `p{line-height:var(--global--line-height-
   body)}` matching the <p> directly, so the block's 1.75 never reached it.
   `inherit` re-points the paragraph at the rule above rather than restating
   the number in two places. */
.elementor .elementor-element.elementor-widget.bnly-post__body p{
  line-height:inherit;
}

/* THE LINK FIX, and it is the most important rule in this file.

   Today: #0092D5 on white = 3.46 with text-decoration:none, from the WPCode
   snippet. That fails AA for 18px text AND makes colour the only signal that
   a link exists, which fails WCAG 1.4.1 independently. Colour law 2 says
   brand blue never carries small text. Navy on white is 9.86, and the
   underline is not decoration — it is the non-colour affordance.

   `font-weight:inherit` is not tidiness: the snippet bolds every inline link
   to 600, so without it the fix would ship a page of bold navy sentences.

   Two other rules would beat a bare `.bnly-post__body a`: Elementor's
   `.elementor a{text-decoration:none}` at (0,0,1,1) is an exact TIE, won on
   the front end only by load order — and the editor reverses that, so the
   underline would vanish exactly where an editor would look for it. */
.elementor .elementor-element.elementor-widget.bnly-post__body a{
  color:var( --e-global-color-secondary );
  font-weight:inherit;
  text-decoration:underline;
  text-decoration-thickness:1px;
  text-underline-offset:3px;
  text-decoration-color:rgba(21,68,120,.4);
}
/* text-decoration-style is restated because the parent theme's bare
   `a:hover{text-decoration-style:dotted}` matches here too. */
.elementor .elementor-element.elementor-widget.bnly-post__body a:hover{
  color:var( --e-global-color-ink );      /* 15.66 on white */
  text-decoration-color:currentColor;
  text-decoration-style:solid;
}
.elementor .elementor-element.elementor-widget.bnly-post__body a:focus-visible{
  outline:2px solid var( --e-global-color-secondary );
  outline-offset:2px;
  border-radius:2px;
  text-decoration:none;
}

.elementor .elementor-element.elementor-widget.bnly-post__body ul,
.elementor .elementor-element.elementor-widget.bnly-post__body ol{
  padding-left:1.35em;
}
.elementor .elementor-element.elementor-widget.bnly-post__body li{
  margin-block:.4em;
  line-height:inherit;
}
.elementor .elementor-element.elementor-widget.bnly-post__body li::marker{
  color:var( --e-global-color-edge );
}
.elementor .elementor-element.elementor-widget.bnly-post__body strong{
  color:var( --e-global-color-ink );
  font-weight:600;
}

/* Quote. Ink on mist is 14.35; the snippet's treatment is #42423E on
   #FAFAF8 with a brand-blue rule and a 1rem radius.

   `font:inherit` on the <p> is required, not tidiness: the parent theme sets
   font-family, font-size, font-style, font-weight AND line-height on
   `blockquote p` directly, so the block's own face would never have reached
   the text it wraps. */
.elementor .elementor-element.elementor-widget.bnly-post__body blockquote{
  margin-block:1.8em;
  padding:clamp(20px,2vw,26px) clamp(22px,2.2vw,30px);
  border:0;
  border-left:3px solid var( --e-global-color-secondary );
  border-radius:0 var(--benely-radius,8px) var(--benely-radius,8px) 0;
  background:var( --e-global-color-mist );
  font:400 clamp(18px,1.5vw,20px)/1.6 'Poppins',sans-serif;
  color:var( --e-global-color-ink );
}
.elementor .elementor-element.elementor-widget.bnly-post__body blockquote p{
  font:inherit;
  letter-spacing:normal;
}
.elementor .elementor-element.elementor-widget.bnly-post__body blockquote p:last-child{
  margin-bottom:0;
}
.elementor .elementor-element.elementor-widget.bnly-post__body blockquote::before{
  content:none;                           /* the snippet paints a quote glyph
     through a pseudo-element it inherits from the parent theme */
}

.elementor .elementor-element.elementor-widget.bnly-post__body figure{
  margin-block:1.9em;
}
.elementor .elementor-element.elementor-widget.bnly-post__body img{
  border:1px solid rgba(124,136,148,.22);
  border-radius:var(--benely-radius,8px);
  margin-block:0;
}
.elementor .elementor-element.elementor-widget.bnly-post__body figcaption{
  margin-top:10px;
  font:400 13px/1.5 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );     /* 8.86, not the muted token */
  text-align:left;                        /* the snippet and the parent theme
     both centre captions; centred under a left-aligned measure reads as a
     mistake */
}

/* TABLES. Half the posts have one, and at 390 two of them render 500px and
   437px inside a 312px column today.

   The table is NOT restacked into label/value rows: that needs a data-label
   on every cell, which cannot be generated for arbitrary editor HTML.

   167 of the 172 tables already sit in a Gutenberg .wp-block-table wrapper,
   so that wrapper becomes the scroll container. The other 5 are bare, and get
   `display:block` on the <table> itself, which makes the element its own
   scroll container without needing a wrapper injected into post content.

   The fade is painted with background-attachment:local so it only shows while
   there is something to scroll to. */
.bnly-post__body .wp-block-table{
  max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;
  margin-block:1.9em;
  background:
    linear-gradient(to right,#fff 30%,rgba(255,255,255,0)) left / 28px 100% no-repeat,
    linear-gradient(to left,#fff 30%,rgba(255,255,255,0)) right / 28px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%,rgba(16,29,43,.14),transparent) left / 12px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%,rgba(16,29,43,.14),transparent) right / 12px 100% no-repeat;
  background-attachment:local,local,scroll,scroll;
}
/* the bare case: the table becomes its own scroll box */
.bnly-post__body > table,
.bnly-post__body > figure:not(.wp-block-table) > table{
  display:block;max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;
}
.bnly-post__body table{
  border-collapse:collapse;width:100%;
  font:400 15px/1.5 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );
}
.bnly-post__body th{
  background:var( --e-global-color-mist );
  font-weight:700;color:var( --e-global-color-ink );text-align:left;
}
.bnly-post__body th,.bnly-post__body td{
  padding:11px 14px;border:1px solid rgba(124,136,148,.28);vertical-align:top;
}
/* The four rules above are the plan's, unchanged. These three carry the same
   declarations to where the rendered page actually puts the elements:

   - the bare-table case one level deeper, because in the Elementor build the
     prose is not a child of the element wearing .bnly-post__body — the
     widget nests a .elementor-widget-container — so `>` would miss all five
     bare tables live while matching perfectly in the prototype;
   - the table, th and td at the armoured tier, because the WPCode snippet
     styles `table`, `thead th` and `tbody td` at (0,0,3,1) and (0,0,3,2)
     with its own font-size, padding and #E8E8E3 rules, and would otherwise
     win every one of those properties. */
.elementor .elementor-element.elementor-widget.bnly-post__body > .elementor-widget-container > table,
.elementor .elementor-element.elementor-widget.bnly-post__body
  > .elementor-widget-container > figure:not(.wp-block-table) > table{
  display:block;max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;
}
.elementor .elementor-element.elementor-widget.bnly-post__body table{
  border-collapse:collapse;width:100%;
  font:400 15px/1.5 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );
}
.elementor .elementor-element.elementor-widget.bnly-post__body th,
.elementor .elementor-element.elementor-widget.bnly-post__body thead th{
  background:var( --e-global-color-mist );
  font-weight:700;color:var( --e-global-color-ink );text-align:left;
}
.elementor .elementor-element.elementor-widget.bnly-post__body th,
.elementor .elementor-element.elementor-widget.bnly-post__body td,
.elementor .elementor-element.elementor-widget.bnly-post__body thead th,
.elementor .elementor-element.elementor-widget.bnly-post__body tbody td{
  padding:11px 14px;border:1px solid rgba(124,136,148,.28);vertical-align:top;
}

/* Embeds. 35% of posts carry an iframe. */
.elementor .elementor-element.elementor-widget.bnly-post__body iframe{
  max-width:100%;
  aspect-ratio:16/9;
  height:auto;
  width:100%;
  border:0;
  border-radius:var(--benely-radius,8px);
}

/* --- the index ------------------------------------------------------------
   Built from the post's own h2s by [benely_post_index], and PRESENT only
   when there are at least three: 89 of 347 posts have no h2 at all and 11
   have one or two, where an index is noise. That leaves 247 posts (71%)
   showing one. The shortcode returns "" below the threshold — an empty box
   reads as a broken component, so there must be no box at all.

   It sticks, and `top` clears the 120px fixed header. */
.bnly-post__index{
  position:sticky;
  top:calc(var(--benely-headerh,120px) + 24px);
  /* 31 h2s is the worst case measured, so the list is allowed to scroll
     inside itself rather than run past the viewport */
  max-height:calc(100vh - var(--benely-headerh,120px) - 48px);
  overflow-y:auto;
}

/* A sticky box travels only inside its own parent, and here the <nav> is NOT
   the grid item: the shortcode renders it two wrappers deep inside an
   Elementor column, and the column, its widget wrap, the widget and the
   widget container all shrink-wrap to the nav's own height. That leaves ZERO
   room, and a sticky box with zero room is inert — indistinguishable from a
   page that simply scrolls, which is why tools/post.py scrolls to a computed
   target and reads elementFromPoint rather than trusting coordinates.
   Stretching the chain to the grid area's full height, and leaving only the
   nav content-height, restores the travel.

   Parent-relative by nature ("the column that CONTAINS the index"), so no
   control can express it — the documented `:has()` exception. */
.bnly-post__grid > .elementor-container > .elementor-column:has(.bnly-post__index),
.bnly-post__grid > .e-con-inner > .e-con:has(.bnly-post__index){
  align-self:stretch;
}
.bnly-post__grid .elementor-column:has(.bnly-post__index) > .elementor-widget-wrap,
.bnly-post__grid .elementor-widget:has(.bnly-post__index),
.bnly-post__grid .elementor-widget:has(.bnly-post__index) > .elementor-widget-container,
/* .elementor-shortcode is the nav's DIRECT parent and it is easy to miss:
   the Shortcode widget renders widget > widget-container > .elementor-shortcode
   > the nav, which is THREE wrappers, not two. Measured on
   /employee-benefits-compliance-checklist/ with the four selectors above in
   place: the column, its wrap, the widget and the container all stretched to
   26627px and .elementor-shortcode still measured 832px - the nav's own
   height. room = parent - box = 0, so the sticky had zero travel and was
   inert, and tools/post.py reported the whole `sticky` group as "not
   exercised" rather than failing. A not-exercised report is not a pass;
   check the ancestor chain by measuring it, not by reading the markup. */
.bnly-post__grid .elementor-widget:has(.bnly-post__index) .elementor-shortcode{
  height:100%;
}

.bnly-post__index h2{
  margin:0 0 14px;
  font:700 11.5px/1.2 'Poppins',system-ui,sans-serif;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var( --e-global-color-secondary );
}
.bnly-post__index ol{ list-style:none;margin:0;padding:0 }
.bnly-post__index li{ border-top:1px solid rgba(124,136,148,.22);margin:0 }
.bnly-post__index li:last-child{ border-bottom:1px solid rgba(124,136,148,.22) }
/* the second selector clears the snippet's `.elementor-location-single a`
   at (0,0,1,1), which paints every link in the template brand blue at 600 */
.bnly-post__index a,
.elementor .bnly-post__index a{
  display:block;
  padding:11px 2px;
  font:400 14px/1.45 'Poppins',system-ui,sans-serif;
  color:var( --e-global-color-deep );     /* 8.86 on white */
  text-decoration:none;
}
.bnly-post__index a:hover,
.elementor .bnly-post__index a:hover{
  color:var( --e-global-color-secondary );
  text-decoration:none;
}
.bnly-post__index a:focus-visible,
.elementor .bnly-post__index a:focus-visible{
  outline:2px solid var( --e-global-color-secondary );
  outline-offset:-2px;                    /* inset, because the list is flush
     to the column edge and an outset ring would clip */
  border-radius:2px;
}

/* ==========================================================================
   RELATED — the existing 3-post widget, wearing the /blog card

   The CARDS are not styled here. They are the Pro Posts widget and its Style
   panel owns the frame, radius, padding, colour and type; those go in the
   data, with SLIDER shapes for the border and radius — a dimensions value on
   a slider control emits nothing and reads back correct, which is how a 0px
   border and Elementor's own 3px default radius shipped on /blog and the
   archive. Those controls emit at (0,0,4,0) and clear the WPCode snippet's
   card rules at (0,0,3,0) on their own.
   ========================================================================== */
/* Only the ground. The heading and the cards are two sibling sections in
   template 3149 and both wear this class, so the band's rhythm must be paid
   once across the pair - b12eae4 carries the top, 291c0c6 the bottom, both in
   their own Padding control. The `+ .bnly-related` and
   `:has(.bnly-related__head)` corrections that used to live here are gone with
   it: see ONE SYSTEM above. Splitting the property lets the control, at
   (0,0,3,0) in every media query, delete the stylesheet's half without a
   warning - four times in this project so far. */
.bnly-related{
  background:var( --e-global-color-mist );
}

.bnly-related .bnly-related__head,
.bnly-related .bnly-related__head .elementor-heading-title{
  margin:0 0 clamp(20px,2.4vw,28px);
  font:500 clamp(24px,2.4vw,32px)/1.2 'Poppins',sans-serif;
  color:var( --e-global-color-ink );
}

/* ==========================================================================
   BREAKPOINTS

   Measure every breakpoint the CSS names. A desktop screenshot proves
   nothing about the one below it — the services cards rendered 36px tall
   under 860px in the prototype as well as in WordPress, so porting
   faithfully carried the bug across.
   ========================================================================== */
@media (max-width:1024px){
  /* `1fr` for the same reason as the base rule: the pair fills the rail and
     ends level with the cover. */
  .bnly-post__grid > .elementor-container,
  .bnly-post__grid > .e-con-inner,
  .bnly-post__grid:not(:has(> .elementor-container)):not(:has(> .e-con-inner)){
    grid-template-columns:minmax(0,1fr) 240px;
  }
}

@media (max-width:860px){
  /* The index moves ABOVE the prose and stops sticking. Below the prose it
     would be an index nobody reaches; beside it there is no room. */
  .bnly-post__grid > .elementor-container,
  .bnly-post__grid > .e-con-inner,
  .bnly-post__grid:not(:has(> .elementor-container)):not(:has(> .e-con-inner)){
    grid-template-columns:minmax(0,1fr);
  }
  .bnly-post__index{
    position:static;
    max-height:none;
    overflow:visible;
    order:-1;
    margin-bottom:clamp(24px,3vw,36px);
    padding:18px 20px;
    background:var( --e-global-color-mist );
    border-radius:var(--benely-radius,8px);
  }
  /* `order` is a property of the GRID ITEM, and the nav is not one — the
     column that contains it is. Ordering the nav alone would move nothing at
     all on the rendered page while working perfectly in the prototype. */
  .bnly-post__grid > .elementor-container > .elementor-column:has(.bnly-post__index),
  .bnly-post__grid > .e-con-inner > .e-con:has(.bnly-post__index){
    order:-1;
    align-self:auto;
  }
  .bnly-post__grid .elementor-column:has(.bnly-post__index) > .elementor-widget-wrap,
  .bnly-post__grid .elementor-widget:has(.bnly-post__index),
  .bnly-post__grid .elementor-widget:has(.bnly-post__index) > .elementor-widget-container,
  .bnly-post__grid .elementor-widget:has(.bnly-post__index) .elementor-shortcode{
    height:auto;
  }
  /* the column is already narrower than the cap here, and leaving it on
     would do nothing but risk looking like an indent */
  .bnly-post__body,
  .elementor .elementor-element.elementor-widget.bnly-post__body{ max-width:none }
}
