/*==============================================================================
  Digi Kydo — EDITORIAL LAYER

  WHY THIS FILE EXISTS, AND WHY IT IS NOT IN overrides.css.
  overrides.css states its own contract at the top: "if it moves a pixel, it
  does not belong here." Everything below moves pixels on purpose. Brief
  changed 2026-07-29: the goal is no longer pixel-parity with the old
  WordPress site, it is a better layout for /service/*, /our-services/ and
  /about/. Keeping the two intents in separate files means the parity layer
  stays auditable.

  THE CONSTRAINT THIS LAYER IS DESIGNED AROUND: THERE IS NO PHOTOGRAPHY.
  Six of eight service pages have zero images in content_json. So none of the
  composition below depends on an image existing. The hierarchy is carried by
  type scale, an 8px spacing rhythm, colour-blocked bands and rules — all of
  which render identically whether a photo is present or not. Where a real
  image does exist (2 banners, 3 branding gallery shots, 91 portfolio
  thumbnails, 6 client logos) it is used as reinforcement, never as structure.

  COLOUR RULES ARE MEASURED, NOT ASSUMED. See tokens.css for the full matrix.
  The one that constrains every decision here (value changed 2026-07-29 when
  the owner replaced the green accent with the logo's orange; the RULE did not
  change shape, only its number):
      --secondary-color #f95e1a is 3.16:1 on white.
  So an orange band carries INK text, and bright orange text on white is never
  used. The accent survives as: a surface tint, an on-dark accent, and a
  rule/keyline. Orange TEXT is --secondary-deep / --secondary-deepest.

  Load order: LAST, after storefront.css.
==============================================================================*/

/*------------------------------------------------------------------------------
  0. THE CONTRAST FIX  (ui-ux-pro-max priority 1, `color-contrast`)

  core.css:32 sets `body { color: var(--gray-color) }`, i.e. #8f93a5, which
  measures 3.05:1 on white. That was the site's DEFAULT BODY COLOUR, so every
  paragraph on every page failed WCAG AA. Repointed to #565a69 (6.86:1).

  --gray-color itself is deliberately NOT changed: main.css uses it for ~40
  borders, rules and overlays where it is decoration, not text, and rewriting
  the token would move pixels on pages this brief says not to touch. Only the
  text uses are repointed, here.
------------------------------------------------------------------------------*/
body {
  color: var(--text-body-color);
}

/* REGRESSION I CAUSED AND CAUGHT, documented so it is not reintroduced.
   The line above is right for the ~95% of the site that is light, and WRONG
   for the two dark surfaces, which inherited from `body` too. Measured:
       #565a69 on #1a1a20  =  2.53:1   FAIL   (what the line above did)
       #8f93a5 on #1a1a20  =  5.62:1   PASS   (what it was before, and is now)
   The legacy grey was never the problem on dark — it was only ever the
   problem on white. So the dark surfaces are pinned back to it explicitly. */
.shock-footer,
.shock-footer p,
.shock-footer .text,
.menu-grid,
.menu-grid p,
.slide-info,
.slide-info p {
  color: var(--gray-color);
}

/* .gray / .gray-75 as a TEXT colour. Same reasoning: these two selectors are
   the text-bearing uses (descriptions, article prose, list copy). The
   border/overlay uses of the same tokens are untouched. */
.shock-section .content,
.shock-section .content p,
.shock-section .content li,
.basic-intro .description.gray,
.basic-intro .description.gray p,
p.gray,
li.gray {
  color: var(--text-body-color);
}
/* .gray-75 on body-size text resolves to 2.21:1. Only ever legitimate on the
   oversized italic display strings, which are large-text (3:1). */
.shock-section .content .gray-75:not([class*="text-style-"]) {
  color: var(--text-body-color);
}

/* Tables inside the editorial prose. /our-services/ has a two-column
   "your situation -> start here" table in body_html whose <td>s were 2.85:1. */
.ed-prose table { width: 100%; margin: var(--space-15) 0; border-collapse: collapse; }
.ed-prose th,
.ed-prose td {
  padding: 0.625rem 0.75rem 0.625rem 0;
  border-bottom: 1px solid var(--border-subtle);
  text-align: left;
  vertical-align: top;
  color: var(--text-body-color);
  line-height: 1.5;
}
.ed-prose th { font-family: var(--secondary-font); font-weight: 700; color: var(--black-color); }

/*  HOMEPAGE + SHARED COMPONENTS — COLOUR ONLY, NO LAYOUT.
    The brief says do not touch the homepage, and this does not: there is no
    size, weight, spacing or position change below, only a text colour that
    was failing AA. Three components, all measured on / at 1280 and 375:
      .slide-description .description.gray p   3.05:1 -> 6.86:1
      .slide-action-title                      3.05:1 -> 6.86:1
    The big italic scrolling-text (.text-style-8.gray-75, 35px/200) is LARGE
    text, so its threshold is 3:1 — it measures 2.21:1 and still fails, but it
    is a decorative marquee of technology names that is also duplicated as
    real navigation elsewhere. Raising it would visibly change the homepage's
    signature effect, so it is left alone and FLAGGED rather than silently
    restyled or silently ignored.

    NOTE ON SPECIFICITY: dynamic-slider.min.css declares
    `.dynamic-slider .slide-action-title { color: var(--gray-color) }` at
    0,2,0. A bare `.slide-action-title` (0,1,0) loses to it — verified by the
    fix not landing on the first attempt. The selectors below match the
    vendor's own specificity deliberately. */
.dynamic-slider .slide-description .description.gray p,
.dynamic-slider .slide-description .description.gray,
.dynamic-slider .slide-action-title {
  color: var(--text-body-color);
}

/*------------------------------------------------------------------------------
  1. BANDS — the vertical rhythm and the colour blocking.

  Section spacing tiers are 8px multiples throughout: 96 / 64 / 56 / 40 / 24.
------------------------------------------------------------------------------*/
.ed-band {
  position: relative;
  padding-top: 3.5rem;    /* 56px */
  padding-bottom: 3.5rem;
}
@media (min-width: 992px) {
  .ed-band {
    padding-top: var(--space-6);   /* 96px */
    padding-bottom: var(--space-6);
  }
}
.ed-band--tight { padding-top: 2.5rem; padding-bottom: 2.5rem; }
.ed-band--neutral   { background-color: var(--surface-neutral); }
.ed-band--primary   { background-color: var(--surface-primary); }
.ed-band--secondary { background-color: var(--surface-secondary); }
.ed-band--ink       { background-color: var(--surface-ink); color: #fff; }

/* A hairline that reads as a deliberate keyline rather than a Bootstrap <hr>. */
.ed-band + .ed-band:not([class*="--"]) {
  border-top: 1px solid var(--border-subtle);
}

.ed-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 1.5rem;
}
@media (min-width: 768px) {
  .ed-inner { padding: 0 2rem; }
}

/* Long-form prose measure. ui-ux-pro-max §6 `line-length`: 60-75 chars.
   68ch at 16px Lato lands at ~640px, which is the readable band. Before this,
   article copy ran the full 8-column width (~740px, ~95 chars). */
/* 72ch at 16px Lato is ~660px, i.e. ~72 characters — the top of the 60-75
   band in ui-ux-pro-max §6 `line-length`. The old layout ran ~95 characters. */
.ed-measure { max-width: 72ch; }
.ed-measure--wide { max-width: 78ch; }

/*------------------------------------------------------------------------------
  2. TYPE — the hierarchy that does the work photography would have done.
------------------------------------------------------------------------------*/

/* Eyebrow / kicker. Small, but 700 weight and a real colour, so it reads as a
   label and not as faded text. #2d6076 is 6.89:1 on white, 5.94:1 on the
   tints — it passes AA at this size on every surface used here. */
.ed-eyebrow {
  display: block;
  margin: 0 0 var(--space-15);
  font-family: var(--secondary-font);
  font-size: 0.8125rem;  /* 13px */
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--primary-deep);
}
.ed-band--ink .ed-eyebrow { color: var(--secondary-color); } /* 7.25:1 on ink */
.ed-eyebrow--secondary { color: var(--secondary-deep); }

/* Section heading. Fluid between 28px and 44px, so the step from H1 down is
   obvious at every width without a media-query staircase. */
.ed-h2 {
  margin: 0 0 var(--space-15);
  font-family: var(--secondary-font);
  font-size: clamp(1.75rem, 1.25rem + 2vw, 2.75rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--black-color);
}
.ed-band--ink .ed-h2 { color: #fff; }

/* The lede. One size up from body, ink-dark, capped measure. This is the
   single biggest reason the old page read as a grey wall: the opening
   paragraphs were the same 16px #8f93a5 as everything else. */
.ed-lede {
  margin: 0 0 var(--space-2);
  max-width: 60ch;
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.3125rem);
  line-height: 1.55;
  color: var(--black-color);
}
.ed-band--ink .ed-lede { color: rgba(255, 255, 255, 0.92); }

