/* GENERATED FILE. Do not edit here.
   Source: _kim/mockups/src/epp-sheet.css
   Regenerate: python _kim/mockups/port-to-plugin.py
   Editing this copy directly will be overwritten by the next port and
   will silently desync the mockup from production. */
/* ENTTEC product pages, epp-* component layer for Kadence-free product bodies.
   Loaded ONLY on products registered in enttec_product_pages_html_mode_slugs(),
   so Kadence product pages are untouched.
   All font sizes are px, never rem: the theme sets html{font-size:10px}.

   TASK 3 LIFTS THIS FILE VERBATIM into assets/product-pages.css.

   ---------------------------------------------------------------------------
   DESIGN DIRECTION (Kim, 2026-07-30). READ THIS BEFORE CHANGING ANY NUMBER.
   ---------------------------------------------------------------------------
   The first four rounds of this page were built by MEASURING the live Pixelator
   Mini Mk2 page and matching its numbers: band colours, column widths, type
   sizes, table row counts. That produced something structurally faithful and
   visually dead, because Pixelator Mini Mk2 is a Kadence page and cloning it
   inherited a mediocre layout without Kadence's excuse for it.

   The reference is now the PROJECT PAGES design language
   (wp-content/mu-plugins/enttec-project-pages/assets/project-pages.css), which
   is the current, Kadence-free, editorial system this site is moving to. This
   sheet is deliberately its sibling: same palette, same type scale, same
   sub-nav, same editorial product rows, same closing CTA. Where the two need
   the same thing they say it the same way, so a reader moving between a project
   page and a product page reads one site rather than two.

   DESIGN CONTRACT (do not drift):
    - Palette is STRICT SCM-SOP-144 brand only. No blue tints/opacities, ever.
        blue #0ea5df . black #000 . white #fff . dark grey #333
        mid grey #4c4c4c . light grey #d9d9d9
    - Type mirrors the project pages:
        h1 Montserrat 67/500 . h2 Montserrat 38/400 . h3 Montserrat 24/500
        body Source Sans 21/400 . body colour #4c4c4c
      h2 at 38px is Kim's 2026-07-30 decision, unchanged. What DID change is the
      weight: 400, not 600. That single value is most of the difference between
      "product datasheet" and "editorial page", and it is why the headings now
      read as the project pages' do.
    - Band rhythm alternates and never repeats: dark, white, grey, white, dark,
      white, grey, dark. Five stacked dark bands was the single biggest thing
      wrong with the previous draft.
    - NO stat cards, NO accordion-as-content, NO "Features" heading. All three
      were cut by Kim on 2026-07-30. The accordion survives only where it is a
      genuine disclosure control (it currently survives nowhere on this page).
   --------------------------------------------------------------------------- */

/* ======================= VARIABLES (the only literals) =======================
   Brand palette pinned locally, exactly as project-pages.css does it, so
   nothing on the page can reach for an off-brand design-system tint. */
.epp {
  --epp-blue:   #0ea5df;
  --epp-black:  #000000;
  --epp-dark:   #333333;   /* brand "dark grey" - the dark bands */
  --epp-mid:    #4c4c4c;   /* brand "mid grey"  - body text on white */
  --epp-light:  #d9d9d9;   /* brand "light grey" - hairlines on light bands */
  --epp-subnav: #2a2a2a;   /* subtly darker than the #333 band below it */
  --epp-grey:   #f2f2f2;   /* the light grey band */

  /* Content measure. 1500px matches the project pages (the design system's own
     default is 1760, which is too wide for reading). */
  --eds-container: 1500px;

  /* Split sections: heading alone in a narrow left column, content in a wide
     right column. 24/76 is the live product page's measured 309/966, used by
     specifications and resources. */
  --epp-split-head: 24fr;
  --epp-split-body: 76fr;

  /* Sub-nav height drives every section's scroll-margin-top, so an anchor jump
     never lands under the bar. 18px padding on a 16px/1.4 line = 58px. */
  --epp-subnav-h: 58px;

  /* Related-products carousel: how many product cards are visible at once, and
     the gap between them. The slide width is computed from these two, so a
     future page with a different card count changes one number here rather than
     a hand-worked percentage in three media queries. */
  --epp-carousel-visible: 3;
  --epp-carousel-gap: var(--eds-gap-card);

  /* On-dark ink. Values match eds-section--tone-dark so the two layers cannot
     disagree about what "secondary text on charcoal" means. */
  --epp-dark-ink:  #ffffff;
  --epp-dark-body: rgba(255,255,255,0.72);
  --epp-dark-soft: rgba(255,255,255,0.55);
  --epp-dark-rule: rgba(255,255,255,0.16);
  --epp-dark-panel:rgba(255,255,255,0.05);
}

/* ============================== BANDS ==============================
   Each band sets its ink and rule colours ONCE. Components below read only
   those variables, never a band-specific colour, which is why the same figure,
   table, capability tile and gallery markup works on charcoal, on white and on
   grey without a single "if we are on the light band" override. */
.epp-section--dark {
  background: var(--epp-dark);
  --epp-ink:      var(--epp-dark-ink);
  --epp-body-ink: var(--epp-dark-body);
  --epp-soft-ink: var(--epp-dark-soft);
  --epp-rule:     var(--epp-dark-rule);
  --epp-panel:    var(--epp-dark-panel);
}
.epp-section--light {
  background: #ffffff;
  --epp-ink:      var(--epp-black);
  --epp-body-ink: var(--epp-mid);
  --epp-soft-ink: var(--epp-mid);
  --epp-rule:     var(--epp-light);
  --epp-panel:    var(--epp-grey);
}
.epp-section--grey {
  background: var(--epp-grey);
  --epp-ink:      var(--epp-black);
  --epp-body-ink: var(--epp-mid);
  --epp-soft-ink: var(--epp-mid);
  --epp-rule:     var(--epp-light);
  --epp-panel:    #ffffff;
}

/* ======================= THE THEME'S BANNER AND SUMMARY =======================
   The banner image, h1, breadcrumb, short description, SKU and "Contact an
   Expert" button are printed by the THEME, above and outside our article, so
   they are the one part of this page we restyle from the outside rather than
   author. They are scoped to .product-details-top, which exists only on a
   single-product template, and this sheet only loads on html-mode products.

   Left alone, the theme sets the h1 at its own size and weight and the page
   opens on a different type system to the one the body uses two hundred pixels
   further down. Matching the project-page h1 here is what makes the banner and
   the article read as one page. */