/* The intro block promotes its FIRST paragraph to lede size. Doing it by
   position rather than by asking the content agent to add a class means it
   works on all eight service pages today, and on any new one tomorrow. */
.ed-lede-block > p:first-child {
  max-width: 60ch;
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.3125rem);
  line-height: 1.55;
  color: var(--black-color);
}

/* Article prose inside the editorial bands. */
/* Vertical rhythm is deliberately tighter than the default editorial instinct.
   These articles run 1,200-1,700 words; at 4rem between H2s the /seo/ detail
   band measured 4,552px on its own. Measured back down to ~3,600px at these
   values without the copy feeling cramped — paragraph leading is untouched. */
.ed-prose > * + * { margin-top: 1rem; }
.ed-prose p,
.ed-prose li { color: var(--text-body-color); line-height: 1.6; }
.ed-prose h2 {
  margin: var(--space-3) 0 var(--space-1);
  font-family: var(--secondary-font);
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--black-color);
}
/* A short green keyline above each H2 — a section marker that costs no image
   and survives at 375px. */
.ed-prose h2::before {
  content: "";
  display: block;
  width: 48px;
  height: 4px;
  margin-bottom: var(--space-15);
  background-color: var(--secondary-color);
  border-radius: 2px;
}
.ed-prose h3 {
  margin: var(--space-15) 0 var(--space-05);
  font-family: var(--secondary-font);
  font-size: 1.1875rem;  /* 19px */
  font-weight: 700;
  line-height: 1.3;
  color: var(--black-color);
}
.ed-prose ul { margin: var(--space-1) 0; padding-left: 1.25rem; }
.ed-prose li { margin-bottom: var(--space-05); }
.ed-prose strong { color: var(--black-color); }
.ed-prose a {
  color: var(--primary-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.ed-prose a:hover { text-decoration-thickness: 2px; }

/*------------------------------------------------------------------------------
  3. POINT CARDS — service_points.bullet_points rendered as cards.

  Was: a flat <ul> of Font Awesome check icons in 16px grey, dumped after the
  whole article. The bullets are the most scannable content on the page and
  they were the least visible thing on it.

  The check icon is kept (it is the only icon data that exists) but is drawn
  as a token-coloured SVG-free glyph chip rather than inline text, so the card
  has a fixed visual anchor at a consistent size.
------------------------------------------------------------------------------*/
.ed-points {
  display: grid;
  gap: var(--space-1);
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 640px) { .ed-points { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .ed-points { grid-template-columns: repeat(3, 1fr); gap: var(--space-15); } }

.ed-point {
  display: flex;
  gap: var(--space-1);
  align-items: flex-start;
  padding: var(--space-15);
  background-color: #fff;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg, 12px);
  /* The left edge is the only colour on the card, so the grid reads as a set
     without six competing filled blocks. */
  border-left: 3px solid var(--secondary-color);
  box-shadow: 0 1px 2px rgba(26, 26, 32, 0.04);
  /* transform/opacity only — never width/height (ui-ux-pro-max §7). */
  transition: transform var(--duration-fast) ease-out,
              box-shadow var(--duration-fast) ease-out;
}
.ed-point:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-small);
}
.ed-point__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background-color: var(--surface-secondary);
  color: var(--secondary-deep);  /* 6.77:1 on the green tint */
  font-size: 0.875rem;
}
.ed-point__text {
  margin: 0;
  font-size: 1rem;   /* never below 16px — iOS auto-zooms smaller body text */
  line-height: 1.55;
  color: var(--black-color);
}

/* Mobile density. At 375px the eleven point cards on /service/seo/ measured
   1,381px stacked one-up. Trimming the chrome (not the type — body stays at
   16px) brings that down without making the cards feel cramped. */
@media (max-width: 639px) {
  .ed-point { padding: var(--space-1); gap: 0.75rem; }
  .ed-point__icon { width: 28px; height: 28px; font-size: 0.8125rem; }
}

/* The bullet group's own heading + its closing line. */
.ed-points-group + .ed-points-group { margin-top: var(--space-4); }
.ed-points-group__head { margin-bottom: var(--space-2); }
.ed-points-group__cta {
  margin: var(--space-2) 0 0;
  font-family: var(--secondary-font);
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--primary-deep);
}

/*------------------------------------------------------------------------------
  4. PROCESS STEPS — service_steps, which the old template rendered NOWHERE.

  Three rows of real copy per page were sitting unused in content_json. A
  numbered process is the classic no-photography agency module: the numeral is
  the graphic.
------------------------------------------------------------------------------*/
.ed-steps {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: 1fr;
  /* MEASURED BUG, not a preference: without this the shorter step was
     vertically centred in its grid row (step 02 sat 8px below 01 and 03 at
     1280px, so the numerals and titles did not line up across the three
     columns). `start` top-aligns them. */
  align-items: start;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 768px) { .ed-steps { grid-template-columns: repeat(3, 1fr); gap: var(--space-3); } }

.ed-step { position: relative; padding-top: var(--space-2); }
/* The connecting rule between steps. Decorative, so it is hidden from AT and
   dropped entirely on mobile where the steps stack. */
.ed-step::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background-color: var(--border-subtle);
}
.ed-step:first-child::before { background-color: var(--secondary-color); }

.ed-step__num {
  display: block;
  margin-bottom: var(--space-05);
  font-family: var(--secondary-font);
  font-size: clamp(2.5rem, 2rem + 2vw, 3.5rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.03em;
  /* Outlined numeral: the theme's own .text-outline idiom, reused so the
     module looks native to the brand rather than bolted on. Falls back to a
     solid tinted numeral where -webkit-text-stroke is unsupported. */
  color: var(--surface-primary);
  -webkit-text-stroke: 2px var(--primary-deep);
}
/* Browsers without -webkit-text-stroke (it is unprefixed nowhere) would render
   the numeral in the near-white fill and it would vanish. Paint it solid there. */
@supports not (-webkit-text-stroke: 2px black) {
  .ed-step__num { color: var(--primary-deep); }
}
.ed-step__title {
  margin: 0 0 var(--space-05);
  font-family: var(--secondary-font);
  font-size: 1.1875rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--black-color);
}
.ed-step__body { margin: 0; color: var(--text-body-color); line-height: 1.6; }

/*------------------------------------------------------------------------------
  5. FAQ ACCORDION — native <details>/<summary>.

  Was: Bootstrap collapse with `.show` + aria-expanded="true" on every row, so
  all six answers were open and the "accordion" was just more wall. Native
  <details> is keyboard-operable and screen-reader-announced with zero JS,
  which also means zero new console-error surface.

  Touch target: the <summary> row is min-height 56px (ui-ux-pro-max §2 needs
  >=44px).
------------------------------------------------------------------------------*/
.ed-faq { border-top: 1px solid var(--border-subtle); }
.ed-faq__item { border-bottom: 1px solid var(--border-subtle); }

.ed-faq__q {
  display: flex;
  gap: var(--space-1);
  align-items: center;
  justify-content: space-between;
  min-height: 56px;
  padding: var(--space-15) 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--secondary-font);
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--black-color);
  transition: color var(--duration-fast) ease-out;
}
.ed-faq__q::-webkit-details-marker { display: none; }
.ed-faq__q:hover { color: var(--primary-deep); }
.ed-faq__q:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}

/* The +/- indicator. Built from two bars so it can rotate on open without
   animating layout, and it is not conveying anything by colour alone — the
   open/closed state is also exposed natively by <details>. */
.ed-faq__sign {
  position: relative;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
}
.ed-faq__sign::before,
.ed-faq__sign::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 2px;
  background-color: var(--primary-deep);
  transform: translate(-50%, -50%);
  transition: transform var(--duration-fast) ease-out, opacity var(--duration-fast) ease-out;
}
.ed-faq__sign::after { transform: translate(-50%, -50%) rotate(90deg); }
.ed-faq__item[open] .ed-faq__sign::after {
  transform: translate(-50%, -50%) rotate(0deg);
  opacity: 0;
}

.ed-faq__a {
  padding: 0 0 var(--space-15);
  max-width: 68ch;
  color: var(--text-body-color);
  line-height: 1.65;
}
.ed-faq__item[open] .ed-faq__a { animation: ed-reveal var(--duration-base) ease-out; }
@keyframes ed-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/*------------------------------------------------------------------------------
  6. PRIMARY CTA  (ui-ux-pro-max §4 `primary-action`: ONE per screen)

  The old page had no button at all — the two conversion points were inline
  body links in the same colour as the surrounding prose. Every page now ends
  on one ink band with exactly one filled button. Secondary routes (phone,
  email) sit under it as plainly subordinate text links.
------------------------------------------------------------------------------*/
.ed-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  min-height: 52px;           /* >= 44px touch target */
  padding: 0.875rem 2rem;
  border: 2px solid transparent;
  border-radius: var(--radius-full);
  font-family: var(--secondary-font);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
  transition: transform var(--duration-fast) ease-out,
              background-color var(--duration-fast) ease-out,
              color var(--duration-fast) ease-out;
}
/* Filled green + INK label. NOT white-on-green: that pair is 2.39:1 and is the
   single most tempting mistake in this palette. Ink on #60b988 is 7.25:1. */
.ed-btn--primary {
  background-color: var(--secondary-color);
  color: var(--black-color);
}
.ed-btn--primary:hover,
.ed-btn--primary:focus-visible {
  background-color: #7ac79c;
  color: var(--black-color);
  transform: translateY(-2px);
}
.ed-btn--ghost {
  border-color: currentColor;
  background-color: transparent;
  color: var(--black-color);
}
.ed-band--ink .ed-btn--ghost { color: #fff; }
.ed-btn--ghost:hover { transform: translateY(-2px); }
.ed-btn:focus-visible { outline: 3px solid var(--primary-color); outline-offset: 3px; }

.ed-cta__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  align-items: center;
  margin-top: var(--space-2);
}
.ed-cta__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  color: rgba(255, 255, 255, 0.85);   /* 12.5:1 on ink */
  text-decoration: underline;
  text-underline-offset: 4px;
}
.ed-cta__link:hover { color: var(--secondary-color); }

/*------------------------------------------------------------------------------
  7. PROOF — portfolio slice + client logos.

  The only genuinely photographic assets this site owns (91 portfolio
  thumbnails, 6 client logos). Everything here is lazy-loaded; the strip sits
  well below the fold on every page that uses it.
------------------------------------------------------------------------------*/
.ed-proof {
  display: grid;
  gap: var(--space-1);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 640px) { .ed-proof { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .ed-proof { grid-template-columns: repeat(6, 1fr); } }

/* object-fit is `contain`, NOT `cover`, and this is not a style preference.
   The 91 "portfolio thumbnails" are brand LOGOS, not photographs — verified by
   eye at 1280px: with `cover` the strip rendered "HEAL(TH)", "Ang(el)" and
   "(Exp)ert" with their wordmarks sliced off at the frame edge. Cropping a
   client's logo is worse than showing it small. `contain` on a white card with
   padding is the treatment a logo asset actually wants. */
.ed-proof__item {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: var(--space-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background-color: #fff;
  aspect-ratio: 1 / 1;   /* reserves space — CLS (ui-ux-pro-max §3) */
  transition: border-color var(--duration-fast) ease-out,
              box-shadow var(--duration-fast) ease-out;
}
.ed-proof__item:hover {
  border-color: var(--secondary-color);
  box-shadow: var(--shadow-small);
}
.ed-proof__item img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  transition: transform var(--duration-slow) ease-out;
}
.ed-proof__item:hover img { transform: scale(1.04); }

.ed-proof__more { margin: var(--space-2) 0 0; }

/* The 3-shot first_gallery that only /service/branding/ has. Sits inline in
   the article, 3-up, and simply never renders on the other seven pages. */
.ed-proof--gallery {
  margin: var(--space-3) 0;
  grid-template-columns: repeat(3, 1fr);
}
.ed-proof--gallery .ed-proof__item {
  aspect-ratio: 3 / 2;
  cursor: default;
  padding: 0;
}
/* These three ARE photographs, unlike the logo thumbnails above, so they take
   `cover`. With the inherited `contain` they rendered 161x107 inside a 217px
   card, floating in white margins. Verified by eye on /service/branding/.
   SEPARATE ISSUE, FLAGGED NOT FIXED: the three images are generic stock
   photos of people in a cafe, not examples of Digi Kydo branding work. That
   is a content decision for the owner, not a CSS one. */
.ed-proof--gallery .ed-proof__item img {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
}

.ed-logos__title {
  margin: var(--space-3) 0 var(--space-1);
  font-family: var(--secondary-font);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--black-color);
}
.ed-logos {
  display: grid;
  gap: var(--space-1);
  grid-template-columns: repeat(3, 1fr);
  align-items: center;
}
@media (min-width: 768px) { .ed-logos { grid-template-columns: repeat(6, 1fr); } }
.ed-logos__item {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 64px;
  padding: var(--space-1);
}
.ed-logos__item img {
  max-width: 100%;
  max-height: 48px;
  width: auto;
  height: auto;
  object-fit: contain;
}

/*------------------------------------------------------------------------------
  8. SIDE RAIL — the "at a glance" / related-services rail.

  Replaces the old right column, which held a "Tag cloud" heading and ~200px
  of chips against ~6,000px of article, leaving a third of the page empty on
  desktop.
------------------------------------------------------------------------------*/
.ed-split {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}
@media (min-width: 992px) {
  .ed-split { grid-template-columns: minmax(0, 1fr) 300px; gap: var(--space-5); }
}
/* No rail: centres the 72ch column instead of leaving it hard left with ~400px
   of dead space down the right of the article (observed at 1280px before this
   rule existed).

   NO CALLER AS OF 2026-08-05. /our-services/ was the one page using it — the
   note here used to say a rail "would just repeat" its card grid, which was
   true of the links but not of their usefulness: that article is 5,307px tall
   at 1273px and the cards leave the viewport after the first screen, so it now
   takes `rail => 'services'` like /about/ and the industry hubs. The rule is
   kept, not deleted: it is the one-line way back if that call is reversed, and
   any future body-only page wants it. */
.ed-split--single { grid-template-columns: minmax(0, 1fr); }
.ed-split--single > .ed-measure { margin-inline: auto; }
.ed-rail { position: sticky; top: var(--space-2); align-self: start; }
@media (max-width: 991px) { .ed-rail { position: static; } }

/* Rail first on small screens. Opt-in, and currently only the industry hub /
   spoke template asks for it — the service and blog pages keep the rail after
   the article, which is right for them because their rails repeat links that
   already appear high up the page.

   Measured on /industries/healthcare-marketing-kolkata/ at 375px before this
   rule: the rail's four spoke links began at y=7468px, and the earliest
   in-prose link to any spoke was y=6400px — 7.1 screens of scrolling before a
   hub offered any route to the pages it exists to introduce. On desktop the
   rail is sticky and sits beside the article from the first screen, so this
   only ever applied below 992px.

   `order` on a grid item, so nothing about the desktop two-column geometry or
   the DOM order (article before aside, which is the correct reading order for
   assistive tech on desktop) changes. */
@media (max-width: 991px) {
  .ed-split--rail-first > .ed-rail { order: -1; }
}

/* Rail on desktop only. The opposite opt-in to --rail-first, and currently only
   /our-services/ asks for it.

   Below 992px the rail is static and lands at the very bottom of the article.
   Measured on /our-services/ at 375px: that put an 8-link "Our services" card
   at y=12,530 of a 13,110px page — after five screens of prose, immediately
   above a CTA band and a footer whose "Our Services" tag cloud already carries
   the same eight links as 44px targets. 580px of duplicate navigation, at the
   one width where vertical space is scarcest.

   On desktop the rail is sticky and useful from the first screen, which is the
   entire reason it was added, so it stays there.

   display:none rather than visibility or a wrapper: the links are duplicated in
   the footer at every breakpoint, so nothing becomes unreachable and no
   assistive-tech user loses a route. */
@media (max-width: 991px) {
  .ed-split--rail-desktop > .ed-rail { display: none; }
}

.ed-rail__card {
  padding: var(--space-2);
  background-color: var(--surface-neutral);
  border-radius: var(--radius-lg, 12px);
  border: 1px solid var(--border-subtle);
}
.ed-rail__title {
  margin: 0 0 var(--space-1);
  font-family: var(--secondary-font);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--black-color);
}
.ed-rail__list { margin: 0; padding: 0; list-style: none; }
.ed-rail__list li { border-top: 1px solid var(--border-subtle); }
.ed-rail__list li:first-child { border-top: 0; }
.ed-rail__list a {
  display: flex;
  align-items: center;
  min-height: 44px;          /* touch target */
  padding: var(--space-05) 0;
  color: var(--text-body-color);
  text-decoration: none;
  transition: color var(--duration-fast) ease-out;
}
.ed-rail__list a:hover { color: var(--primary-deep); text-decoration: underline; }