.product-details-top h1 {
  font-family: var(--enttec-font-display); font-weight: 500;
  font-size: clamp(40px, 6vw, 67px); line-height: 1.1; letter-spacing: normal;
  text-transform: none; margin: 0 0 18px;
}
.product-details-top .woocommerce-product-details__short-description p {
  font-family: var(--enttec-font-body); font-size: clamp(17px, 1.4vw, 21px);
  /* 60ch, not 46ch. This paragraph is the widest thing in .product-info-area and
     a flex item that shrinks to its content, so its max-width is what sets the
     whole summary column's width. At 46ch (412px) the ELM HUB opener wrapped to
     five lines; 58ch is where it drops to four, and 60ch (537px) keeps a line's
     worth of slack for copy edits and font fallbacks. The theme caps the column
     at 570px, so this stays inside the theme's own limit and nothing overflows
     the band. Measured at 1440; narrower viewports are bound by the column, not
     by this value. */
  font-weight: 400; line-height: 1.5; max-width: 60ch; margin: 0 0 26px;
}
/* The banner render is composed on brand dark grey (see build-elm-hub-banner.py),
   so the band it sits in must be that same grey or a seam shows at whatever
   height the theme's object-fit:cover crops to. */
.product-details-top .banner-image { background: var(--epp-dark); }

/* BANNER HEIGHT. The theme gives the banner 100vh, and the summary block
   (breadcrumb, h1, short description, button) ends at about 70% of it, so on a
   900px window there are 264px of empty dark band before the sub-nav, and on a
   taller screen there is more. Capping at 700px removes the dead space without
   crowding the summary.

   TWO THINGS MAKE THIS FIDDLY, both worth knowing before touching it:

   1. The height lives on .product-details-top, NOT on the image. The image is
      position:absolute with height:100%, and .banner-image itself computes to
      0px, so setting a height on either of those two does nothing useful.

   2. The theme's own rule is
          body.single-product.newLookEnabled .product-details-top img
      at (0,3,1), which OUTRANKS a plain .product-details-top .banner-image img
      at (0,2,1). It also sets object-position: center top. Anchoring the top
      means a shorter box crops off the BOTTOM of the source, which would cut the
      base of the unit off (it occupies y 197-740 of the 900px composition).
      Centred, the crop takes an equal slice from each end and the unit is
      untouched.

   3. The cap is MAX-HEIGHT, not height, and that is the point rather than a
      workaround. The theme owns `height: 100vh` at (0,3,1) and there is no
      beating that from (0,2,1) without either chaining .newLookEnabled (which
      rots the moment the theme renames it) or repeating a class purely for
      specificity. max-height is a property the theme never sets, so it applies
      uncontested, AND it says what we actually mean: keep the theme's responsive
      100vh behaviour, just stop it getting taller than it needs to be. The
      theme's inline min-height (about 542px, set from its own script) is left
      alone and does not conflict, being well under the cap.

   Both rules are scoped with body.single-product, a WooCommerce CORE body class
   rather than a theme invention, so the scope does not rot either.

   Desktop only: below 1025px the summary block reflows much taller and needs all
   the height the theme gives it. */
@media (min-width: 1025px) {
  body.single-product .product-details-top { max-height: 700px; }
  body.single-product .product-details-top .banner-image img { object-position: 50% 50%; }
}

/* ============================== BASE ==============================
   A COMPLETE, SELF-SUFFICIENT baseline for every element this body uses.

   Why it is spelled out in full instead of as a short list of overrides: ELM HUB
   is the first product body without Kadence, and the intent is to remove Kadence
   and the legacy theme CSS entirely. So nothing here may be written as a delta
   from what those sheets happen to do today. Every declaration states what we
   want in absolute terms, and the test applied to each was:

       if Kadence and the theme CSS vanished tomorrow, would this line still be
       correct, and would it still be needed?

   No !important is used to win against the theme or Kadence, anywhere. The only
   !important declarations in this sheet are in the reduced-motion block at the
   very bottom, overriding OUR OWN transitions, which is the one case where an
   accessibility preference has to beat every component rule unconditionally.

   ---------------------------------------------------------------------------
   WHY EVERY ELEMENT SELECTOR BELOW IS WRAPPED IN :where(.epp)

   This is the load-bearing detail of the whole block, and it is easy to get
   backwards. `:where()` matches normally but contributes ZERO to specificity, so
   `:where(.epp) li` scores as a bare `li` (0,0,1) while still only ever matching
   inside our article.

   Without it, `.epp li` would score (0,1,1), HIGHER than a single-class
   component rule like `.epp-cap` (0,1,0). The baseline would then beat our own
   components no matter what order they appear in. The first draft did exactly
   that, and silently stripped the padding off every tile.

   So the cascade we want is, weakest to strongest:

       legacy bare-element rules   (0,0,1) earlier in the document
       this baseline               (0,0,1) but LAST in <head>, so it wins the tie
       our epp-* components        (0,1,0) and above, so they win outright

   Never @layer here: unlayered styles beat layered ones outright, so putting
   this in a layer would hand every property straight back to the theme.
   --------------------------------------------------------------------------- */

.epp, .epp *, .epp *::before, .epp *::after { box-sizing: border-box; }

.epp { font-family: var(--enttec-font-body); color: var(--epp-mid);
  font-size: clamp(17px, 1.4vw, 21px); line-height: 1.5; font-weight: 400;
  font-style: normal; text-align: start; -webkit-font-smoothing: antialiased;
  width: 100%; margin-inline: 0; }

/* Bands run edge to edge. The theme insets `main section`; neutralise that
   horizontal inset and let .epp fill its wrapper rather than using a 100vw
   breakout (which mixes vw, counting the scrollbar, with 50%, which does not,
   and shifts the whole page a scrollbar-width left). */
.epp .epp-section { width: 100%; max-width: none; margin-inline: 0; padding-inline: 0; }

/* Headings carry no bottom margin except h2's, which the project pages set to
   30px. Every other gap on this page comes from a grid gap or an explicit
   spacing rule, so heading margins would double up and margin collapse would
   make the result depend on sibling order. */
:where(.epp) h2, :where(.epp) h3, :where(.epp) h4 {
  font-family: var(--enttec-font-display); color: inherit;
  margin: 0; padding: 0; text-transform: none; }
:where(.epp) h2 { font-weight: 400; line-height: 1.2; letter-spacing: 0; margin: 0 0 30px; }
:where(.epp) h3 { font-weight: 500; line-height: 1.25; letter-spacing: 0; }
:where(.epp) h4 { font-weight: 600; line-height: 1.3; letter-spacing: 0; font-size: 18px; }