/*------------------------------------------------------------------------------
  9. INTRO MEDIA — used only where a real image exists (2 of 8 service pages).
  The layout must not collapse when it is absent, so the image lives in an
  optional grid cell, not in a background or a fixed-height hero.
------------------------------------------------------------------------------*/
.ed-intro { display: grid; gap: var(--space-3); grid-template-columns: 1fr; }
@media (min-width: 992px) {
  .ed-intro--with-media { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: var(--space-5); align-items: center; }
}
.ed-intro__media { margin: 0; }
.ed-intro__media img,
.ed-intro__media picture {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg, 12px);
}
.ed-intro__media img { aspect-ratio: 4 / 3; object-fit: cover; }

/*------------------------------------------------------------------------------
  10. REDUCED MOTION. overrides.css §5 already zeroes durations globally; this
  adds the two animations introduced in this file.
------------------------------------------------------------------------------*/
@media (prefers-reduced-motion: reduce) {
  .ed-point,
  .ed-btn,
  .ed-proof__item img,
  .ed-faq__sign::before,
  .ed-faq__sign::after {
    transition: none !important;
  }
  .ed-point:hover,
  .ed-btn:hover,
  .ed-proof__item:hover img { transform: none !important; }
  .ed-faq__item[open] .ed-faq__a { animation: none !important; }
}

/*------------------------------------------------------------------------------
  11. FIXED ENQUIRY RAIL  (owner request, 2026-07-29)

  Two always-visible actions — Call and Email — on every public page. Markup in
  resources/views/partials/enquiry-rail.blade.php; values come from Settings.

  MOTION IS TRANSFORM-ONLY (ui-ux-pro-max §7). The collapsed/expanded states are
  one translateX on the rail, not an animated width or max-width, so expanding
  costs no layout. The label column is a fixed 8.5rem so both actions travel the
  same distance and the two icons stay vertically aligned mid-animation.

  COLOUR, measured (see tokens.css):
    Call  : ink text on --secondary-color #f95e1a          5.48:1   AA
    Email : white text on --surface-ink #1a1a20           17.32:1   AAA
    Email hover/focus becomes the orange fill with ink text, i.e. it converges
    on the Call treatment rather than inventing a third state colour.
  Exactly one of the two is a filled brand action (Call), so the pair still
  reads as one primary + one subordinate (ui-ux-pro-max §4).

  STACKING: z-index 12. Above page content and above .side-widget (11), and
  deliberately BELOW .menu-grid (60), .menu-toggle (70) and the Bootstrap modal
  (1055) — so it can never cover the hamburger, the open off-canvas menu, or a
  dialog.
------------------------------------------------------------------------------*/
.eq-rail {
  --eq-label-w: 8.5rem;
  --eq-size: 56px;
  position: fixed;
  z-index: 12;
  top: 50%;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Parked so only the --eq-size icon block is on screen. */
  transform: translate3d(var(--eq-label-w), -50%, 0);
  transition: transform var(--duration-base) var(--ease-emphasis);
}
.eq-rail:hover,
.eq-rail:focus-within {
  transform: translate3d(0, -50%, 0);
}

.eq-rail__item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: calc(var(--eq-size) + var(--eq-label-w));
  min-height: var(--eq-size);
  padding-right: 1rem;
  background-color: var(--surface-ink);
  color: #fff;              /* 17.32:1 on ink */
  text-decoration: none;
  touch-action: manipulation;
  transition: background-color var(--duration-fast) ease-out,
              color var(--duration-fast) ease-out;
}
.eq-rail__item:first-child { border-top-left-radius: var(--radius-md); }
.eq-rail__item:last-child  { border-bottom-left-radius: var(--radius-md); }

/* The one filled brand action. INK on orange, never white on orange — white on
   #f95e1a is 3.16:1 and is the tempting mistake in this palette. */
.eq-rail__item--primary {
  background-color: var(--secondary-color);
  color: var(--black-color);   /* 5.48:1 */
}
.eq-rail__item:hover,
.eq-rail__item:focus-visible {
  background-color: var(--secondary-color);
  color: var(--black-color);
}
.eq-rail__item:focus-visible {
  outline: 3px solid var(--primary-color);
  outline-offset: -3px;   /* inset: the rail is flush to the viewport edge */
}

.eq-rail__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--eq-size);
  height: var(--eq-size);   /* 56x56 touch target on its own */
}
.eq-rail__icon svg { display: block; }

.eq-rail__label {
  flex: 0 0 auto;
  font-family: var(--secondary-font);
  font-size: 0.9375rem;   /* 15px */
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/*  BELOW 1280px THE RAIL BECOMES A BOTTOM BAR. THIS BREAKPOINT IS MEASURED,
    NOT GUESSED, AND MY FIRST ATTEMPT AT IT WAS WRONG.

    I first set it at 768px. Then I swept every page at 768 / 992 / 1024 /
    1152 / 1280, scrolling through each one and testing the rail's 56px
    collapsed strip against PAINTED TEXT RUNS (Range.getClientRects, not
    element boxes — a <p> box reaches the column edge even when its glyphs do
    not) and against every interactive control. Results:

      768px   overlapped body copy, portfolio thumbnails, .full-link cards
              AND a form <input> on /contact/
      992px   overlapped portfolio/gallery thumbnail links and /about/ copy
      1024px  overlapped thumbnail links and /service/branding/ copy
      1152px  overlapped thumbnail links and /service/branding/ copy
      1280px  CLEAR on all 12 page types at every scroll position

    The arithmetic agrees: the container is 1140px, so the content's right
    edge sits at (viewport - 1140)/2 + 1140. A 56px rail flush to the viewport
    edge only clears it from 1252px up. 1280 is the next standard breakpoint.

    So the right-edge rail is a >=1280px treatment and everything below it —
    phone AND tablet — gets a two-up bottom bar with both labels permanently
    visible. It is a fixed bar, so the page needs matching bottom clearance:
    that is added to .shock-footer below, not to <body>, because a body
    padding would show as a gap under the ink footer. */
@media (max-width: 1279.98px) {
  .eq-rail {
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    flex-direction: row;
    gap: 0;
    transform: none;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background-color: var(--surface-ink);
    box-shadow: 0 -2px 12px rgba(26, 26, 32, 0.25);
  }
  .eq-rail:hover,
  .eq-rail:focus-within { transform: none; }
  .eq-rail__item {
    flex: 1 1 50%;
    width: auto;
    min-height: 56px;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: 0 !important;
  }
  .eq-rail__icon { width: 24px; height: 24px; }
  .eq-rail__icon svg { width: 20px; height: 20px; }
  .eq-rail__label { font-size: 1rem; }
  /* CLEAR THE BAR — and this is the SECOND attempt, the first was wrong.
     I first put the clearance on `.shock-footer .bottom-bar`. Measured at
     375 and 414 on /blog/: the copyright line was still covered by 35px,
     because .bottom-bar centres its content, so a 72px padding-bottom only
     moves the text up ~36px. Putting the padding on .shock-footer instead
     adds the space BELOW the bottom bar, and because the footer is ink and
     the enquiry bar is ink, the join is seamless. Re-measured: clear at
     375 / 414 / 768 / 1024 / 1152 on /blog/, /contact/ and /. */
  .shock-footer {
    padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px));
  }
  /* And lift the back-to-top widget off it so neither covers the other. */
  .side-widget.to-right {
    height: calc(100% - 56px - env(safe-area-inset-bottom, 0px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .eq-rail,
  .eq-rail__item { transition: none !important; }
}

/*------------------------------------------------------------------------------
  12. THE ORANGE REBRAND — the handful of places that needed more than a token
  swap, because a value that was legible against green is not against orange.
------------------------------------------------------------------------------*/

/* The filled button's hover. Was a lighter green; now a lighter orange, which
   also RAISES the ink contrast (5.48 -> 6.35) rather than lowering it. */
.ed-btn--primary {
  background-color: var(--secondary-color);
  color: var(--black-color);          /* 5.48:1 */
}
.ed-btn--primary:hover,
.ed-btn--primary:focus-visible {
  background-color: var(--secondary-hover-color);   /* 6.35:1 with ink */
  color: var(--black-color);
}

/* Orange text on a light surface uses the DEEPEST variant, not --secondary-deep.
   On the orange tint --secondary-deep is 4.83:1 — a pass, but close enough to
   the 4.5 line that any future tint tweak would break it. Deepest is 6.42:1. */
.ed-eyebrow--secondary { color: var(--secondary-deepest); }
.ed-point__icon { color: var(--secondary-deepest); }   /* 6.42:1 on the tint */

/* THE BADGE. Pre-existing AA failure, surfaced by the audit and fixed here.
   Three states, all measured:

     resting outline badge   #8f93a5 on #fff       3.05:1  FAIL  -> #565a69, 6.86
     .active (accent fill)   #fff   on #60b988     2.39:1  FAIL  -> ink,     5.48
     :hover  (accent fill)   #fff   on #f95e1a     3.16:1  FAIL  -> ink,     5.48

   I CAUSED AND CAUGHT A REGRESSION HERE. My first pass repointed
   `.badge .badge-text.gray` to body colour without excluding the FILLED
   states, which put #565a69 on the orange fill at 2.17:1 — worse than the
   white it replaced. Measured on /portfolio/a-to-z/ and /<post>/, where the
   active chip is `.badge.outline.active`, not `.badge.secondary`. Hence the
   explicit .active / :hover selectors below and the :not() on the grey rule.
   Do not simplify this back into one selector. */
.badge.secondary .badge-text,
.badge.accent .badge-text,
.badge.active .badge-text,
.badge:hover .badge-text,
.link:hover .badge .badge-text {
  color: var(--black-color) !important;
}
.tag-cloud .badge:not(.active):not(:hover) .badge-text.gray,
.badge:not(.active):not(:hover) .badge-text.gray {
  color: var(--text-body-color);
}
/* ...except inside the ink footer, where the grey IS the accessible choice
   (#8f93a5 on #1a1a20 is 5.62:1 and #565a69 would be 2.53:1). */
.shock-footer .badge:not(.active):not(:hover) .badge-text.gray { color: var(--gray-color); }

/*------------------------------------------------------------------------------
  13. THE REMAINING PAGES  (2026-07-29 — /contact/, /gallery/, /blog/, posts,
  /portfolio/ detail, taxonomy archives, 404).

  Same system as §1-9. Nothing below introduces a new colour, a new type step
  or a new spacing value — it composes the tokens already locked in tokens.css.
------------------------------------------------------------------------------*/

/* The article <h1>. Only pages/post uses it: everywhere else the H1 is the
   .text-style-2 lockup. Sized to sit clearly above .ed-h2 without reaching the
   lockup's 83px. */
.ed-h1 {
  margin: 0 0 var(--space-1);
  font-family: var(--secondary-font);
  font-size: clamp(2rem, 1.4rem + 2.6vw, 3rem);
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.025em;
  color: var(--black-color);
}

/*  CONTACT — three tappable actions. Not decoration: these are the only
    above-the-fold routes to the phone and the inbox on the page whose entire
    job is starting a conversation. Values come from Settings. */
.ed-contact {
  display: grid;
  gap: var(--space-1);
  grid-template-columns: 1fr;
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 768px) { .ed-contact { grid-template-columns: repeat(3, 1fr); } }

.ed-contact__card {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  height: 100%;
  min-height: 96px;          /* well over the 44px touch minimum */
  padding: var(--space-15);
  background-color: #fff;
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--secondary-color);
  border-radius: var(--radius-lg, 12px);
  text-decoration: none;
  transition: transform var(--duration-fast) ease-out,
              box-shadow var(--duration-fast) ease-out;
}
.ed-contact__card:hover { transform: translateY(-2px); box-shadow: var(--shadow-small); }
.ed-contact__card:focus-visible { outline: 3px solid var(--primary-color); outline-offset: 3px; }
.ed-contact__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin-bottom: var(--space-05);
  border-radius: var(--radius-full);
  background-color: var(--surface-secondary);
  color: var(--secondary-deepest);   /* 6.42:1 on the tint */
}
.ed-contact__icon svg { display: block; }
.ed-contact__label {
  font-family: var(--secondary-font);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-body-color);     /* 6.86:1 */
}
.ed-contact__value {
  font-size: 1.0625rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--black-color);         /* 17.32:1 */
  overflow-wrap: anywhere;
}

/*  CONTACT — the form inside an editorial band. The field markup is the
    theme's, untouched, so only the things that were failing are repointed:
    the floating <label> was #8f93a5 (3.05:1, an AA failure on every field on
    the page) and the input underline was the legacy grey. */
.ed-form { margin-top: var(--space-2); }
.ed-form .form-label,
.ed-form .form-check-label { color: var(--text-body-color); }
.ed-form .form-control {
  color: var(--black-color);
  border-bottom-color: var(--gray-50-color);
}
.ed-form .form-control:focus {
  border-bottom-color: var(--secondary-deep);
  box-shadow: none;
}
.ed-form .form-control:focus-visible {
  outline: 3px solid var(--primary-color);
  outline-offset: 2px;
}
.ed-form .form-check { min-height: 44px; display: flex; align-items: center; gap: 0.5rem; }
.ed-form .form-check-input { margin-top: 0; }
.ed-form .sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.ed-rail__address {
  margin: 0 0 var(--space-1);
  font-style: normal;
  line-height: 1.6;
  color: var(--text-body-color);
}
.ed-rail__address strong { color: var(--black-color); }

/*  BLOG / ARCHIVE — the card grid. `align-items: stretch` plus a flex card
    body is what makes three cards with different image and title lengths line
    their meta rows up; the old Bootstrap columns did not. */
.ed-posts {
  display: grid;
  gap: var(--space-15);
  grid-template-columns: 1fr;
  align-items: stretch;
  margin-top: var(--space-2);
}
@media (min-width: 640px) { .ed-posts { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .ed-posts { grid-template-columns: repeat(3, 1fr); gap: var(--space-2); } }

/* Overrides the older .post-card block in overrides.css §6, which this
   supersedes. Kept as a separate selector rather than editing that file
   because overrides.css is the parity layer and must stay pixel-honest. */
.post-card.ed-post {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background-color: #fff;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg, 12px);
  box-shadow: 0 1px 2px rgba(26, 26, 32, 0.04);
  transition: transform var(--duration-fast) ease-out,
              box-shadow var(--duration-fast) ease-out;
}
.post-card.ed-post:hover { transform: translateY(-3px); box-shadow: var(--shadow-small); }

/* aspect-ratio reserves the frame before the image lands — CLS. */
.ed-post__media {
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background-color: var(--surface-neutral);
  border-bottom: 1px solid var(--border-subtle);
}
.ed-post__media img,
.ed-post__media picture {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-slow) ease-out;
}
.post-card.ed-post:hover .ed-post__media img { transform: scale(1.04); }

.ed-post__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: var(--space-15);
}
.post-card.ed-post .post-meta,
.ed-band .post-meta {
  margin: 0 0 var(--space-05);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-body-color);   /* was #8f93a5, 3.05:1 */
}
.ed-post__topic { color: var(--secondary-deepest); }  /* 7.11:1 on white */
.post-card.ed-post .post-title {
  margin: 0 0 var(--space-05);
  font-family: var(--secondary-font);
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1.3;
}
.post-card.ed-post .post-title a {
  color: var(--black-color);
  text-decoration: none;
}
.post-card.ed-post .post-title a:hover { text-decoration: underline; text-underline-offset: 3px; }
.post-card.ed-post .post-title a:focus-visible { outline: 3px solid var(--primary-color); outline-offset: 3px; }
.post-card.ed-post .post-excerpt {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--text-body-color);   /* was #8f93a5, 3.05:1 */
}

/*  ARTICLE / CASE STUDY HERO. Full band width, capped so a 1,600px source does
    not become a 1,140px-tall slab, and aspect-ratio'd so it reserves space. */
.ed-article__hero { margin: var(--space-2) 0 0; }
.ed-article__hero img,
.ed-article__hero picture {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg, 12px);
}
.ed-article__hero img {
  aspect-ratio: 16 / 9;
  object-fit: cover;
  max-height: 520px;
}

/*  PROJECT / PAGE GALLERY. Used by the portfolio detail band and, through
    .ed-tree, by whatever content-tree renders on /gallery/. */
.ed-gallery,
.ed-tree .gallery.has-gap {
  display: grid;
  gap: var(--space-1);
  grid-template-columns: repeat(2, 1fr);
  margin-top: var(--space-2);
}
@media (min-width: 640px) { .ed-gallery, .ed-tree .gallery.has-gap { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .ed-gallery, .ed-tree .gallery.has-gap { grid-template-columns: repeat(4, 1fr); } }

.ed-gallery__item,
.ed-tree .gallery.has-gap > * {
  display: block;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background-color: #fff;
}
.ed-gallery__item img,
.ed-gallery__item picture,
.ed-tree .gallery.has-gap img,
.ed-tree .gallery.has-gap picture {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-slow) ease-out;
}
.ed-gallery__item:hover { border-color: var(--secondary-color); }
.ed-gallery__item:hover img { transform: scale(1.04); }
.ed-gallery__item:focus-visible { outline: 3px solid var(--primary-color); outline-offset: 3px; }