/* Heading SIZES are split out at (0,1,1), unlike the appearance rules above.
   A mobile media query in the minified theme bundle (cross-origin, so its
   selector cannot even be read from the page) sets h2 to 1.5em and h3 to 1.3em,
   and it outranks a bare element selector. Left in the baseline, our 38px
   headings silently collapsed to 24.75px below the theme's breakpoint while
   looking perfectly correct at 1440. That is why the win-everywhere check has
   to run at every breakpoint, not just at desktop. */
.epp h2 { font-size: clamp(28px, 4vw, 38px); }
.epp h3 { font-size: clamp(20px, 2vw, 24px); }

:where(.epp) p { margin: 0 0 1.2em; padding: 0;
  font-size: inherit; line-height: inherit; color: inherit; }
:where(.epp) p:last-child { margin-bottom: 0; }

/* Semibold, not bold. 600 against a 400 body is a clear step up without the
   shouty look 700 gives on Source Sans, and it matches the weight the design
   system uses for card titles and buttons. Used to pick out the load-bearing
   phrase in a paragraph (Kim 2026-07-31). Inherits colour deliberately: a
   highlight that also changed colour would compete with the links. */
:where(.epp) strong, :where(.epp) b { font-weight: 600; color: inherit; }

:where(.epp) ul, :where(.epp) ol { margin: 0; padding: 0; list-style: none; }
:where(.epp) li { margin: 0; padding: 0; }

:where(.epp) a { color: var(--epp-blue); text-decoration: none;
  background-color: transparent; transition: color .15s var(--eds-ease); }
/* Underline ONLY inside body copy, never on cards, buttons or nav (those are
   links too and must stay clean), and only on hover. Mirrors the project pages. */
:where(.epp) p a { text-underline-offset: 3px; text-decoration-thickness: 1.5px; }
:where(.epp) p a:hover { text-decoration: underline; }

:where(.epp) img { display: block; max-width: 100%; height: auto; border: 0; }
:where(.epp) figure, :where(.epp) figcaption { margin: 0; padding: 0; }

:where(.epp) button { font-family: var(--enttec-font-body); font-size: inherit;
  font-weight: inherit; line-height: inherit; color: inherit;
  letter-spacing: inherit; text-transform: none; text-align: center;
  margin: 0; background: none; border: 0; border-radius: 0; cursor: pointer; }
/* appearance is split out at (0,1,0) because a button rule in the minified theme
   bundle outranks a bare element selector and was painting platform button
   chrome over our gallery thumbnails. Raising specificity is the fix rather than
   an !important, and it stays correct once that bundle is gone. */
.epp button { -webkit-appearance: none; appearance: none; }

:where(.epp) table { width: 100%; margin: 0; border-collapse: collapse;
  border-spacing: 0; font-size: inherit; color: inherit; }
:where(.epp) th, :where(.epp) td { text-align: left; vertical-align: top; background: none; }

.epp-section { color: var(--epp-ink);
  /* Anchor jumps must clear the sticky bar. scroll-margin on the TARGET is the
     scoped way to do this: scroll-padding would have to sit on html. The bar now
     pins at --enttec-chrome-h rather than 0, so the same offset has to be in
     here or an anchor jump lands with its heading tucked under the bar. */
  scroll-margin-top: calc(var(--epp-subnav-h) + var(--enttec-chrome-h, 0px) + 12px); }
.epp-section--dark h2, .epp-section--dark p { color: var(--epp-ink); }

/* Buttons keep their design-system label colour inside .epp: the broad
   `:where(.epp) a` colour rule above must not repaint button text. */
.epp a.eds-btn--primary, .epp a.eds-btn--ghost { color: #fff; }
.epp a.eds-btn--outline { color: var(--epp-black); }
.epp .epp-section--dark a.eds-btn--outline { color: #fff; }
/* An eds-btn that is an ANCHOR loses the design system's transform/background
   transition to the `a { transition: color }` rule above, because that rule
   REPLACES the declaration rather than merging with it. Restore the full
   transition on the anchor form or every link-button on the page snaps instead
   of lifting. Same trap project-pages.css documents at its line 351. */
.epp a.eds-btn { transition: transform var(--eds-dur) var(--eds-ease),
                             background var(--eds-dur) var(--eds-ease),
                             border-color var(--eds-dur) var(--eds-ease),
                             color var(--eds-dur) var(--eds-ease); }

/* Eyebrow: solid brand blue on every band. The design system tints it #5ec9ef
   on dark, which is an off-brand blue variant we are not allowed to use. */
.epp .eds-eyebrow { color: var(--epp-blue); font-size: 13px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; line-height: 1.3;
  margin: 0 0 var(--eds-space-2xs); }
.epp .epp-section--dark .eds-eyebrow { color: var(--epp-blue); }
.epp .eds-section-head p { color: var(--epp-body-ink); margin: .5em 0 0; max-width: 68ch; }

/* ============================== SUB-NAV ==============================
   Clone of the project pages' .proj-subnav: dark bar, centred links, solid-blue
   underline growing from the centre on hover and for the current section.

   The theme's own [navigation-shortcode] is not used: it parses post_content as
   blocks and knows only nine hardcoded anchor names, so it renders empty on a
   hand-authored body.

   position:sticky rather than the project pages' JS pinning, but that needs the
   rule below to work at all. CORRECTED 2026-08-03: the earlier note here claimed
   overflow:hidden was set on the project template only and not on the product
   one. It is not template-specific. `#container` is the sitewide wrapper and
   main-style.css gives it overflow:hidden, so sticky was silently dead on this
   page too: the bar scrolled away instead of pinning.

   overflow:CLIP fixes it at the root instead of working around it. clip crops
   exactly as hidden does, but it does NOT create a scroll container, and a
   scroll container is the thing that captures a descendant's position:sticky.
   Scoped to single-product because this sheet only loads on html-mode product
   pages, so no other template's clipping is touched. In a browser too old for
   clip the declaration is simply dropped and we are back to today's behaviour,
   which is a degradation, not a break.

   The bar pins below the site chrome, not at 0: --enttec-chrome-h is published
   by header-stack.js (fixed banners + navbar) and is the same number the theme's
   own subnav offsets read, so the navbar can never land on top of this bar. */
body.single-product #container { overflow: clip; }
.epp-subnav { position: sticky; top: var(--enttec-chrome-h, 0px); z-index: 25;
  background: var(--epp-subnav); }
.epp-subnav ul { list-style: none; margin: 0; padding: 0; display: flex;
  justify-content: center; gap: 0;
  /* Horizontal scroll INSIDE the bar, so a six-item nav never gives the whole
     page a horizontal scrollbar at 390px. */
  overflow-x: auto; scrollbar-width: none; }
.epp-subnav ul::-webkit-scrollbar { display: none; }
.epp-subnav li { display: flex; margin: 0; }
.epp-subnav a { color: var(--epp-dark-body); font-family: var(--enttec-font-body);
  font-size: 16px; font-weight: 400; white-space: nowrap; padding: 18px 22px;
  display: block; text-decoration: none; position: relative;
  transition: color var(--eds-dur-fast) var(--eds-ease); }
/* Absolutely positioned so the underline never affects the bar's height or
   triggers the ul's overflow-x. */
.epp-subnav a::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--epp-blue); transform: scaleX(0);
  transform-origin: center; transition: transform var(--eds-dur) var(--eds-ease); }
.epp-subnav a:hover,
.epp-subnav a[aria-current="true"] { color: #fff; }
.epp-subnav a:hover::after,
.epp-subnav a[aria-current="true"]::after { transform: scaleX(1); }
.epp-subnav a:focus-visible { outline: 2px solid var(--epp-blue); outline-offset: -2px; }
@media (max-width: 700px) {
  .epp-subnav ul { justify-content: flex-start; }
  .epp-subnav a { padding: 14px 16px; font-size: 15px; }
  /* The bar is shorter here, so the scroll offset must shrink with it or every
     anchor jump lands 8px too low. */
  .epp { --epp-subnav-h: 48px; }
}

/* ============================== SPLIT SECTIONS ==============================
   Heading alone in a narrow left column, content in a wide right column, at the
   live product page's measured 309/966. Used by specifications and resources.
   RESTORED 2026-07-30: an intermediate draft moved these to the project pages'
   0.85/1.15, which is right for a project page (where the left column carries a
   sentence-long statement) and wrong here (where it carries one word). */
.epp-split { display: grid;
  grid-template-columns: minmax(0, var(--epp-split-head)) minmax(0, var(--epp-split-body));
  gap: var(--eds-space-lg); align-items: start; }
@media (max-width: 900px) { .epp-split { grid-template-columns: 1fr; gap: var(--eds-space-md); } }
.epp-split__body { display: flex; flex-direction: column; gap: var(--eds-space-lg); }

/* ============================== OVERVIEW ==============================
   Two equal columns: marketing statement plus lead on the left, the rest of the
   prose on the right. This is the live product page's own overview layout, and
   it is also exactly what Kim described on 2026-07-30: "there should be a
   marketing subtitle not the word Overview, then on the right column, that's
   where the rest of the text should be."

   --eds-space-md, not -lg: the real page's two columns sum to 1305 of the 1345
   available, so its gutter is 40px. -lg (72px here) would miss the measurement. */
.epp-overview { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--eds-space-md); align-items: start; }
@media (max-width: 900px) { .epp-overview { grid-template-columns: 1fr; } }
.epp-overview__head h2 { margin-bottom: 0; }
/* ONE size for the whole right column, and no size-up lead paragraph. Kim
   2026-07-31: "we need to make the right section the same font size". The
   emphasis now comes from semibold runs and links inside the prose, which is
   the better instrument anyway: a bigger first paragraph tells you where to
   start reading, while semibold tells you what actually matters, and only the
   second of those survives a reader who skims. */
.epp-overview__body p { color: var(--epp-body-ink); }
/* Links inside the overview sit on charcoal, where the brand blue on #333 is a
   thin 3.4:1. Underlining them permanently (rather than on hover, as body links
   elsewhere do) is what keeps them findable without inventing a lighter blue,
   which the palette does not allow. */
.epp-overview__body a { text-decoration: underline; text-underline-offset: 3px;
  text-decoration-thickness: 1.5px; text-decoration-color: rgba(14,165,223,.55);
  transition: text-decoration-color var(--eds-dur-fast) var(--eds-ease); }
.epp-overview__body a:hover { text-decoration-color: var(--epp-blue); }

/* ============================== HARDWARE ==============================
   Gallery left, accordion right, at the live product page's measured 598/677
   (47:53 of the 1273px left after the gap). minmax(0,...) on both tracks so
   neither child can force the grid wider than its container.
   RESTORED 2026-07-30 along with the accordion: an intermediate draft replaced
   this with prose-plus-gallery and a tile grid, which reads as a project page. */
.epp-features { display: grid; grid-template-columns: minmax(0, 47fr) minmax(0, 53fr);
  gap: var(--eds-space-lg); align-items: start; }
@media (max-width: 980px) { .epp-features { grid-template-columns: 1fr; gap: var(--eds-space-md); } }

/* Accordion: the design system's eds-accordion, re-inked from the band
   variables so the same markup works on charcoal, white and grey. */
.epp-accordion details { border-color: var(--epp-rule); }
.epp-accordion summary { font-size: 18px; color: var(--epp-ink);
  transition: color var(--eds-dur-fast) var(--eds-ease); }
.epp-accordion summary:hover { color: var(--epp-blue); }
.epp-accordion summary::after { border-right-color: var(--epp-soft-ink);
  border-bottom-color: var(--epp-soft-ink); }
.epp-accordion summary:hover::after { border-right-color: var(--epp-blue);
  border-bottom-color: var(--epp-blue); }
.epp-accordion .eds-accordion__body { color: var(--epp-body-ink); font-size: 16.5px; }
/* Open/close motion. Native <details> cannot animate its own height, so the
   panel fades and lifts in instead. Gated on .epp-js so a page without script
   never holds content at opacity 0, and killed outright under reduced motion. */
.epp-js .epp-accordion details[open] .epp-accordion__panel {
  animation: epp-panel-in var(--eds-dur) var(--eds-ease) both; }
@keyframes epp-panel-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ======================= CAPABILITY TILES =======================
   Replaces BOTH things Kim cut on 2026-07-30: the four stat cards ("32,768
   universes per protocol") and the eight-pane feature accordion.

   Why one component for both: the stat cards and the accordion panes were the
   same content type (a short labelled fact) wearing two different costumes, one
   of which hid seven of its eight items behind a click for no reason. There is
   no disclosure problem to solve here, the content is short, so it is simply
   shown. The blue rule is the only ornament, borrowed from nothing: it is the
   same 3px blue bar the design system uses to mark a section start, reused at
   tile scale so the grid reads as a set. */
.epp-caps { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--eds-space-lg); }
@media (max-width: 900px) { .epp-caps { grid-template-columns: 1fr; gap: var(--eds-space-md); } }
.epp-cap { margin: 0; }
.epp-cap::before { content: ""; display: block; width: 44px; height: 3px;
  background: var(--epp-blue); border-radius: 2px; margin-bottom: var(--eds-space-xs); }