/* content-tree emits Bootstrap columns inside .row wrappers; inside .ed-tree
   the grid above owns the layout, so the column widths are neutralised. */
.ed-tree .gallery.has-gap > [class*="col-"] { width: auto; max-width: none; padding: 0; flex: none; }
.ed-tree .row { --bs-gutter-x: 0; }

/*  404 — the recovery routes as real buttons rather than 13.6px grey chips. */
.ed-linklist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
}
.ed-linklist .ed-btn { min-height: 48px; padding: 0.75rem 1.5rem; }

/*  Pagination inside an editorial band. */
.ed-band .pagination-wrapper,
.ed-band nav[aria-label$="pagination"] { margin-top: var(--space-3); }

@media (prefers-reduced-motion: reduce) {
  .ed-contact__card,
  .post-card.ed-post,
  .ed-post__media img,
  .ed-gallery__item img { transition: none !important; }
  .ed-contact__card:hover,
  .post-card.ed-post:hover { transform: none !important; }
  .post-card.ed-post:hover .ed-post__media img,
  .ed-gallery__item:hover img { transform: none !important; }
}

/*==============================================================================
  13. THE HOMEPAGE EDITORIAL REGION  (added 2026-07-29)

  WHAT THIS REPLACES. Everything below the homepage's `.zzz` divider was
  partials/page-body: `body_html` poured into one `article.content` column
  inside a `col-md-8`. Measured on / at 1280px before the change:

      1,209 rendered words, 9 H2s, 26 H3s, 5,107px tall
      = 57% of the page's 9,020px height, in one grey column occupying the
        left 62% of the viewport with the right 38% empty
      15 links at the Bootstrap default #0d6efd (3.68:1 on white — a FAIL,
        and not a brand colour)

  The owner's objection was not the styling, it was the journey: a homepage
  is scanned, and that was a document. So the region is now four designed
  modules plus the CTA band, and roughly 40% of the copy is no longer
  rendered here because it already exists on /about/ and /our-services/.
  App\Support\HomeBody holds the routing table and the audit trail.

  NO NEW TOKENS. Every colour, radius, duration and space below is an
  existing custom property. The only literal values are grid track counts and
  the two breakpoints, which match the ones already used in §3 and §4.

  CONTRAST, measured against the surfaces each rule actually lands on
  (the matrix is in tokens.css):
      --black-color        on #fff / #f6f7f9 / #fdf1ea   17.32 / 16.24 / 14.92
      --text-body-color    on #fff / #f6f7f9 / #fdf1ea    6.86 /  6.44 /  5.91
      --secondary-deep     on #fff / #f6f7f9              5.36 /  5.03
      --secondary-deepest  on #fdf1ea                             6.33
  So a link on the orange tint uses DEEPEST, per the rule already stated in
  §12. There is no bright-orange text on any light surface anywhere below.
==============================================================================*/

/*------------------------------------------------------------------------------
  13a. THE OPENER — the two paragraphs that used to be the top of the wall.

  Two columns rather than one, because the first paragraph is the positioning
  statement and the second is the client list: they are different kinds of
  sentence and stacking them at the same size is what made them read as a
  document. The client list is set smaller behind a keyline, i.e. as a
  margin note.
------------------------------------------------------------------------------*/
.ed-opener {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 992px) {
  .ed-opener { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: var(--space-4); }
}
.ed-opener__lead > p {
  margin: 0;
  max-width: 30ch;
  font-family: var(--secondary-font);
  font-size: clamp(1.375rem, 1.05rem + 1.4vw, 2rem);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.015em;
  color: var(--black-color);
}
.ed-opener__lead > p + p { margin-top: var(--space-1); }
.ed-opener__note {
  padding-left: var(--space-15);
  border-left: 3px solid var(--secondary-color);
}
.ed-opener__note > p {
  margin: 0;
  font-size: 1rem;      /* never below 16px — iOS auto-zooms smaller body text */
  line-height: 1.6;
  color: var(--text-body-color);
}
.ed-opener__note > p + p { margin-top: var(--space-1); }
@media (max-width: 991px) {
  /* The 30ch cap is a desktop measure; at 375px it would break the statement
     into a ragged 4-word column. */
  .ed-opener__lead > p { max-width: none; }
}

/*------------------------------------------------------------------------------
  13b. THE SERVICE GRID.

  The eight service blocks were `h3 + p + bare blue link` in a prose column.
  They are the most clickable thing on the homepage and were the least
  visible. They are now the SAME `.card.double-edge.has-icon` tile
  /our-services/ uses — same component, same icons, same "VIEW MORE"
  affordance — deliberately not a second card pattern.

  The card component needs no Bootstrap column (it is `width:100%` + flex
  column in main.css), so it is dropped straight into a CSS grid and the
  gutters come from `gap`.

  NINE CELLS, NOT EIGHT. Eight tiles in a three-up grid leaves a two-card
  orphan row. The section's own closing link — "See all services", already in
  the copy — takes the ninth cell as a tinted tile, so the desktop grid is a
  clean 3x3 and the link stops being a stray line of text under the grid.
------------------------------------------------------------------------------*/
.ed-svc {
  display: grid;
  gap: var(--space-15);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) { .ed-svc { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .ed-svc { grid-template-columns: repeat(3, 1fr); gap: var(--space-2); } }
/* Grid items default to min-width:auto, which lets a long unbroken service
   name push the track wider than its share and overflow the band. */
.ed-svc > * { min-width: 0; }

/* main.css gives .card.double-edge a 2.25rem body. At three-up inside the
   1140px band that leaves a 271px text column, which is the /our-services/
   measure and is right there.

   MOBILE DENSITY IS A MEASURED FIX, NOT A PREFERENCE. Nine stacked tiles at
   the desktop chrome measured the region 6,827px tall at 375px, i.e. 283px
   TALLER than the prose wall it replaced — the copy got shorter but the tiles
   got taller. Trimming the chrome (padding, icon, and main.css's 1.75rem gap
   above VIEW MORE) and not the type — body copy stays at 16px, because below
   that iOS auto-zooms the page on focus — takes it back under. */
@media (max-width: 575px) {
  .ed-svc { gap: var(--space-1); }
  .ed-svc .card.double-edge .card-body { padding: 1.25rem; }
  .ed-svc .card.has-icon .title { margin-top: 0.625rem; }
  .ed-svc .card .title { margin-bottom: 0.5rem; }
  .ed-svc .card .card-image-icon { height: 38px; }
  .ed-svc .card .button-wrapper { margin-top: 0; padding-top: var(--space-1); }
}

/* .card is display:flex/column in main.css but .card-body is not a flex
   parent, so `margin-top:auto` on the button wrapper had nothing to push
   against and VIEW MORE sat directly under the copy. Observed at 1280px: the
   three baselines in the top row landed at 466 / 479 / 466px. */
.ed-svc .card .card-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: flex-start;
}
.ed-svc .card .card-body > .description { width: 100%; }
.ed-svc .card .description p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--text-body-color);
}
/* The rotated second edge is drawn by an absolutely positioned pseudo-element
   with no layout box, so a card whose text is short leaves its neighbours
   taller. Stretch is the grid default; this states it because the visual
   depends on it. */
.ed-svc .card { height: 100%; }
.ed-svc .card .button-wrapper { margin-top: auto; padding-top: var(--space-15); }
/* The whole tile is the target (main.css .full-link), so the focus ring has to
   be drawn on the card, not on a zero-size anchor. */
.ed-svc .card:focus-within { outline: 3px solid var(--primary-color); outline-offset: 6px; }

.ed-svc__more {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-05);
  min-height: 100%;
  padding: var(--space-2);
  border-radius: var(--radius-md);
  background-color: var(--surface-secondary);
  color: var(--black-color);          /* 14.92:1 on the tint */
  text-decoration: none;
  transition: transform var(--duration-fast) ease-out,
              box-shadow var(--duration-fast) ease-out;
}
/* The ninth cell only lands cleanly in the three-up grid. In the two-up
   range it is an orphan half-tile at the bottom left — observed at 768px — so
   it takes the whole row and reads as the section's closing action instead. */
@media (min-width: 576px) and (max-width: 991px) {
  .ed-svc__more { grid-column: 1 / -1; min-height: 0; padding: var(--space-15) var(--space-2); }
}
.ed-svc__more:hover { transform: translateY(-2px); box-shadow: var(--shadow-small); color: var(--black-color); }
.ed-svc__more:focus-visible { outline: 3px solid var(--primary-color); outline-offset: 3px; }
.ed-svc__more-title {
  font-family: var(--secondary-font);
  font-size: 1.1875rem;   /* 19px, the .card .title size */
  font-weight: 700;
  line-height: 1.3;
}
.ed-svc__more-title::after {
  content: " \2192";
  color: var(--secondary-deepest);    /* 6.33:1 on the tint */
}