.epp-cap h3 { margin: 0 0 8px; }
/* Tile body copy is a step DOWN from the 21px page body: at 21px, six tiles
   three across become six columns of running text competing with each other,
   and the h3s stop reading as headings. */
.epp p.epp-cap__text { font-size: 16.5px; line-height: 1.55; color: var(--epp-body-ink); margin: 0; }

/* ==================== SOFTWARE LEAD BLOCK ====================
   Direction A from _kim/mockups/software-section-directions.html, chosen by Kim
   2026-07-31. One capability at full size with its image beside it, then the
   supporting four as a quiet row underneath (.epp-caps--4).

   Left-aligned, unlike the centred two-up it replaces. Centring was most of why
   that version was hard to look at: with no common left edge the eye has to find
   the start of every block on its own, and the section reads slower than the
   ones above and below it. */
.epp-swlead { display: grid; grid-template-columns: 1fr 1.15fr;
  gap: clamp(32px, 5vw, 72px); align-items: center;
  margin-bottom: clamp(44px, 5vw, 72px);
  padding-bottom: clamp(36px, 4vw, 56px); border-bottom: 1px solid var(--epp-rule); }
.epp-swlead__copy h3 { font-size: clamp(24px, 2.6vw, 30px); font-weight: 500;
  margin: 0 0 18px; text-wrap: balance; }
.epp-swlead__copy p { color: var(--epp-body-ink); font-size: 19px; max-width: 52ch; }
/* The lead image is a photograph with its own dark background and a baked-in
   award badge, so it takes a plain rounded frame and no panel fill: a tinted
   panel behind a full-bleed photo just shows as a rim. */
.epp-swlead__media { border-radius: var(--eds-radius); overflow: hidden;
  border: 1px solid var(--epp-rule); }
.epp-swlead__media img { display: block; width: 100%; height: auto; }
@media (max-width: 1000px) { .epp-swlead { grid-template-columns: 1fr; } }

/* Four across for the supporting row. Steps to two then one rather than
   squeezing four columns of two-word lines. */
.epp-caps--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--eds-space-md); }
@media (max-width: 1000px) { .epp-caps--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .epp-caps--4 { grid-template-columns: 1fr; } }
/* Supporting tiles are a step down from the lead: 17px against the lead's 19px,
   and the heading drops to 18px. They are there to be scanned, not read. */
.epp-caps--4 .epp-cap h3 { font-size: 18px; font-weight: 600; }
.epp .epp-caps--4 .epp-cap__text { font-size: 15.5px; }

/* ============================== GALLERY ==============================
   One main image with a thumbnail strip under it, NOT a carousel: with three
   real product images, a component that hides two of them is the wrong shape.
   Arrows stay for keyboard and touch.

   object-fit:contain, never cover: these are product renders and annotated
   panel callouts, and a crop would cut a connector or a callout label.

   The stage is WHITE because the renders are photographed on white. On a
   charcoal band an untreated one reads as a floating white rectangle with a
   hard edge; painting the holder white and padding it turns that edge into the
   edge of an intentional product panel. On the white hardware band it simply
   disappears, which is why this section is on white. */
.epp-gallery__stage { position: relative; border-radius: var(--eds-radius-lg);
  overflow: hidden; background: #ffffff; border: 1px solid var(--epp-rule);
  padding: var(--eds-space-xs); }
.epp-gallery__main { display: block; width: 100%; aspect-ratio: 16 / 10;
  object-fit: contain; }
.epp-gallery .eds-carousel__arrow:focus-visible { outline: 3px solid var(--epp-blue); outline-offset: 2px; }
/* The slide counter is the non-motion half of the gallery's feedback: with
   reduced motion on there is no crossfade, so this text and the active thumb
   are what tell you the control worked. Never hide it from sighted users. */
.epp-gallery__status { margin: var(--eds-space-2xs) 0 0; text-align: center;
  font-size: 13px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--epp-soft-ink); }
.epp-gallery__thumbs { list-style: none; margin: var(--eds-space-xs) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 10px; }
.epp-gallery__thumbs li { margin: 0; }
/* 96x60 clears the 44px tap-target floor on both axes. box-sizing is explicit
   so the padding cannot eat into that floor if the global border-box rule ever
   stops applying here. */
.epp-gallery__thumb { box-sizing: border-box; width: 96px; height: 60px;
  padding: 4px; display: block;
  border: 1px solid var(--epp-rule); border-radius: var(--eds-radius-sm);
  background: #ffffff; cursor: pointer; overflow: hidden;
  transition: border-color var(--eds-dur) var(--eds-ease), opacity var(--eds-dur) var(--eds-ease); }
.epp-gallery__thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.epp-gallery__thumb:not([aria-current="true"]) { opacity: .62; }
.epp-gallery__thumb:hover { opacity: 1; border-color: var(--epp-soft-ink); }
/* Active state is colour and a ring, never motion alone. */
.epp-gallery__thumb[aria-current="true"] { opacity: 1; border-color: var(--epp-blue);
  box-shadow: inset 0 0 0 2px var(--epp-blue); }
.epp-gallery__thumb:focus-visible { outline: 3px solid var(--epp-blue); outline-offset: 2px; }
/* Crossfade on swap, gated on .epp-js so a no-JS page never dims the image. */
.epp-js .epp-gallery__main { transition: opacity var(--eds-dur) var(--eds-ease); }
.epp-js .epp-gallery__main.epp-swapping { opacity: .25; }
.epp p.epp-figcaption { margin: 12px 2px 0; font-size: 13.5px; line-height: 1.45;
  color: var(--epp-soft-ink); }