/*------------------------------------------------------------------------------
  13c. CARDS — the self-select router and the reasons grid.

  ONE component with a flat modifier, not two, because the two modules differ
  in job (one routes, one reassures) and should not differ in vocabulary.

  `.ed-card__link` swallows the sentence stem the copy already had ("Start
  with SEO."), so the accessible name is "Start with SEO" rather than "SEO"
  and no word of the copy is dropped to make room for a button.
------------------------------------------------------------------------------*/
.ed-cards {
  display: grid;
  gap: var(--space-15);
  grid-template-columns: 1fr;
  align-items: stretch;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 768px) { .ed-cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1200px) { .ed-cards--4 { grid-template-columns: repeat(4, 1fr); gap: var(--space-2); } }
.ed-cards > * { min-width: 0; }

.ed-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-2);
  background-color: #fff;
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--secondary-color);
  border-radius: var(--radius-md);
  box-shadow: 0 1px 2px rgba(26, 26, 32, 0.04);
  transition: transform var(--duration-fast) ease-out,
              box-shadow var(--duration-fast) ease-out;
}
.ed-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-small); }

/* The reasons grid. No card chrome — four filled boxes next to the router's
   four filled boxes would read as eight of the same thing. A top keyline and
   the type carry it instead, which is the same device §4 uses for steps. */
.ed-card--flat {
  padding: var(--space-15) 0 0;
  background-color: transparent;
  border: 0;
  border-top: 2px solid var(--border-subtle);
  border-radius: 0;
  box-shadow: none;
}
.ed-card--flat:first-child { border-top-color: var(--secondary-color); }
.ed-card--flat:hover { transform: none; box-shadow: none; }

.ed-card__title {
  margin: 0 0 var(--space-05);
  font-family: var(--secondary-font);
  font-size: 1.1875rem;   /* 19px */
  font-weight: 700;
  line-height: 1.3;
  color: var(--black-color);
}
.ed-card__body { margin: 0; font-size: 1rem; line-height: 1.6; color: var(--text-body-color); }
.ed-card__body + .ed-card__body { margin-top: var(--space-1); }

/* Block + fit-content, NOT inline-flex. As a flex row the arrow was a
   separate flex item, so when the label wrapped ("Start with digital
   marketing / strategy" at 768px) the arrow was left stranded on its own line.
   As a block with an nbsp-glued ::after it stays welded to the last word. */
.ed-card__link {
  display: block;
  width: fit-content;
  max-width: 100%;
  min-height: 44px;       /* touch target */
  margin-top: auto;
  padding-top: var(--space-1);
  line-height: 1.4;
  font-family: var(--secondary-font);
  font-size: 1rem;
  font-weight: 700;
  color: var(--secondary-deep);       /* 5.36:1 on white, 5.03:1 on #f6f7f9 */
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 1px;
}
.ed-card__link::after { content: "\00a0\2192"; }
.ed-card__link:hover { color: var(--secondary-deepest); text-decoration-thickness: 2px; }
.ed-card__link:focus-visible { outline: 3px solid var(--primary-color); outline-offset: 3px; }
/* On the orange tint --secondary-deep is 4.83:1 — a pass, but §12 already
   ruled that too close to the line, so the tint gets the deepest variant. */
.ed-band--secondary .ed-card__link { color: var(--secondary-deepest); }

/* The line that closes the router ("If none of those is you, ring us…").
   Section-level, not card-level — see the structural rule in HomeBody. */
.ed-cards__outro {
  margin: var(--space-2) 0 0;
  max-width: 60ch;
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--black-color);
}

@media (prefers-reduced-motion: reduce) {
  .ed-card,
  .ed-svc__more { transition: none !important; }
  .ed-card:hover,
  .ed-svc__more:hover { transform: none !important; }
}

/* ── Reveal safety net ────────────────────────────────────────────────
   The homepage service grid shipped invisible: cards carried .aos-animate
   yet computed opacity stayed 0, with every ancestor at opacity 1. The
   vendor rule matched and did not apply. Content visibility must never
   depend on an animation library succeeding, so the editorial bands opt
   out of AOS entirely. Motion here was decoration; the section is not. */
.ed-svc > [data-aos],
.ed-cards > [data-aos],
.ed-band [data-aos] {
  opacity: 1 !important;
  transform: none !important;
}

/*==============================================================================
  14. TAP TARGETS, FOCUS ORDER AND HOVER CONTRAST  (2026-07-30)

  Three verified mobile/accessibility defects, fixed here rather than in
  main.css/overrides.css so the whole change set is one reviewable block that
  can be reverted in one cut. This file loads last (partials/head.blade.php),
  so none of it needs !important.

  Everything below was measured with getBoundingClientRect() PLUS
  document.elementFromPoint() at the box corners, at 375 / 390 / 768. That
  matters: an inline <a> reports only its own line box, so a rect-only audit
  understates any target whose real hit surface is a taller child. Two of the
  reported defects turned out to be exactly that and are NOT touched here —
  see the notes at (2b).
==============================================================================*/

/*------------------------------------------------------------------------------
  14.1  Back-to-top widget: off-screen but still in the tab order.
  WCAG 2.4.7 / 2.4.3.

  .side-widget.to-right is a real <a href="#header">. main.js §"Side Widget"
  adds .show once pageYOffset exceeds one viewport height; until then the
  widget is parked at translateX(100px), i.e. entirely outside the viewport
  (measured left/right: 413/459 at 375, 428/474 at 390, 806/852 at 768) while
  still display:block and focusable. Tab from the top of any page and focus
  lands on an invisible link.

  Not fixed by hiding it below desktop (the pattern its .to-left sibling uses),
  because a mobile back-to-top IS wanted here: §11 above deliberately shortens
  this widget by the height of the enquiry rail so the two do not collide below
  992px. Hiding it would undo that decision. Also not fixed with aria-hidden,
  which on a focusable element is its own violation.

  MEASURED WHILE FIXING THIS, and flagged rather than fixed: the widget never
  actually appears. main.js binds on `$('.shock-body .side-widget')`, and
  nothing in this app puts `shock-body` on <body> — layouts/app.blade.php emits
  a bare `<body>`, and that string occurs nowhere else in the CSS, the Blade
  tree or the rest of main.js. jQuery matches 0 elements, so the scroll handler
  is never bound and .show is never added: at 375 / 768 / 1280, scrolled to
  2000px, the widget still computes transform matrix(1,0,0,1,100,0) and sits at
  left 413 / 806 / 1318. So today it is a dead link that nobody can click and
  only a keyboard user can reach. Making it work means adding a class to <body>,
  which would newly float a button over every page — a design decision, not a
  defect fix, so it is not made here. The rule below is written against .show
  so it does the right thing the moment that is corrected.

  visibility:hidden is the fix: it removes the element from the tab order and
  from hit-testing, is inherited by the icon inside it, costs no geometry
  change (the element is position:fixed and already off-screen), and is undone
  by exactly the same class the animation already keys off.

  The `visibility 0s linear 1s` delay is load-bearing: without it the widget
  would vanish instantly instead of sliding out, because visibility is not an
  animatable property in the interpolating sense.
------------------------------------------------------------------------------*/
.side-widget.to-right {
  visibility: hidden;
  transition:
    transform 1s cubic-bezier(0.2, 1, 0.20, 1),
    visibility 0s linear 1s;
}
.side-widget.to-right.show {
  visibility: visible;
  transition:
    transform 1s cubic-bezier(0.2, 1, 0.20, 1),
    visibility 0s linear 0s;
}

@media (prefers-reduced-motion: reduce) {
  /* overrides.css §5 zeroes durations globally; keep the visibility flip
     immediate in both directions so the widget cannot linger focusable. */
  .side-widget.to-right,
  .side-widget.to-right.show { transition: none; }
}