/* ============================== CONNECTIVITY ============================== */
.epp-figures { display: flex; flex-direction: column; gap: var(--eds-space-lg); }
/* Narrower than the page measure, and centred. Two reasons, both from Kim
   2026-07-31: the images were "showing too big", and then "showing in low
   quality".
   The two are the same problem. These crops come out of the datasheet at 300 DPI
   and that is the ceiling (its pages are flattened rasters, so there is no
   higher-resolution version to extract). At the full 1500px container the panel
   callouts rendered at 0.63 of their natural size, close enough to 1:1 that every
   soft edge showed, and the callout labels baked into them came out LARGER than
   the body copy beside them, which inverts the page's type hierarchy. Halving the
   display width fixes both at once: roughly 0.3 scale hides the softness and puts
   the callout text back at caption size.
   Capping the container rather than the images keeps captions on the same measure
   as the drawing above them. */
.epp-figures--narrow { max-width: 960px; margin-inline: auto; }
.epp-figure { margin: 0; }
.epp-figure img { display: block; width: 100%; height: auto; border-radius: var(--eds-radius); }
.epp-figure figcaption { margin-top: var(--eds-space-xs); font-size: 15px; line-height: 1.5;
  color: var(--epp-body-ink); max-width: 80ch; }
/* Two figures side by side, aligned to the top so a short caption under one does
   not drag the other down. Used twice now: the panel callouts and the dimension
   drawings. */
.epp-figure-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--eds-space-lg); align-items: start; }
@media (max-width: 760px) { .epp-figure-pair { grid-template-columns: 1fr; } }
/* No forced aspect ratio. An earlier version normalised both drawings into a 4/3
   box and letterboxed them with contain, which meant the 2.37:1 front elevation
   sat in the middle of a tall empty white panel. Their natural ratios differ, and
   letting each keep its own is both tidier and safer: nothing is ever cropped,
   which matters here because a crop would hide a dimension. */
.epp-figure--drawing img { background: #ffffff; }

/* ============================== SPECIFICATIONS ==============================
   The project pages' .proj-specs treatment: uppercase key column, hairline
   rows, no outer frame, and the same collapse-with-a-fade expander. 25 rows is
   past what anyone reads in one go, and the previous draft printed all of them
   as a wall. Collapsed it shows the rows that decide a purchase; the rest is
   one click away. */
.epp-specs { width: 100%; border-collapse: collapse; }
.epp-specs th, .epp-specs td { text-align: left; vertical-align: top; padding: 13px 14px;
  font-size: 16px; line-height: 1.5; }
.epp-specs th { width: 30%; font-family: var(--enttec-font-body); font-weight: 600;
  color: var(--epp-soft-ink); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 13px; padding-right: 18px; }
.epp-specs td { color: var(--epp-ink); }

/* GROUP HEADINGS. Baymard's product-page study: a spec sheet over about 20 rows
   needs its specs grouped into labelled subsections, and 38% of sites fail on
   exactly this. At 36 rows a flat list is the "wall of specs" that finding
   describes, and grouping is the single change that does most of the work here. */
.epp-specs__group th { font-family: var(--enttec-font-display); font-weight: 600;
  font-size: 15px; letter-spacing: 0; text-transform: none; color: var(--epp-ink);
  width: auto; padding: var(--eds-space-md) 14px 10px;
  border-bottom: 2px solid var(--epp-blue); }
.epp-specs tbody:first-child .epp-specs__group th { padding-top: 0; }

/* ALTERNATING ROW TINTS, the same study's fourth finding: a band behind the pair
   is what stops the eye losing the line between a label on the left and its
   value 900px away on the right. nth-of-type(even) counts within each tbody, so
   the banding restarts per group and never depends on how many rows came before. */
.epp-specs tbody tr:nth-of-type(even):not(.epp-specs__group) { background: var(--epp-panel); }
.epp-specs tbody tr:not(.epp-specs__group) { transition: background var(--eds-dur-fast) var(--eds-ease); }
.epp-specs tbody tr:not(.epp-specs__group):hover { background: rgba(14,165,223,.10); }

@media (max-width: 620px) {
  .epp-specs th, .epp-specs td { display: block; width: 100%; padding-right: 14px; }
  .epp-specs td { padding-top: 0; }
  .epp-specs__group th { display: block; }
}

/* COLLAPSE. Hides whole GROUPS, never a pixel height, so the cut always lands on
   a labelled boundary and reads as deliberate rather than as a table that ran out
   of room. It also sidesteps the trap the previous version hit: .is-collapsed
   used to carry a max-height TRANSITION, which made a clientHeight read taken
   straight after collapsing return the pre-transition height, so the script
   decided the table was already short enough and disabled its own control. There
   is nothing to measure now. */
.epp-specs-panel.is-collapsed .epp-specs__extra { display: none; }
/* While a search is running every group is in play, so the collapse is off and
   the control is out of the way. */
.epp-specs-panel.is-searching .epp-showmore { display: none; }

/* SEARCH. Hidden until the script un-hides it: with no JS it would be a text box
   that does nothing, which is worse than no text box. */
.epp-specs-search { margin-bottom: var(--eds-space-md); display: none; }
.epp-specs-panel.epp-js-ready .epp-specs-search { display: block; }
/* Visible label, not a placeholder doing double duty: a placeholder disappears
   the moment someone types, and this control needs to stay self-explanatory
   while it is in use. Small and quiet, so it does not compete with the h2. */
.epp-specs-search__label { display: block; font-size: 13px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--epp-soft-ink);
  margin-bottom: 8px; }
/* Scoped under .epp to reach (0,2,0). The theme styles form fields with a long
   list of attribute selectors, `input[type="color"], input[type="date"], ...
   input[type="search"] { background: #fff; color: #2c3338 }`, and each of those
   is (0,1,1), which outranks a single class. Left at one class this field
   rendered as a white box with near-black text on the charcoal band: exactly the
   "white background on the grey background" Kim flagged. Raising our own
   specificity is the fix rather than an !important, and the declarations stay
   correct once that bundle is gone. */
.epp .epp-specs-search__input { width: 100%; max-width: 460px; box-sizing: border-box;
  font-family: var(--enttec-font-body); font-size: 16px; line-height: 1.4;
  /* 16px is not a style choice: anything smaller makes iOS Safari zoom the whole
     page when the field takes focus, and the reader loses their place. */
  padding: 11px 14px; color: var(--epp-ink);
  background: var(--epp-panel); border: 1px solid var(--epp-rule);
  border-radius: var(--eds-radius-sm); -webkit-appearance: none; appearance: none;
  transition: border-color var(--eds-dur-fast) var(--eds-ease), background var(--eds-dur-fast) var(--eds-ease); }
.epp .epp-specs-search__input::placeholder { color: var(--epp-soft-ink); opacity: 1; }
.epp .epp-specs-search__input:hover { border-color: var(--epp-soft-ink); }
.epp .epp-specs-search__input:focus { outline: none; border-color: var(--epp-blue);
  background: rgba(255,255,255,.09); }
.epp .epp-specs-search__input:focus-visible { outline: 2px solid var(--epp-blue); outline-offset: 2px; }
/* Chrome draws its own clear button on a search input and colours it from the
   UA sheet, so on a dark field it lands as a dark X on a dark background.
   Hidden, because Esc already clears the field and the count line says what is
   filtered. */
.epp .epp-specs-search__input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.epp p.epp-specs-search__count { font-size: 13.5px; color: var(--epp-soft-ink);
  margin: 8px 0 0; min-height: 1.3em; }
.epp p.epp-specs-empty { margin: var(--eds-space-md) 0 0; color: var(--epp-body-ink); font-size: 16px; }
.epp-specs-empty[hidden] { display: none; }

.epp-showmore { margin-top: 18px; display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 1px solid var(--epp-rule); border-radius: var(--enttec-radius-pill);
  color: var(--epp-ink); font-family: var(--enttec-font-body); font-size: 14px;
  padding: 9px 20px; cursor: pointer;
  transition: border-color var(--eds-dur) var(--eds-ease), transform var(--eds-dur) var(--eds-ease); }
.epp-showmore:hover { border-color: var(--epp-blue); transform: translateY(-2px); }
/* [hidden] has to be restated because the display:inline-flex above outranks the
   UA's own [hidden]{display:none}. The button ships hidden and the script
   un-hides it, so without this line the control is visible before it works. */
.epp-showmore[hidden] { display: none; }
.epp-showmore:focus { outline: none; }
.epp-showmore:focus-visible { outline: 2px solid var(--epp-blue); outline-offset: 3px; }
.epp-showmore svg { width: 13px; height: 13px; transition: transform var(--eds-dur) var(--eds-ease); }
.epp-showmore[aria-expanded="true"] svg { transform: rotate(180deg); }

/* Compliance marks, under the specification table where they belong (they are a
   specification, not a feature). Four marks, which is every one the site has an
   image for; WEEE has never had one and is stated in the text line instead. */
.epp-compliance { list-style: none; margin: var(--eds-space-md) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--eds-space-sm); align-items: center; }
.epp-compliance li { margin: 0; }
.epp-compliance img { height: 42px; width: auto; }
.epp p.epp-compliance-note { margin: var(--eds-space-xs) 0 0; font-size: 15px;
  line-height: 1.5; color: var(--epp-soft-ink); }

/* ============================== RESOURCES ==============================
   Rows inside the live product page's pane accordion (Data Sheet /
   Documentation / Images), with the first pane open so the data sheet, which is
   what most people come here for, needs no click at all.

   Firmware is deliberately absent: ELM HUB runs Windows and ELM, it has no
   device firmware, and an empty Firmware pane would imply one exists.

   The ROWS themselves keep the polished treatment (hairline rules, hover nudge,
   arrow, a sub-label saying whether the row downloads or navigates). That is the
   split this whole sheet is built on after Kim's 2026-07-30 note: the product
   page's LAYOUT, the project pages' interaction quality. */
/* Three columns exactly as the live product page has them: type icon, label,
   download icon. Kim 2026-07-31.

   BORDERS. There is exactly ONE rule in here, BETWEEN rows, and nothing else.
   The previous version had a border on the wrapper's top edge, a border under
   every row including the last, and the accordion pane's own border underneath
   all of that, which drew a double line at every seam ("way too much vertical
   lines or bottom borders"). :not(:last-child) is what keeps the final row from
   doubling up with the pane border below it. */
.epp-rows { list-style: none; margin: 0; padding: 0; }
.epp-rows > li { margin: 0; }
.epp-rows > li:not(:last-child) { border-bottom: 1px solid var(--epp-rule); }
.epp-row { display: grid; grid-template-columns: 40px minmax(0, 1fr) 28px;
  align-items: center; gap: var(--eds-space-sm);
  /* 14px block padding on a 40px icon clears the 44px tap-target floor. */
  padding: 14px var(--eds-space-xs) 14px 0;
  text-decoration: none; color: inherit;
  transition: background var(--eds-dur-fast) var(--eds-ease),
              padding-left var(--eds-dur) var(--eds-ease); }
.epp a.epp-row { transition: background var(--eds-dur-fast) var(--eds-ease),
                             padding-left var(--eds-dur) var(--eds-ease); }
.epp-row:hover { background: var(--epp-panel); padding-left: var(--eds-space-xs); }
.epp-row:focus-visible { outline: 2px solid var(--epp-blue); outline-offset: -2px; }
/* The two icon rules are scoped under .epp purely to reach (0,2,0). An img
   sizing rule in the minified theme bundle (cross-origin, so its selector cannot
   be read from the page) outranks a single class and was stretching these to
   their intrinsic aspect: the 100x123 icon rendered 40x49 instead of 40x40.
   Raising our own specificity is the fix rather than an !important, and both
   stay correct once that bundle is gone. */
.epp .epp-row__icon { width: 40px; height: 40px; }
.epp .epp-row__dl { width: 28px; height: 28px; justify-self: end;
  transition: transform var(--eds-dur) var(--eds-ease); }
.epp-row:hover .epp-row__dl { transform: translateY(2px); }
.epp-row__label { font-size: 16.5px; color: var(--epp-ink);
  transition: color var(--eds-dur-fast) var(--eds-ease); }
.epp-row:hover .epp-row__label { color: var(--epp-blue); }
/* Resources sits in the 24/76 split, whose right column is 967px wide. Four
   short download rows across that measure leave the download icon stranded a
   long way from its label, so the list is capped and left-aligned inside it.
   Kim 2026-07-31: "the width much smaller". */
.epp-accordion--rows { max-width: 720px; }

/* ============================== RELATED PRODUCTS ==============================
   Four-across card grid, the live product page's layout, using the design
   system's own eds-card--product so these cards match every other product card
   on the site rather than inventing a second kind.

   The renders are all shot on white and the band is grey, so the media box takes
   the band's panel colour and the image is CONTAINED inside it. cover would crop
   a wide render like the Pixelator Mini Mk2 through the middle of the unit.

   NOTE for Kim: .eds-card--product h4 pins card titles at 16px, which is smaller
   than the 16.5px body text beside them. That is a design-system decision, not
   one made here, and changing it would change every product card on the site. It
   is worth a separate look. */