/*------------------------------------------------------------------------------
  14.2  Standalone CTA links in editorial prose: 44px vertical hit area.

  These are plain <a> in a <p> inside .ed-prose — the conversion links on
  /our-services/, the service pages, /about/ and /gallery/. Measured 19px tall
  (16px/1.6 line box), the same height as the body copy around them.

  `padding-block` on an element that is still display:inline is the one way to
  do this with a mathematically guaranteed zero reflow: per CSS 2.1 §10.6.1 the
  vertical padding of a non-replaced inline box does not contribute to the
  height of its line box. Nothing moves; only the element's client rects grow.
  Confirmed by measuring every <p>, <h2> and section top on the pages below
  before and after — identical to 0.1px.

  Scoped to `a:only-child` so a link that is genuinely inside a sentence is
  left alone. Those are exempt anyway (WCAG 2.5.8 "in-line" exception: a
  target whose size is constrained by the line-height of surrounding non-target
  text), and growing them would have overlapped neighbouring lines.

  19 + 25 = 44. Paragraph margins here are 16px plus ~3.3px of half-leading on
  each side, so the grown box stays inside the paragraph's own whitespace and
  does not reach any other target. Verified: zero overlapping link pairs on all
  eight audited pages at all three widths.

  <td> is included because the service comparison table on /our-services/ has
  the same one-link-per-cell shape at 69px row pitch, which leaves clearance.
  <li> is deliberately NOT included: list items are ~30px apart there, so a
  44px box would overlap its neighbour and start stealing taps.
------------------------------------------------------------------------------*/
.ed-prose p > a:only-child,
.ed-prose td > a:only-child {
  padding-block: 12.5px;
}

/* /gallery/ writes the same CTA as `<p>sentence<br><a>Branding services →</a></p>`,
   so the anchor is not :only-child and the rule above missed all five of them
   on the first pass (measured 142.7x19 .. 202.1x19 after that pass, which is
   how this was caught). `br + a:last-child` is the same shape — a link alone on
   the last line of a paragraph — and nothing else in the corpus matches it.
   Here the 12.5px top padding does reach up over the ~6.6px gap into the
   sentence above, but that line is plain text, not a second target: the
   target-pair overlap scan is still 0 new overlaps on every audited page. */
.ed-prose p > br + a:last-child {
  padding-block: 12.5px;
}

/*------------------------------------------------------------------------------
  14.2b  Hero slide CTA ("Get in Touch" / "Get Started" / "Leave a Message").

  22.8px tall (15.2px/1.5). This one is display:flex, not inline, so the
  inline-padding trick does not apply and the padding has to be paid back with
  an equal negative margin. 22.8 + 22 = 44.8; margin-block -11px returns the
  outer height to 22.8 so the flex/block context around it is unchanged and the
  glyphs stay on the same baseline.

  NOT CHANGED, and deliberately: the slider's PREV / NEXT links, reported as
  103x20 and 100x20. Those numbers are the inline <a>'s own line box. The
  <span class="arrow-button"> inside each one already carries `padding: 20px 0`
  and measures 100x44, and document.elementFromPoint() at all four corners of
  that 44px box returns a node inside the anchor at 375, 390 and 768. The tap
  target is already 100x44; there is nothing to fix and padding here would have
  moved the arrow lockup.
------------------------------------------------------------------------------*/
.dynamic-slider .slide-item .slide-action-link {
  padding-block: 11px;
  margin-block: -11px;
}

/*------------------------------------------------------------------------------
  14.3  Footer, shared partial — nav rows and service chips.

  Rows measured 32px (first/last, whose outer padding main.css zeroes) and 40px
  (all others), against a 24px line box + 2 x 8px padding. The rows are
  CONTIGUOUS — there is no gap to borrow from — so 44px targets here cannot be
  free. 10px padding on every row gives 44, and `margin-block: -10px` on the
  <ul> hands 10px back at each end of the list, which pins the FIRST row's text
  to its original y. The cost is real and is stated in the report: a 5-item
  list grows 184 -> 200px, and rows 2..5 move down 4/8/12/16px. There is no
  arrangement of five 44px targets that fits in 184px, so this is the floor,
  not a shortcut.

  The <address> row is already 68px (two lines) and only needs the padding for
  consistency.
------------------------------------------------------------------------------*/
.shock-footer .footer-content .nav-list .nav-link {
  padding-block: 10px;
}
.shock-footer .footer-content .nav-list .nav-item:first-child .nav-link {
  padding-top: 10px;
}
.shock-footer .footer-content .nav-list .nav-item:last-child .nav-link {
  padding-bottom: 10px;
}
.shock-footer .footer-content .nav-list {
  margin-block: -10px;
}

/* Service chips. The visible pill is .badge at 29px and that pill is the real
   hit surface (the wrapping <a> is inline and reports 19px). 29 -> 44 needs
   15px, and the rows are only 5px apart, so an ::after overlay would have
   overlapped the row above and below by 10px and started mis-routing taps.
   Instead the <a> becomes a 44px-tall invisible box centred on the unchanged
   29px pill; row pitch goes 34 -> 44px. The pill itself is not resized, so the
   chip's visual weight is identical. */
.shock-footer .tag-cloud .link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* Measured consequence of the line above: the pill's offset from the tag
   cloud's own content top went 2.5px -> 7.5px, i.e. the first row of chips
   dropped 5px away from the "Our Services" heading. -5px hands that back, and
   also trims 10px off the block's growth (102 -> 122px for three rows at 375,
   rather than 132). Rows 2..n still move down 10px each — the pills are 34px
   apart and 44px targets cannot be packed at 34px pitch. */
.shock-footer .tag-cloud {
  margin-block: -5px;
}

/* Bottom bar. 97x16 — the shortest target in the footer, and the cheapest to
   fix: the <a> is inline inside a centred <div class="text"> with no other
   target within 100px, so the same zero-reflow inline padding as 14.2 applies.
   16 + 28 = 44. */
.shock-footer .bottom-bar .text a {
  padding-block: 14px;
}

/*------------------------------------------------------------------------------
  14.4  Pagination arrows and page numbers: 40x40 -> 44x44.  (LOW)

  /portfolio_category/branding/ etc. These are a measured design element — a
  40px chip with a 1px border that becomes a filled chip on hover and on the
  current page — so growing the box would grow the visible chip. An inset
  ::after overlay adds the missing 4px to the hit area and paints nothing.
  Chips are 8px apart, so the two overlays leave 4px between them and never
  overlap.

  NOT `inset: -2px`, which is what this was on the first pass and which
  measured 42x42, not 44x44: an absolutely positioned box is laid out against
  its containing block's PADDING box, and .page-link is border-box with a 1px
  border, so the offsets started from 38x38 and -2px each side only reached 42.
  Centring a min-44px box on the chip instead is exact and does not depend on
  the border width, and it degrades correctly if a chip is ever wider than 44px
  (a three-digit page number): width:100% then wins over min-width and the
  overlay tracks the chip rather than shrinking it.
------------------------------------------------------------------------------*/
.pagination-list .page-link {
  position: relative;
}
.pagination-list .page-link::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
}

/*------------------------------------------------------------------------------
  14.5  Slider arrow label contrast on hover.  (LOW)

  The reported figure (PREV 3.11:1, NEXT 17.32:1) is real but the diagnosis was
  not: it is the computed `color` property, which is NOT what these glyphs
  render as. .arrow-button .text paints through
  `background-clip: text` + `-webkit-text-fill-color: transparent`, so the
  visible colour is whichever half of a 200%-wide two-stop gradient the current
  background-position exposes.

  Pixel-sampled the rendered glyphs on / at 375 (darkest pixel inside each
  label's rect, ancestor background walked to body = rgb(255,255,255)):

      state        PREV                     NEXT
      default      rgb(26,26,32)  17.32:1   rgb(25,25,32)  17.48:1
      hovered      rgb(80,155,188) 3.11:1   rgb(79,154,188) 3.14:1

  So the default state passes on BOTH and the hover state fails on BOTH — it is
  symmetric, not a per-side inconsistency. rgb(80,155,188) is #509bbc, i.e.
  --primary-color, which tokens.css measures at 3.16:1 on white and reserves
  for accents, never for text on a light surface. The token for that job
  already exists: --primary-deep #2d6076, 7.04:1 on white, 6.8:1 on the
  #f6f7f9 card surface the .cross variant sits on.

  Only the gradient stop that the hover state exposes is repointed, so the
  default rendering is byte-identical. Scoped to .scheme-1 (the light-surface
  variant, where --color-1 is ink); .scheme-2 arrow buttons sit on ink, where
  #509bbc still clears 4.5:1, and are untouched.

  `color` is set too. It is dead in every browser that honours
  -webkit-text-fill-color, but it is the fallback if background-clip:text is
  not applied, and that fallback was the failing blue.
------------------------------------------------------------------------------*/
.arrow-button.scheme-1 .text {
  background-image: linear-gradient(
    to right,
    var(--primary-deep),
    var(--primary-deep) 50%,
    var(--color-1) 50%
  );
  color: var(--color-1);
}
.arrow-button.scheme-1.prev .text {
  background-image: linear-gradient(
    to right,
    var(--color-1),
    var(--color-1) 50%,
    var(--primary-deep) 50%
  );
  color: var(--primary-deep);
}