/* FOUR columns: three of products and a fourth holding the "Explore the full
   range" card, exactly as the live product page lays it out. Kim 2026-07-31.

   ELM HUB has four related products and only three columns, so the product side
   is a scroll-snapping carousel rather than a grid: the fourth product is one
   nudge away instead of dropped or squeezed. Scroll-snap does the work, so the
   list is still a plain scrollable list with no JS (the arrows only call
   scrollBy), and a trackpad, a touch swipe, shift-scroll and the keyboard all
   work without us implementing any of them. */
.epp-related { display: grid; grid-template-columns: 3fr 1fr;
  gap: var(--eds-carousel-gap, var(--eds-gap-card)); align-items: stretch; }
.epp-related .eds-card__media { background: var(--epp-panel); }
.epp-related .eds-card__media > img { width: 100%; height: 100%; object-fit: contain; }

.epp-related__carousel { position: relative; min-width: 0; }
.epp-related__track { list-style: none; margin: 0; padding: 0;
  display: flex; gap: var(--epp-carousel-gap);
  overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth;
  /* The native bar is hidden because the arrows and the peeking fourth card
     already say "there is more", and a permanent bar under a row of product
     cards reads as a rendering fault. Keyboard and touch scrolling are
     untouched. */
  scrollbar-width: none; -ms-overflow-style: none; }
.epp-related__track::-webkit-scrollbar { display: none; }
/* Slide width is derived, not typed: N visible cards share the track minus the
   (N-1) gaps between them. Change --epp-carousel-visible and everything follows. */
.epp-related__track > li { margin: 0; scroll-snap-align: start;
  flex: 0 0 calc((100% - (var(--epp-carousel-visible) - 1) * var(--epp-carousel-gap))
                 / var(--epp-carousel-visible)); }

.epp-related__arrow { position: absolute; top: 32%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--epp-rule);
  background: #fff; color: var(--epp-black); font-size: 22px; line-height: 1;
  cursor: pointer; z-index: 3; display: grid; place-items: center; padding: 0;
  box-shadow: 0 8px 22px -10px rgba(0,0,0,.35);
  transition: background var(--eds-dur) var(--eds-ease), color var(--eds-dur) var(--eds-ease),
              opacity var(--eds-dur) var(--eds-ease), transform var(--eds-dur) var(--eds-ease); }
.epp-related__arrow:hover { background: var(--epp-black); color: #fff; }
.epp-related__arrow:focus-visible { outline: 3px solid var(--epp-blue); outline-offset: 2px; }
.epp-related__arrow--prev { left: -14px; }
.epp-related__arrow--next { right: -14px; }
/* The arrows never disable: the track loops (a 3x clone set with a no-animation
   teleport back to the middle copy), so there is no end to reach. The rule is
   kept because a future single-slide carousel would have nothing to scroll and
   should hide them rather than offer a dead click. */
.epp-related__arrow[disabled] { opacity: 0; pointer-events: none; }
/* Clones are visual filler. Hidden from assistive tech in the markup; this stops
   them being reachable by find-in-page or text selection as well. */
.epp-related__track > [data-epp-clone] { user-select: none; }

/* The fourth column. Bordered rather than filled: on the white band a filled
   panel would out-weigh the product cards beside it, and this card is a way out
   of the section, not the point of it. */
.epp-related__viewall { display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center; gap: var(--eds-space-sm);
  border: 1px solid var(--epp-rule); border-radius: var(--eds-radius-lg);
  padding: var(--eds-space-md); }
.epp h3.epp-related__viewall-title, .epp-related__viewall h3 {
  font-family: var(--enttec-font-display); font-weight: 500; font-size: clamp(20px, 2vw, 26px);
  line-height: 1.2; color: var(--epp-ink); margin: 0; text-wrap: balance; }

@media (max-width: 1100px) { .epp { --epp-carousel-visible: 2; } }
@media (max-width: 900px) {
  .epp-related { grid-template-columns: 1fr; }
  .epp-related__viewall { flex-direction: row; justify-content: space-between; text-align: left; }
}
@media (max-width: 620px) {
  .epp { --epp-carousel-visible: 1; }
  .epp-related__viewall { flex-direction: column; text-align: center; }
  /* Off-canvas arrows have nowhere to sit once the track reaches the viewport
     edge, so they come inside. */
  .epp-related__arrow--prev { left: 4px; }
  .epp-related__arrow--next { right: 4px; }
}

/* NOTE, 2026-07-31: there is deliberately no closing CTA block here any more.
   An earlier draft ended the page with a "Talk to a lighting expert" panel. Kim
   asked whether the real product page has one; it does not (its last section is
   Related Products, then the footer), so it came out. The enquiry-only
   explanation it carried moved into the overview's last paragraph, which is
   where a reader wonders about it anyway, and the conversion point stays the
   theme's own "Contact an Expert" button in the banner. */

/* ============================== REVEAL ==============================
   Gated on .epp-js, added by the script. Without JS nothing is ever hidden, so
   a script failure cannot blank the page. */
.epp-js .epp-reveal { opacity: 0; transform: translateY(22px);
  transition: opacity var(--eds-dur-slow) var(--eds-ease), transform var(--eds-dur-slow) var(--eds-ease); }
.epp-js .epp-reveal.epp-in { opacity: 1; transform: none; }

/* ============================== REDUCED MOTION ==============================
   Blanket-kill animation and transition timing across the whole layer (the same
   way the design system does it), so every hover micro-animation degrades to an
   instant state change rather than motion. Every control keeps a NON-motion
   signal: colour for the nav, the rows and the accordion; the active thumb plus
   the "Image n of m" counter for the gallery. */
@media (prefers-reduced-motion: reduce) {
  .epp *, .epp *::before, .epp *::after { animation: none !important; transition: none !important; }
  /* scroll-behavior is NOT an animation or a transition, so the blanket rule
     above does not touch it and the carousel would keep gliding. The arrows pass
     behavior:'auto' explicitly, which wins for those, but native scrolling
     (keyboard, scroll-into-view) would still animate without this line. */
  .epp-related__track { scroll-behavior: auto !important; }
  .epp-js .epp-reveal { opacity: 1 !important; transform: none !important; }
  .epp-js .epp-gallery__main.epp-swapping { opacity: 1 !important; }
  /* Remove the purely decorative hover transforms. Colour and the sub-nav
     underline still fire, so hover stays legible without anything moving. */
  .epp-row:hover .epp-row__cta::after,
  .epp-showmore:hover,
  .epp .eds-btn:hover { transform: none !important; }
}
