/* ==========================================================================
   Local SEO Spider — custom styles
   --------------------------------------------------------------------------
   Every value here was read from the live site's computed styles, not
   estimated. Source notes live in DESIGN-TOKENS.md.

   CONTENTS
     1. Design tokens
     2. Web fonts
     3. Base / typography
     4. Layout helpers
     5. Header
     6. Mobile nav modal
     7. Get Started modal
     8. Forms
     9. Footer
    10. Accessibility
   ========================================================================== */

/* ==========================================================================
   1. DESIGN TOKENS
   ========================================================================== */
:root {
  /* ==========================================================================
     DESIGN SYSTEM — v2
     Brief: clean corporate SaaS. Restrained, neutral-led, data-forward.

     What changed and why:
       - Type: Quicksand (rounded, soft) and Intro Black (900-weight display)
         are gone. Rounded + very heavy reads consumer; this is sold to
         business owners on technical competence. Inter throughout.
       - Colour: was monochrome violet with no neutral to rest on. Now a slate
         ramp carries 90% of the site and violet appears only as an accent.
         The slate is blue-tinted so it sits with the logo's #2C3966 navy, and
         the accent is a saturated relative of the logo's #918BC3 lilac, so
         the mark still belongs.
     ========================================================================== */

  /* --- Neutrals: the logo's navy, not Tailwind slate ----------------------- */
  --ls-ink:        #141B33;   /* headings, dark grounds  17.0:1 on white */
  --ls-ink-soft:   #232E52;   /* the logo navy, deepened                 */
  --ls-navy:       #2C3966;   /* the logo navy itself     11.2:1         */
  --ls-text:       #4A5578;   /* body                      7.3:1         */
  --ls-muted:      #5F688C;   /* meta, captions   5.5:1 white, 4.8:1 tint */
  --ls-border:     #E3E5EF;   /* hairlines                               */
  --ls-border-mid: #C7CBDD;   /* inputs, stronger rules                  */
  --ls-surface:    #F7F8FC;   /* section bands                           */
  --ls-surface-2:  #EFF1F8;   /* nested surfaces                         */
  --ls-accent:     #FFFFFF;   /* kept: the old name for white            */

  /* --- Brand accent: the logo's periwinkle ---------------------------------
     #918BC3 straight from the mark is only 3.1:1 on white, so it cannot carry
     text. It survives as --ls-brand-light on dark grounds (7.2:1), and the
     on-white accent is the same hue taken down until it passes. */
  --ls-brand:       #5B51A0;  /* links, buttons, active    6.7:1 on white */
  --ls-brand-dark:  #4C4290;  /* hover                     8.4:1          */
  --ls-brand-light: #A9A3D8;  /* accents on dark           7.2:1 on ink   */
  --ls-brand-soft:  #EEEDF7;  /* tint behind icons, chips                 */
  --ls-brand-line:  #D6D3EC;  /* accent-tinted border                     */

  /* Legacy aliases so nothing breaks mid-migration. These map the old violet
     names onto the new system and will be removed as pages are rebuilt. */
  --ls-primary:      var(--ls-brand);
  --ls-primary-deep: var(--ls-brand-dark);
  --ls-lilac-tint:   var(--ls-brand-soft);
  --ls-secondary:    var(--ls-ink);
  --ls-rule:         var(--ls-border);
  --ls-periwinkle:   var(--ls-border-mid);
  --ls-violet-950: var(--ls-ink);
  --ls-violet-900: var(--ls-ink);
  --ls-violet-800: var(--ls-ink-soft);
  --ls-violet-700: var(--ls-brand-dark);
  --ls-violet-600: var(--ls-brand);
  --ls-violet-500: var(--ls-brand);
  --ls-violet-400: var(--ls-brand);
  --ls-violet-200: var(--ls-brand-line);
  --ls-violet-100: var(--ls-brand-soft);
  --ls-violet-50:  var(--ls-surface);

  /* Dark band: the logo navy, deep to mid. */
  --ls-band: linear-gradient(180deg, #141B33 0%, #232E52 100%);

  /* Colour — alpha */
  --ls-shadow-navy-20:  rgba(20, 27, 51, 0.20);
  --ls-shadow-navy-10:  rgba(20, 27, 51, 0.10);
  --ls-shadow-black-20: rgba(0, 0, 0, 0.20);
  --ls-overlay:         rgba(20, 27, 51, 0.72);

  /* --- Type ---------------------------------------------------------------- */
  --ls-font-body:    "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --ls-font-heading: var(--ls-font-body);
  --ls-font-form:    var(--ls-font-body);
  --ls-font-mono:    ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --ls-fw-body:    400;
  --ls-fw-medium:  500;
  --ls-fw-bold:    600;   /* was 700 */
  --ls-fw-heading: 650;   /* was 900 — the single biggest change in tone */

  /* Tighter, flatter scale. The old one topped out at 68px; SaaS pages carry
     authority with restraint, not size. */
  --ls-h1:      clamp(2.125rem, 1.6rem + 2.1vw, 3.25rem);
  --ls-h2:      clamp(1.5rem, 1.28rem + 0.9vw, 2.125rem);
  --ls-h3:      clamp(1.125rem, 1.05rem + 0.3vw, 1.3125rem);
  --ls-h4:      clamp(1.25rem, 1.1rem + 0.6vw, 1.625rem);
  --ls-display: clamp(1.75rem, 1.4rem + 1.5vw, 2.625rem);
  --ls-body:    clamp(0.9375rem, 0.91rem + 0.12vw, 1rem);

  --ls-lh-tight: 1.12;
  --ls-lh-head:  1.25;
  --ls-lh-body:  1.65;
  --ls-track-xl: -0.022em;
  --ls-track-lg: -0.014em;
  --ls-track-eyebrow: 0.06em;

  /* --- Space --------------------------------------------------------------- */
  --ls-space-1:  4px;
  --ls-space-2:  8px;
  --ls-space-3: 12px;
  --ls-space-4: 16px;
  --ls-space-5: 24px;
  --ls-space-6: 32px;
  --ls-space-7: 48px;
  --ls-space-8: 64px;
  --ls-section: clamp(64px, 7vw, 112px);

  /* Height of the trust bar. The hero subtracts it, the bar claims it, so the
     two together come to exactly one viewport. */
  --ls-trustbar:      150px;
  --ls-container:     1200px;
  --ls-container-pad: clamp(20px, 4vw, 32px);
  --ls-header-h:      68px;

  /* Flatter radii — big rounded corners read friendly, not precise. */
  --ls-radius-pill: 999px;
  --ls-radius-lg:   12px;
  --ls-radius-md:    10px;
  --ls-radius-sm:    6px;

  /* Shadows are for things that genuinely float. Cards use hairlines. */
  --ls-shadow-card: 0 1px 2px rgba(20, 27, 51, 0.04);
  --ls-shadow-soft: 0 1px 2px rgba(20, 27, 51, 0.05);
  --ls-shadow-drop: 0 20px 48px rgba(20, 27, 51, 0.16);

  --ls-transition: all 0.4s ease;
  --ls-ease: 160ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ==========================================================================
   2. WEB FONTS
   Inter, self-hosted, variable (400–700 in one file). Replaces Intro (900
   display) and Quicksand (rounded body) — see the note in the token block.
   ========================================================================== */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ==========================================================================
   3. BASE / TYPOGRAPHY
   ========================================================================== */
html { scroll-behavior: smooth; }

body {
  font-family: var(--ls-font-body);
  font-size: var(--ls-body);
  font-weight: var(--ls-fw-body);
  line-height: var(--ls-lh-body);
  color: var(--ls-text);
  background-color: var(--ls-accent);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings sit in navy, not lilac.
   The original put every heading in #918BC3 on white. That is 2.9:1 - under
   the readable threshold at any size below 24px, and the single biggest reason
   the page looked faded. Lilac keeps its job as the ACCENT: the second half of
   a two-tone title, eyebrows, icons, active states. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--ls-font-heading);
  font-weight: var(--ls-fw-heading);
  line-height: var(--ls-lh-head);
  color: var(--ls-ink);
}

/* The two-tone heading treatment is kept, with the roles reversed: navy
   carries the line, lilac highlights the second half. */
/* The old design recoloured the second half of every heading. Doing it on
   every single heading made it a tic rather than emphasis, so spans now
   inherit. Use .u-accent where a highlight is genuinely wanted. */
h1 span, h2 span, h3 span, h4 span { color: inherit; }
.u-accent { color: var(--ls-brand); }

/* Large display type needs negative tracking. A 900-weight face set at 68px
   with default spacing is the loosest thing on the old page. */
h1 { font-size: var(--ls-h1); line-height: var(--ls-lh-tight); letter-spacing: var(--ls-track-xl); }
h5, h6 { font-size: 1rem; letter-spacing: var(--ls-track-lg); }
h2 { font-size: var(--ls-h2); letter-spacing: var(--ls-track-lg); }
h3 { font-size: var(--ls-h3); letter-spacing: var(--ls-track-lg); }
h4 { font-size: var(--ls-h4); letter-spacing: var(--ls-track-lg); }

/* Long words wrap rather than overflow. overflow-wrap is the current property;
   word-break: break-word is a non-standard alias kept alive for compatibility. */
h1, h2, h3, h4, h5, h6, a, p { overflow-wrap: break-word; }

a { color: var(--ls-brand); text-decoration: none; transition: color var(--ls-ease); }
a:hover { color: var(--ls-brand-dark); }

/* ==========================================================================
   4. LAYOUT HELPERS
   The site container is "100% wide, capped at 1260px, 15px each side".
   Bootstrap's .container is compiled to match, so this is only for sections
   that need the cap without Bootstrap's row/col machinery.
   ========================================================================== */
/* The header is fixed and opaque, so it takes no space in the flow. This
   reserves it for every page. */
main { padding-top: var(--ls-header-h); }

/* ...except when the page OPENS with a full-bleed coloured band. There the
   clearance produced a white strip between the header and the band - 82px of
   it on /services/. The band is pulled back up under the header and takes the
   clearance as its own top padding instead, so the colour runs to the header
   edge while the text still clears it. */
main > .page-hero:first-child,
main > .archive-hero:first-child,
main > .post > .post__hero {
  margin-top: calc(var(--ls-header-h) * -1);
  padding-top: calc(var(--ls-header-h) + var(--ls-space-7));
}

.ls-container {
  width: 100%;
  max-width: var(--ls-container);
  margin-inline: auto;
  padding-inline: var(--ls-container-pad);
}

/* ==========================================================================
   5. HEADER
   Fixed and transparent over the hero, turning solid white once scrolled.
   ========================================================================== */
/* The header used to be transparent over the hero, with pale lilac links. The
   hero backdrop is a solid #918BC3 slab, so those links ran at about 2.1:1 -
   effectively unreadable. A solid bar with a hairline that appears on scroll is
   both legible and the current convention. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 999;
  background-color: var(--ls-accent);
  border-bottom: 1px solid transparent;
  padding-inline: var(--ls-container-pad);
  transition: border-color var(--ls-ease);
}
.site-header__inner {
  width: 100%;
  max-width: var(--ls-container);
  margin-inline: auto;
  min-height: var(--ls-header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.site-header__logo img { width: 224px; height: auto; max-width: 100%; display: block; }

/* Primary nav */
.site-nav__list { display: flex; align-items: center; margin: 0; padding: 0; list-style: none; }
.site-nav__link {
  position: relative;
  display: block;
  padding: 10px 16px;
  font-family: var(--ls-font-body);
  font-size: 1rem;
  /* Weight is fixed in every state on purpose. The old rule swapped 400 -> 700
     on hover, which re-flowed the entire nav under the cursor. */
  font-weight: var(--ls-fw-medium);
  color: var(--ls-ink);
  transition: color var(--ls-ease);
}
.site-nav__link:hover,
.site-nav__link:focus-visible { color: var(--ls-primary-deep); }

/* Current page is marked with a rule rather than a weight change, so the nav
   keeps its metrics. */
.site-nav__link[aria-current="page"] { color: var(--ls-primary-deep); }
.site-nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 16px; right: 16px; bottom: 0;
  height: 2px;
  border-radius: 2px;
  background-color: var(--ls-primary);
}

/* Scrolled state - the bar is already white, so all that changes is the
   hairline appearing beneath it. */
.site-header.is-stuck { border-bottom-color: var(--ls-border); }

/* Service dropdown */
.site-nav__item--has-children { position: relative; }
.site-nav__submenu {
  /* Anchored to the right: the nav is right-aligned, so a left-anchored
     320px panel overflows the viewport and forces a horizontal scrollbar. */
  position: absolute; top: 100%; right: 0; left: auto; min-width: 220px;
  margin: 0; padding: 8px; list-style: none;
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-md);
  box-shadow: var(--ls-shadow-card);
  opacity: 0; visibility: hidden; transform: translateY(6px);
  transition: opacity var(--ls-ease), transform var(--ls-ease), visibility var(--ls-ease);
}
.site-nav__item--has-children:hover > .site-nav__submenu,
.site-nav__item--has-children:focus-within > .site-nav__submenu {
  opacity: 1; visibility: visible; transform: translateY(0);
}
.site-nav__sublink {
  display: block; padding: 10px 14px;
  border-radius: var(--ls-radius-sm);
  font-family: var(--ls-font-body); font-size: 0.9375rem; font-weight: var(--ls-fw-medium);
  color: var(--ls-text);
  transition: color var(--ls-ease), background-color var(--ls-ease);
}
.site-nav__sublink:hover,
.site-nav__sublink:focus-visible { color: var(--ls-ink); background-color: var(--ls-surface); }

/* Hamburger — hidden on desktop, shown at the tablet-portrait edge (<=1024px) */
.site-header__burger {
  display: none;
  align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  border: 1px solid var(--ls-border); border-radius: var(--ls-radius-sm);
  background-color: var(--ls-accent); color: var(--ls-ink);
  cursor: pointer;
  transition: border-color var(--ls-ease), color var(--ls-ease);
}
.site-header__burger:hover { border-color: var(--ls-primary); color: var(--ls-primary-deep); }
.site-header__burger svg { width: 18px; height: 18px; fill: currentColor; display: block; }

@media (max-width: 1024px) {
  .site-header__nav { display: none; }
  .site-header__burger { display: inline-flex; }
  .site-header__logo img { width: 190px; }
}

/* ==========================================================================
   6. MOBILE NAV MODAL  (replaces Elementor popup #1043)
   ========================================================================== */
.ls-mobile-nav .modal-content {
  position: relative;
  isolation: isolate;
  border: 0; border-radius: 0;
  background-color: var(--ls-ink);
  background-image: var(--ls-band);
  padding: 0;
  overflow-y: auto;
}

/* Corner web, same motif as the service heroes. */
.ls-mobile-nav__web {
  position: absolute;
  top: 0; right: 0;
  z-index: 0;
  width: 240px; height: 240px;
  color: var(--ls-brand-light);
  opacity: 0.22;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(circle at 100% 0%, #000 30%, transparent 82%);
          mask-image: radial-gradient(circle at 100% 0%, #000 30%, transparent 82%);
}
.ls-mobile-nav__web line,
.ls-mobile-nav__web path {
  fill: none; stroke: currentColor; stroke-width: 1;
  stroke-linecap: round; vector-effect: non-scaling-stroke;
}

/* Hanging in the clear band between the link text and the row arrows -
   measured at 390px: text ends by 55%, arrows sit at ~88%, so 72% is empty
   down the whole list. The web's lower edge is 240px, so 236px reads as the
   spider having just dropped out of it. */
.ls-mobile-nav__thread {
  position: absolute;
  left: 72%;
  top: 0;
  width: 1px;
  height: 236px;
  z-index: 0;
  background: linear-gradient(180deg,
    rgba(169, 163, 216, 0) 0%,
    rgba(169, 163, 216, 0.5) 30%,
    rgba(169, 163, 216, 0.7) 100%);
  pointer-events: none;
}
.ls-mobile-nav__spider {
  position: absolute;
  left: 72%;
  top: 236px;
  z-index: 0;
  width: 84px;
  height: auto;
  transform: translate(-50%, -4%);
  overflow: visible;
  pointer-events: none;
  filter: drop-shadow(0 0 12px rgba(145, 139, 195, 0.4));
}
/* Original logo colours, as on the homepage. */
.ls-mobile-nav__spider .sp-body { fill: #2C3966; }
.ls-mobile-nav__spider .sp-legs { fill: #918BC3; }
.ls-mobile-nav__spider .sp-eye  { fill: #FFFFFF; }
.ls-mobile-nav__spider .sp-pale { fill: #E0DEF0; }

/* Very short screens compress the list; the thread would then cross text. */
@media (max-height: 620px) {
  .ls-mobile-nav__thread, .ls-mobile-nav__spider { display: none; }
}

.ls-mobile-nav__top {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--ls-space-4);
  padding: var(--ls-space-5) var(--ls-container-pad);
  border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
/* Inlined and recoloured rather than filtered. brightness(0) invert(1) is a
   silhouette: it would flatten the spider's eyes into its body. */
.ls-mobile-nav__logo { display: block; }
.logo-light { display: block; width: auto; height: 32px; }

/* Wordmark: the navy half goes white, the periwinkle half stays an accent. */
.logo-light .lg-wm-dark   { fill: #FFFFFF; }
.logo-light .lg-wm-accent { fill: var(--ls-brand-light); }

/* Spider: lifted off the band, but keeping its own tonal steps so the eyes
   still read as eyes. */
.logo-light .lg-sp-dark   { fill: var(--ls-brand-light); }
.logo-light .lg-sp-accent { fill: #C9C5E8; }
.logo-light .lg-sp-light  { fill: #FFFFFF; }
.logo-light .lg-sp-pale   { fill: #E0DEF0; }
.ls-mobile-nav__close {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  margin: 0; padding: 0;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.08);
  /* Bootstrap's close glyph is a dark SVG background; invert it for the band. */
  filter: invert(1) grayscale(1) brightness(2);
  opacity: 1;
}
.ls-mobile-nav__close:focus-visible { outline: 3px solid var(--ls-brand-light); outline-offset: 2px; }

.ls-mobile-nav .modal-body {
  position: relative; z-index: 1;
  display: flex; flex-direction: column;
  gap: var(--ls-space-5);
  padding: var(--ls-space-6) var(--ls-container-pad) var(--ls-space-8);
}

.ls-mobile-nav__list { list-style: none; margin: 0; padding: 0; }
.ls-mobile-nav__list > li + li { border-top: 1px solid rgba(255, 255, 255, 0.08); }

.ls-mobile-nav__link {
  display: flex; align-items: center; justify-content: space-between;
  padding: 15px 0;
  font-family: var(--ls-font-heading);
  font-size: 1.25rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-lg);
  color: #fff;
  transition: color var(--ls-ease);
}
.ls-mobile-nav__link::after {
  content: "\2192";
  font-size: 1rem;
  color: var(--ls-brand-light);
  opacity: 0.55;
  transition: transform var(--ls-ease), opacity var(--ls-ease);
}
.ls-mobile-nav__link:hover,
.ls-mobile-nav__link:focus-visible { color: var(--ls-brand-light); }
.ls-mobile-nav__link:hover::after { opacity: 1; transform: translateX(3px); }

/* Service pages read as children of Service, not as siblings of Home. */
.ls-mobile-nav__sublist {
  list-style: none;
  margin: 0 0 var(--ls-space-4);
  padding: 0 0 0 var(--ls-space-5);
  border-left: 1px solid rgba(255, 255, 255, 0.14);
}
.ls-mobile-nav__sublink {
  display: block;
  padding: 9px 0;
  font-size: 0.9375rem;
  color: rgba(255, 255, 255, 0.72);
  transition: color var(--ls-ease);
}
.ls-mobile-nav__sublink:hover,
.ls-mobile-nav__sublink:focus-visible { color: #fff; }

/* Not margin-top:auto. Pinning the foot to the bottom of a tall screen left
   a large gap under the last link; it reads better directly under the list. */
.ls-mobile-nav__foot {
  padding-top: var(--ls-space-5);
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}
.ls-mobile-nav__cta { width: 100%; }

.ls-mobile-nav__contact {
  list-style: none;
  margin: var(--ls-space-6) 0 0;
  padding: 0;
  display: grid; gap: var(--ls-space-4);
}
.ls-mobile-nav__label {
  display: block; margin-bottom: 2px;
  font-size: 0.6875rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase;
  color: rgba(255, 255, 255, 0.50);
}
.ls-mobile-nav__contact a { font-size: 1rem; color: #fff; }
.ls-mobile-nav__contact a:hover { color: var(--ls-brand-light); }

/* ==========================================================================
   7. GET STARTED MODAL  (replaces Elementor popup #4440)
   Live dialog: 640px wide, overlay rgba(0,0,0,.8), shadow 2px 8px 23px 3px.
   ========================================================================== */
.ls-modal .modal-dialog { max-width: 640px; }
.ls-modal .modal-content {
  border: 0; border-radius: var(--ls-radius-lg);
  box-shadow: var(--ls-shadow-drop);
}
.ls-modal .modal-body { padding: clamp(24px, 4vw, 40px); }
.ls-modal .btn-close { position: absolute; top: 20px; right: 20px; font-size: 20px; z-index: 2; }

/* Overlay opacity to match the live popup exactly */
.modal-backdrop.show { opacity: 1; background-color: var(--ls-overlay); }

/* ==========================================================================
   8. FORMS
   Field styling read from the live Get Started popup.
   ========================================================================== */
/* The old field was a 2px near-white border with no radius and a 46px x 109px
   horizontal blur behind it. Replaced with a plain hairline input, a real
   radius, and a focus ring you can actually see. */
.ls-form__field {
  width: 100%;
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border-mid);
  border-radius: var(--ls-radius-sm);
  padding: 13px 15px;
  font-family: var(--ls-font-body);
  font-size: 1rem;
  font-weight: var(--ls-fw-medium);
  color: var(--ls-secondary);
  box-shadow: none;
  transition: border-color var(--ls-ease), box-shadow var(--ls-ease);
}
.ls-form__field::placeholder { color: var(--ls-muted); opacity: 1; }
.ls-form__field:focus {
  outline: none;
  border-color: var(--ls-primary);
  box-shadow: 0 0 0 3px rgba(145, 139, 195, 0.25);
}
.ls-form__label {
  display: block;
  margin-bottom: var(--ls-space-2);
  color: var(--ls-ink);
  font-family: var(--ls-font-body);
  font-size: 0.875rem;
  font-weight: var(--ls-fw-bold);
}

/* Native select arrow replaced with the site's own chevron asset */
select.ls-form__field {
  -webkit-appearance: none; appearance: none;
  /* padding-right clears the chevron so a long option label cannot run under it. */
  padding-right: 44px;
  /* An inline SVG rather than down.png: it stays crisp at any density and it
     can carry the brand colour, which a flat raster cannot. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%235B51A0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3,6 8,11 13,6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 15px;
  background-position: right 16px center;
  cursor: pointer;
}
/* The open list is an OS control - these two are the only properties browsers
   honour, and only on Windows and Linux. */
select.ls-form__field option { background-color: #fff; color: var(--ls-ink); }
select.ls-form__field:invalid,
select.ls-form__field option[value=""] { color: var(--ls-muted); }

/* On the dark card the chevron needs the light accent instead. */
.ls-form--dark select.ls-form__field {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23A9A3D8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3,6 8,11 13,6'/%3E%3C/svg%3E");
}
.ls-form--dark select.ls-form__field option { background-color: #232E52; color: #fff; }

/* "optional" beside a label - lighter than the label, never bold. */
.ls-form__optional {
  margin-left: 4px;
  font-weight: var(--ls-fw-medium);
  text-transform: none;
  color: var(--ls-muted);
}

/* The modal is a narrow dialog; two columns only once there is room. */
.ls-modal .ls-form__grid { gap: var(--ls-space-4) var(--ls-space-4); }
@media (max-width: 575px) {
  .ls-modal .ls-form__grid { grid-template-columns: 1fr; }
}
.ls-form__submit {
  background-color: var(--ls-ink);
  color: #fff;
  border: 0;
  border-radius: var(--ls-radius-pill);
  min-height: 48px;
  padding: 13px 28px;
  margin-top: var(--ls-space-5);
  font-family: var(--ls-font-body);
  font-size: 1rem;
  font-weight: var(--ls-fw-bold);
  cursor: pointer;
  transition: background-color var(--ls-ease);
}
.ls-form__submit:hover { background-color: var(--ls-primary-deep); }
.ls-form__submit:disabled { opacity: 0.6; cursor: not-allowed; }

/* Inline newsletter form (blog page) */
.ls-form__row { display: flex; flex-wrap: wrap; gap: 15px; align-items: stretch; }
.ls-form__row > div { flex: 1 1 260px; }
.ls-form--inline .ls-form__submit { margin-top: 0; flex: 0 0 auto; padding: 12px 32px; }
@media (max-width: 767px) {
  .ls-form__row { flex-direction: column; }
  .ls-form--inline .ls-form__submit { width: 100%; }
}

/* Honeypot — off-screen rather than display:none, so bots still fill it */
.ls-form__hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px;
  overflow: hidden;
}

/* Inline feedback. Inside a modal these render in the modal body, not behind it. */
.ls-form__message { margin-top: var(--ls-space-4); padding: 12px 16px; border-radius: var(--ls-radius-sm); font-size: 0.9375rem; }
.ls-form__message--success { background-color: var(--ls-lilac-tint); color: var(--ls-ink); }
.ls-form__message--error   { background-color: #FDECEC; color: #8A1F1F; }
.ls-form__field--invalid { border-color: #C0392B; }
.ls-form__field--invalid:focus { border-color: #C0392B; box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.2); }

/* ==========================================================================
   9. FOOTER
   Navy band; an 800px centred row holding a white logo card and the nav.
   ========================================================================== */
.site-footer { background-color: var(--ls-violet-950); padding: var(--ls-space-8) var(--ls-container-pad) var(--ls-space-7); }
.site-footer__row {
  width: 100%; max-width: 800px; margin-inline: auto;
  display: flex; align-items: stretch; gap: 50px;
}
.site-footer__logo-card {
  flex: 0 0 340px;
  background-color: var(--ls-accent);
  border-radius: var(--ls-radius-lg);
  padding: var(--ls-space-6) var(--ls-space-5);
  display: flex; flex-direction: column; justify-content: center; align-items: center;
}
.site-footer__logo-card img { width: 290px; height: 46px; max-width: 100%; display: block; }
.site-footer__nav { flex: 1 1 auto; display: flex; flex-direction: column; justify-content: flex-end; padding: 10px; }
.site-footer__list { list-style: none; margin: 0; padding: 0; }
.site-footer__link {
  display: block;
  padding: 6px 0;
  font-family: var(--ls-font-body); font-size: 1rem; font-weight: var(--ls-fw-medium);
  color: var(--ls-accent);
}
.site-footer__link:hover, .site-footer__link:focus-visible { color: var(--ls-lilac-tint); }
.site-footer__copyright {
  margin: var(--ls-space-8) auto 0;
  padding-top: var(--ls-space-6);
  max-width: 800px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  text-align: center;
  font-family: var(--ls-font-body); font-weight: var(--ls-fw-body); font-size: 0.9375rem;
  color: rgba(255, 255, 255, 0.75);
}
.site-footer__copyright a { color: var(--ls-periwinkle); }
.site-footer__copyright a:hover { color: var(--ls-accent); }

@media (max-width: 1024px) {
  .site-footer__row { gap: 10px; }
  .site-footer__logo-card img { width: 280px; }
  .site-footer__link { font-size: 15px; }
  .site-footer__copyright { padding-top: 40px; font-size: 15px; }
}
@media (max-width: 767px) {
  .site-footer__row { flex-direction: column; align-items: center; gap: 10px; }
  .site-footer__logo-card { flex: 0 0 auto; width: 100%; padding: 20px 0; }
  .site-footer__nav { align-items: center; justify-content: center; }
  .site-footer__list { text-align: center; }
  .site-footer__copyright { padding-top: 30px; }
}

/* ==========================================================================
   10. BLOG POST
   The hero band uses the post's own featured image as a full-bleed background
   under a 95%-opacity lilac overlay — that is why the title reads white and
   the image shows only as a faint texture. Reproduced exactly.
   ========================================================================== */
.post__hero {
  position: relative;
  padding: var(--ls-space-7) var(--ls-container-pad) 0;
  min-height: 0;
  background-size: cover;
  background-position: 50% 50%;
  background-repeat: no-repeat;
  overflow: hidden;
}
/* Same band gradient as every other hero, so a post header and a page header
   are the same surface. */
.post__hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg,
      rgba(20, 27, 51, 0.97) 0%,
      rgba(20, 27, 51, 0.94) 38%,
      rgba(20, 27, 51, 0.66) 72%,
      rgba(20, 27, 51, 0.42) 100%),
    linear-gradient(180deg, rgba(20, 27, 51, 0.45) 0%, rgba(20, 27, 51, 0.70) 100%);
}
.post__hero-inner {
  position: relative;
  width: 100%;
  max-width: var(--ls-container);
  margin-inline: auto;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
}
.post__hero-text { padding-block: var(--ls-space-7); max-width: 46rem; }
.post__title {
  font-family: var(--ls-font-heading);
  font-size: var(--ls-h4);
  font-weight: var(--ls-fw-heading);
  line-height: var(--ls-lh-head);
  letter-spacing: var(--ls-track-lg);
  color: var(--ls-accent);
  margin: 0 0 var(--ls-space-4);
}
.post__breadcrumb ol {
  display: flex; align-items: center; gap: 10px;
  list-style: none; margin: 0; padding: 0;
}
.post__breadcrumb a {
  font-family: var(--ls-font-body);
  font-size: 0.9375rem; font-weight: var(--ls-fw-medium);
  color: rgba(255, 255, 255, 0.85);
}
.post__breadcrumb a:hover { color: var(--ls-lilac-tint); }
.post__breadcrumb-sep { display: flex; color: var(--ls-accent); }
.post__hero-figure { width: min(420px, 34%); height: auto; align-self: flex-end; }

.post__body { padding: var(--ls-section) 0; }

/* A 1260px measure is unreadable for prose. The whole post column now sits in
   one ~780px reading width, which is the single biggest legibility win on the
   blog template. */
.post__body > .ls-container { max-width: 780px; }

.post__terms {
  display: flex; flex-wrap: wrap; gap: 18px;
  list-style: none; margin: 0 0 10px; padding: 0;
}
.post__terms a {
  display: inline-block;
  padding: 5px 12px;
  border-radius: var(--ls-radius-pill);
  background-color: var(--ls-lilac-tint);
  font-family: var(--ls-font-body);
  font-size: 0.8125rem; font-weight: var(--ls-fw-bold); line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--ls-ink);
}
.post__terms a:hover { color: var(--ls-primary-deep); }

.post__featured { margin: 0 auto 30px; text-align: center; }
.post__featured img { width: 500px; max-width: 100%; height: auto; }

.post__content {
  font-family: var(--ls-font-body);
  font-size: 1.0625rem; font-weight: var(--ls-fw-body); line-height: 1.75;
  color: var(--ls-text);
}
.post__content h2 {
  font-family: var(--ls-font-heading);
  font-size: var(--ls-h3);
  color: var(--ls-ink);
  margin: var(--ls-space-7) 0 var(--ls-space-4);
}
.post__content h2:first-child { margin-top: 0; }
.post__content p { margin: 0 0 var(--ls-space-5); }
.post__content a { color: var(--ls-primary-deep); text-decoration: underline; text-underline-offset: 3px; }
.post__content img { max-width: 100%; height: auto; border-radius: var(--ls-radius-md); }

/* The rest of the elements a real article uses. Only h2, p, a and img were
   styled before, because every post body was placeholder text. */
.post__content h3 {
  font-family: var(--ls-font-heading);
  font-size: 1.125rem;
  letter-spacing: var(--ls-track-lg);
  color: var(--ls-ink);
  margin: var(--ls-space-6) 0 var(--ls-space-3);
}
.post__content ul,
.post__content ol { margin: 0 0 var(--ls-space-5); padding-left: 1.4em; }
.post__content li { margin-bottom: var(--ls-space-3); padding-left: 0.25em; }
.post__content li:last-child { margin-bottom: 0; }
.post__content li::marker { color: var(--ls-primary); }
.post__content ol li::marker { font-weight: var(--ls-fw-bold); }
.post__content strong { font-weight: var(--ls-fw-bold); color: var(--ls-secondary); }
.post__content em { font-style: italic; }
.post__content code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.875em;
  padding: 2px 6px;
  border-radius: 5px;
  background-color: var(--ls-lilac-tint);
  color: var(--ls-ink);
}
.post__content blockquote {
  margin: var(--ls-space-6) 0;
  padding: var(--ls-space-1) 0 var(--ls-space-1) var(--ls-space-5);
  border-left: 3px solid var(--ls-primary);
  color: var(--ls-secondary);
}

/* Prev/next used to be 18px of full-strength lilac on white - 2.9:1, the same
   readability problem the headings had. Label stays lilac as an accent (it is
   small and bold), the title carries in navy. */
.post__nav {
  display: flex; align-items: stretch;
  margin-top: var(--ls-space-8);
  padding-top: var(--ls-space-6);
  border-top: 1px solid var(--ls-border);
  font-family: var(--ls-font-body); font-size: 1rem; color: var(--ls-text);
}
.post__nav-item { width: calc(50% - 1px); display: flex; flex-direction: column; gap: var(--ls-space-1); }
.post__nav-item--next { text-align: right; align-items: flex-end; }
.post__nav-sep { width: 1px; background-color: var(--ls-border); }
.post__nav-label {
  font-size: 0.75rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase;
  color: var(--ls-primary-deep);
}
.post__nav-title { color: var(--ls-ink); font-weight: var(--ls-fw-medium); }
.post__nav a:hover .post__nav-title { color: var(--ls-primary-deep); }
.post__nav-item--empty { visibility: hidden; }

@media (max-width: 1024px) {
  .post__hero { padding-top: var(--ls-space-6); }
  .post__hero-inner { min-height: 0; }
  .post__hero-figure { width: 400px; }
  .post__hero-text { padding-bottom: 40px; }
}
@media (max-width: 767px) {
  .post__hero-inner { flex-direction: column; align-items: flex-start; }
  .post__hero-figure { width: 300px; align-self: center; }
  .post__title { font-size: 28px; line-height: 32px; }
  .post__body { padding-bottom: 60px; }
  .post__nav { flex-direction: column; gap: 20px; }
  .post__nav-item { width: 100%; }
  .post__nav-item--next { text-align: left; align-items: flex-start; }
  .post__nav-sep { display: none; }
}

/* ==========================================================================
   11. BLOG LISTING, CARDS AND ARCHIVES
   One card component, reused by /blog/, every tag tab and every archive page.
   Grid is 3 / 2 / 1 columns with 30px column and 35px row gaps, as live.
   ========================================================================== */
.post-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 30px;
  row-gap: 35px;
  list-style: none;
  margin: 0;
  padding: 0;
}
@media (max-width: 1024px) { .post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 767px)  { .post-grid { grid-template-columns: 1fr; } }

/* The card used to be outlined in 2px of full-strength lilac. A hairline that
   warms to lilac on hover carries the same structure without the thick,
   dated outline. */
.post-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  overflow: hidden;
  transition: border-color var(--ls-ease), box-shadow var(--ls-ease);
}
.post-card:hover { border-color: var(--ls-primary); box-shadow: var(--ls-shadow-card); }
.post-card__thumb { position: relative; display: block; aspect-ratio: 16 / 10; overflow: hidden; background-color: var(--ls-surface); }
.post-card__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* White text on a transparent background sat directly on the photo and was
   unreadable over light images. It is a solid chip now. */
.post-card__badge {
  position: absolute; left: 12px; top: 12px;
  margin: 0; padding: 5px 11px;
  background-color: var(--ls-accent);
  border-radius: var(--ls-radius-pill);
  color: var(--ls-ink);
  font-family: var(--ls-font-body);
  font-size: 0.75rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: 0.02em;
}
.post-card__text { display: flex; flex-direction: column; gap: var(--ls-space-3); padding: var(--ls-space-5); flex: 1 1 auto; }
.post-card__title { margin: 0; font-family: var(--ls-font-heading); font-size: 1.125rem; line-height: 1.3; letter-spacing: var(--ls-track-lg); }
.post-card__title a { color: var(--ls-ink); }
.post-card__title a:hover { color: var(--ls-primary-deep); }
.post-card__excerpt {
  margin: 0;
  font-family: var(--ls-font-body);
  font-weight: var(--ls-fw-body);
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ls-text);
}
.post-card__more {
  margin-top: auto;
  padding-top: var(--ls-space-2);
  font-family: var(--ls-font-body);
  font-size: 0.875rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: 0.02em;
  color: var(--ls-primary-deep);
}
.post-card__more:hover { color: var(--ls-ink); }

/* Load More — reveals already-rendered cards, no server call */
.load-more-wrap { display: flex; justify-content: center; margin-top: 40px; }
.load-more {
  background-color: var(--ls-ink);
  color: var(--ls-accent);
  border: 0;
  border-radius: var(--ls-radius-pill);
  min-height: 48px;
  padding: 13px 32px;
  font-family: var(--ls-font-body);
  font-size: 1rem;
  font-weight: var(--ls-fw-bold);
  cursor: pointer;
  transition: background-color var(--ls-ease);
}
.load-more:hover { background-color: var(--ls-primary-deep); }

/* Tag tabs */
.blog-tabs { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 40px; padding: 0; list-style: none; border: 0; }
.blog-tabs__btn {
  background: transparent;
  border: 1px solid var(--ls-border-mid);
  border-radius: var(--ls-radius-pill);
  padding: 9px 18px;
  font-family: var(--ls-font-body);
  font-size: 0.9375rem;
  font-weight: var(--ls-fw-medium);
  color: var(--ls-text);
  cursor: pointer;
  transition: background-color var(--ls-ease), border-color var(--ls-ease), color var(--ls-ease);
}
.blog-tabs__btn:hover { background-color: var(--ls-surface); border-color: var(--ls-ink); color: var(--ls-ink); }
.blog-tabs__btn[aria-selected="true"] {
  background-color: var(--ls-ink);
  border-color: var(--ls-ink);
  color: var(--ls-accent);
}
.blog-panel[hidden] { display: none; }

/* Section headings on /blog/ and the archives */
.blog-section { padding: var(--ls-section) 0; }
.blog-section__title { margin: 0 0 var(--ls-space-7); }
.archive-hero {
  position: relative;
  padding: var(--ls-section) var(--ls-container-pad);
  background-color: var(--ls-violet-900);
  background-image: var(--ls-band);
}
.archive-hero__title { color: var(--ls-accent); margin: 0 0 var(--ls-space-2); }
.archive-hero__count { color: rgba(255, 255, 255, 0.85); font-family: var(--ls-font-body); font-size: 1rem; margin: 0; }

/* ==========================================================================
   12. BUTTONS (shared)
   ========================================================================== */
/* 22px x 57px of padding on a 16px label is a 2015-size button. These are
   sized to a 48px control height, which is the current norm and also the
   minimum comfortable touch target. */
.btn-primary-ls,
.btn-ghost-ls {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ls-space-2);
  min-height: 48px;
  padding: 13px 28px;
  border: 1px solid transparent;
  border-radius: var(--ls-radius-pill);
  font-family: var(--ls-font-body);
  font-size: 1rem;
  font-weight: var(--ls-fw-bold);
  line-height: 1.2;
  cursor: pointer;
  text-align: center;
  transition: background-color var(--ls-ease), border-color var(--ls-ease), color var(--ls-ease);
}
.btn-primary-ls { background-color: var(--ls-ink); color: var(--ls-accent); }
.btn-primary-ls:hover { background-color: var(--ls-primary-deep); color: var(--ls-accent); }

/* The ghost button had no border at all, so it read as stray text next to the
   filled one. It is a real outlined button now. */
.btn-ghost-ls { background-color: transparent; border-color: var(--ls-border-mid); color: var(--ls-ink); }
.btn-ghost-ls:hover { background-color: var(--ls-surface); border-color: var(--ls-ink); color: var(--ls-ink); }

/* The dark-ground variant. */
.btn-ghost-ls--light {
  background-color: transparent;
  border-color: rgba(255, 255, 255, 0.38);
  color: #fff;
}
.btn-ghost-ls--light:hover,
.btn-ghost-ls--light:focus-visible {
  background-color: rgba(255, 255, 255, 0.10);
  border-color: #fff;
  color: #fff;
}

/* Section heading. Navy carries it; the lilac accent lives on the <span>. */
.section-label { font-size: var(--ls-h2); margin: 0 0 var(--ls-space-5); }

/* ==========================================================================
   13. HOME PAGE
   ========================================================================== */

/* --- Hero --- */
/* padding-top clears the header, which is a solid bar now rather than a
   transparent overlay. The height is capped against the viewport so the hero
   does not eat a whole 768px laptop screen. */
.hero {
  position: relative;
  min-height: min(640px, 82vh);
  display: flex; align-items: center; overflow: hidden;
}


/* --- Client logo strip --- */
.clients { padding: var(--ls-section) 0; }

/* "Trusted by local businesses" is a label, not a headline. Set at 45px in a
   900-weight display face it shouted louder than the section it introduced. */
.clients__title {
  font-family: var(--ls-font-body);
  font-size: 0.8125rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow);
  text-transform: uppercase;
  color: var(--ls-muted);
  text-align: center;
  margin: 0 0 var(--ls-space-7);
}
/* --- Logo marquee ---
   Two identical lists sit side by side and both slide left by exactly 100% of
   their own width. Each list carries a trailing padding equal to the gap, so
   "100%" includes the space after the last logo and the loop is seamless with
   no jump. Keeping the offset at a flat -100% avoids putting a custom property
   inside @keyframes, which animates unreliably in some browsers. */
.clients__marquee {
  --clients-gap: 64px;
  display: flex;
  overflow: hidden;
  /* soften both edges so logos fade in and out rather than being cut */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 6%, #000 94%, transparent 100%);
}

.clients__list {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--clients-gap);
  padding-right: var(--clients-gap);
  margin: 0;
  list-style: none;
  animation: clients-scroll 38s linear infinite;
}

@keyframes clients-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}

/* Pause so a visitor can actually stop on a logo, by mouse or by keyboard. */
.clients__marquee:hover .clients__list,
.clients__marquee:focus-within .clients__list { animation-play-state: paused; }

.clients__list li { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; }

/* These logos mix square marks (1:1) with wide wordmarks (up to 3.4:1), so a
   fixed max-WIDTH would render the squares huge and the wordmarks tiny.
   Constraining by height gives every logo similar optical weight, and the
   width cap stops the widest wordmark dominating the row. */
.clients__list img {
  max-height: 60px;
  max-width: 200px;
  width: auto;
  height: auto;
  object-fit: contain;
  /* Desaturated by default, full colour on hover. Six competing brand palettes
     at full saturation fight the lilac heading; grey lets the row read as one
     band, matching how the previous logo strip looked. */
  filter: grayscale(1);
  opacity: 0.6;
  transition: filter 0.3s ease, opacity 0.3s ease;
}
.clients__list img:hover { filter: grayscale(0); opacity: 1; }

/* A square mark at the same height as a wordmark reads much smaller, because
   it covers roughly a third of the area. Squares get extra height so every
   logo carries comparable optical weight across the row. */
.clients__list img.is-square { max-height: 80px; }

@media (max-width: 767px) {
  .clients__marquee { --clients-gap: 40px; }
  .clients__list img { max-height: 44px; max-width: 150px; }
  .clients__list img.is-square { max-height: 60px; }
}

/* The motion is decorative. If the visitor has asked for reduced motion, hold
   the strip still, drop the duplicate copy and let the real list wrap. */
@media (prefers-reduced-motion: reduce) {
  .clients__marquee {
    flex-wrap: wrap;
    justify-content: center;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .clients__list {
    animation: none;
    flex-wrap: wrap;
    justify-content: center;
    padding-right: 0;
    row-gap: 30px;
  }
  .clients__marquee > .clients__list[aria-hidden="true"] { display: none; }
}

/* --- About --- */
/* About is the one section on a tinted band. Every other block on the home
   page is white, so a single surface change gives the page some rhythm and
   stops this block reading as more of the same. Its columns also run the other
   way round to the Why section below it, which used the same image-left
   arrangement. */
.about {
  padding: var(--ls-section) 0;
  background-color: var(--ls-surface);
  border-block: 1px solid var(--ls-border);
}
.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  align-items: center;
  gap: clamp(32px, 5vw, 72px);
}
/* The illustration is 1056x1008, so the panel is square. The previous 5:6
   portrait ratio would have cropped about a fifth off each side of it and lost
   the sideboard and the right-hand chair. */
.about__image {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--ls-radius-lg);
  display: block;
}
.about__text { max-width: 56ch; }
.about__text p { margin: 0 0 var(--ls-space-4); }
.about__text .btn-primary-ls { margin-top: var(--ls-space-5); }

/* --- Why choose us ---
   Restructured. It used to be a narrow image column beside a vertical stack of
   three wide cards, which left most of the left column empty and stretched
   each card's text across a very long line. Now it is a centred header above a
   three-across card row, and each card carries its own image. */
.why { padding: var(--ls-section) 0; }

/* Centred header above the card row. The measure cap is what keeps a centred
   paragraph readable - centred text across a 1260px container is not. */
.why__head {
  max-width: 56ch;
  margin: 0 auto var(--ls-space-8);
  text-align: center;
}
.why__head p { margin: 0; }

/* Three cards across. */
.why__list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--ls-space-5);
  list-style: none;
  margin: 0;
  padding: 0;
}

.why-card {
  display: flex;
  flex-direction: column;
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  overflow: hidden;
  transition: border-color var(--ls-ease);
}
.why-card:hover { border-color: var(--ls-primary); }

/* contain, not cover: two of the three images are product shots on transparent
   backgrounds, and cropping them cuts off the thing being shown. The tint panel
   behind them is what makes the set read as one row despite the mixed source
   art - a screenshot, a device mockup and an illustration. */
.why-card__media {
  /* 4:3 rather than 16:10 so the two portrait-ish images (a phone mockup and a
     seated figure) get enough height to read, instead of being pillarboxed
     down to a third of the panel. */
  aspect-ratio: 4 / 3;
  background-color: var(--ls-lilac-tint);
  display: grid;
  padding: var(--ls-space-4);
  overflow: hidden;
}
/* min-height/min-width 0 is load-bearing, not tidying. A grid item defaults to
   min-height:auto, which floors at the image's INTRINSIC height - so once the
   images actually loaded, each panel grew to its own image's height and the
   three cards stopped matching. Flooring at 0 keeps the panel the size the
   aspect-ratio asked for and lets object-fit do the scaling.
   No place-items here either: the item must stretch to fill the panel, and
   object-fit:contain already centres the picture inside it. */
.why-card__media img {
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: contain;
  display: block;
}

.why-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--ls-space-3);
  padding: var(--ls-space-5);
}
.why-card__icon {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  background-color: var(--ls-lilac-tint);
  border-radius: var(--ls-radius-sm);
}
.why-card__icon img { width: 22px; height: auto; display: block; }

.why-card__title { margin: 0; font-size: var(--ls-h3); }
.why-card__text  { margin: 0; font-size: 0.9375rem; line-height: 1.65; }

@media (max-width: 1024px) {
  .about__grid { grid-template-columns: 1fr; }
  .about__text { max-width: none; }
  .about__image { max-width: 380px; margin-inline: auto; }
  .why__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .why__list { grid-template-columns: 1fr; }
}

/* --- Services --- */
/* The one dark band in the middle of the page. Three near-white sections in a
   row is what made the palette still feel washed out even after the ramp
   changed - a tint alone cannot carry the weight the hero set. The service
   cards are white, so they gain contrast here rather than losing it. */
.services {
  padding: var(--ls-section) 0;
  overflow: hidden;
  background-color: var(--ls-violet-900);
  background-image: var(--ls-band);
  border-block: 0;
}
.services .section-label { color: var(--ls-accent); }
.services .section-label span { color: var(--ls-violet-200); }
.services__head p { color: rgba(255, 255, 255, 0.82); }

/* A violet-900 button would vanish into the band, so the primary inverts. */
.services__head .btn-primary-ls {
  background-color: var(--ls-accent);
  color: var(--ls-violet-900);
}
.services__head .btn-primary-ls:hover {
  background-color: var(--ls-violet-100);
  color: var(--ls-violet-900);
}

/* Bullets invert too - the navy-violet active dot was invisible on the band. */
.services .ls-carousel__dot::before { background-color: rgba(255, 255, 255, 0.34); }
.services .ls-carousel__dot:hover::before { background-color: rgba(255, 255, 255, 0.62); }
.services .ls-carousel__dot[aria-selected="true"]::before { background-color: var(--ls-accent); }
.services .ls-carousel__dot:focus-visible { outline-color: var(--ls-accent); }
.services__head { margin-bottom: var(--ls-space-7); }
.services__head p { max-width: 62ch; margin-bottom: var(--ls-space-5); }
/* Service cards.
   They used to be solid lilac blocks holding a large icon disc and a title,
   with nothing else in them — which is why they read as placeholders. Each one
   now carries a description, and the card is a white hairline card so that
   description is actually legible: white body text on #918BC3 is 3.1:1 and
   fails, and there is no way to put readable small text on that fill.
   The colour moves to the section band and the icon instead.
   Icons remain inline SVG — no icon font, no external request — and each
   depicts its own service: magnifier+bars for SEO, bar chart for analysis,
   storefront for the business profile, map pin for local. */
.service-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ls-space-4);
  height: 100%;
  padding: var(--ls-space-6);
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  transition: border-color var(--ls-ease);
}
.service-card:hover { border-color: var(--ls-primary); }

.service-card__icon {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background-color: var(--ls-lilac-tint);
  transition: background-color var(--ls-ease);
}
.service-card__icon svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: var(--ls-primary-deep);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--ls-ease);
}
.service-card:hover .service-card__icon { background-color: var(--ls-primary); }
.service-card:hover .service-card__icon svg { stroke: var(--ls-accent); }

.service-card__title {
  margin: 0;
  font-size: var(--ls-h3);
  color: var(--ls-ink);
  text-align: left;
}
.service-card__text {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--ls-text);
}

@media (max-width: 767px) {
  .service-card { padding: var(--ls-space-5); }
  .service-card__icon { width: 46px; height: 46px; }
  .service-card__icon svg { width: 23px; height: 23px; }
}

/* --- Testimonials --- */
.testimonials { padding: var(--ls-section) 0; }
.testimonials__head { max-width: 62ch; margin-bottom: var(--ls-space-7); }
.testimonial {
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  padding: var(--ls-space-6);
  height: 100%;
}
/* Initials, not a stock headshot. These are real named people and a bought
   photograph beside a real name is a misrepresentation, not a placeholder. */
.testimonial__avatar {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin-bottom: var(--ls-space-4);
  border-radius: 50%;
  background-color: var(--ls-lilac-tint);
  color: var(--ls-violet-700);
  font-family: var(--ls-font-heading);
  font-size: 1rem;
  letter-spacing: 0.02em;
}
.testimonial h3 { margin: 0 0 2px; font-size: 1.0625rem; }
.testimonial__role { margin: 0 0 var(--ls-space-4); font-size: 0.875rem; color: var(--ls-muted); }
.testimonial__role a { color: var(--ls-primary-deep); text-decoration: underline; text-underline-offset: 2px; }
.testimonial__quote {
  margin: 0;
  /* blockquote carries a UA margin and indent that would break the card */
  padding: 0;
  font-size: 0.9375rem;
  line-height: 1.7;
}

/* --- Carousel (replaces Swiper) ---
   CSS scroll-snap does the sliding; JS only renders and syncs the bullets.
   Bullet styling matches the live Swiper bullets exactly: 24px, 6.4px white
   border, pill radius, navy fill. */
.ls-carousel { position: relative; }
.ls-carousel__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--ls-carousel-gap, 30px) * (var(--per-view, 3) - 1))) / var(--per-view, 3));
  gap: var(--ls-carousel-gap, 30px);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  list-style: none;
  margin: 0;
  padding: 0 0 10px;
}
.ls-carousel__track::-webkit-scrollbar { display: none; }
.ls-carousel__slide { scroll-snap-align: start; min-width: 0; }
/* The bullet is drawn by ::before so the BUTTON can stay a 28px hit target
   while the mark itself is 8px. Sizing the button to the mark meant a 28px
   focus ring wrapped an 8px dot, which read as a fourth, differently-styled
   bullet rather than as focus. */
.ls-carousel__dots {
  display: flex; align-items: center; justify-content: center;
  gap: var(--ls-space-1);
  margin-top: var(--ls-space-6);
}
.ls-carousel__dots[hidden] { display: none; }
.ls-carousel__dot {
  width: 28px; height: 28px; padding: 0;
  border: 0;
  border-radius: var(--ls-radius-pill);
  background: none;
  box-shadow: none;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.ls-carousel__dot::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: var(--ls-radius-pill);
  background-color: var(--ls-border-mid);
  transition: width var(--ls-ease), background-color var(--ls-ease);
}
.ls-carousel__dot:hover::before { background-color: var(--ls-primary); }
.ls-carousel__dot[aria-selected="true"]::before {
  width: 22px;
  background-color: var(--ls-ink);
}
.ls-carousel__dot:focus-visible { outline: 2px solid var(--ls-ink); outline-offset: -2px; }

@media (max-width: 1024px) { .ls-carousel__track { --per-view: 2; } }
@media (max-width: 767px)  { .ls-carousel__track { --per-view: 1; } }

/* --- FAQ ---
   Tinted band, continuing the white / tinted alternation down the page, with
   the list capped at 820px. An accordion left to fill the 1260px column gives
   answers a line length nobody wants to read. */
.faq {
  padding: var(--ls-section) 0;
  background-color: var(--ls-surface);
  border-block: 1px solid var(--ls-border);
}
.faq__head {
  max-width: 56ch;
  margin: 0 auto var(--ls-space-7);
  text-align: center;
}
.faq__head p { margin: 0; }
.faq__head a { color: var(--ls-primary-deep); text-decoration: underline; text-underline-offset: 3px; }

.faq__list { max-width: 820px; margin-inline: auto; }
/* .ls-accordion carries a 30px top margin for its use on the service page,
   where it follows body copy directly. Here the section head already spaces it. */
.faq__list.ls-accordion { margin-top: 0; }
.faq__list .accordion-button { padding: var(--ls-space-4) var(--ls-space-5); }
.faq__list .accordion-body { padding: 0 var(--ls-space-5) var(--ls-space-5); }

/* ==========================================================================
   14. PAGE HERO — v2
   Full-bleed image behind a scrim, copy on the left. Replaces the circular
   "bubble" figure and gold badge.

   The scrim is doing real work, not decoration. White text sits over an
   arbitrary photograph, so legibility cannot depend on what that photograph
   happens to contain: a near-opaque wash on the left where the text is, fading
   right to let the artwork show. Even against a white image the composite
   stays dark enough for white type.
   ========================================================================== */
.page-hero {
  position: relative;
  isolation: isolate;
  background-color: var(--ls-ink);
  padding: calc(var(--ls-header-h) + var(--ls-space-8)) 0 var(--ls-space-8);
  overflow: hidden;
}
.page-hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--hero-img);
  background-size: cover;
  /* Overridable per page: some artwork has its subject off-centre, and the
     default only suits images whose interest sits right of middle. */
  background-position: var(--hero-pos, 78% center);
  background-repeat: no-repeat;
}
.page-hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg,
      rgba(20, 27, 51, 0.97) 0%,
      rgba(20, 27, 51, 0.94) 38%,
      rgba(20, 27, 51, 0.66) 72%,
      rgba(20, 27, 51, 0.42) 100%),
    linear-gradient(180deg, rgba(20, 27, 51, 0.45) 0%, rgba(20, 27, 51, 0.70) 100%);
}

.page-hero__inner { position: relative; max-width: var(--ls-container); }
.page-hero__title { margin: 0 0 var(--ls-space-4); max-width: 20ch; color: #fff; }
.page-hero__lead {
  max-width: 52ch;
  margin: 0;
  font-size: 1.0625rem;
  line-height: var(--ls-lh-body);
  color: rgba(255, 255, 255, 0.80);
}
.page-hero__actions { display: flex; flex-wrap: wrap; gap: var(--ls-space-3); margin-top: var(--ls-space-6); }

.page-hero__breadcrumb ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ls-space-3);
  list-style: none; margin: 0 0 var(--ls-space-5); padding: 0;
}
.page-hero__breadcrumb a,
.page-hero__breadcrumb span {
  font-size: 0.8125rem;
  color: rgba(255, 255, 255, 0.68);
}
.page-hero__breadcrumb a:hover { color: #fff; }
.page-hero__sep { color: rgba(255, 255, 255, 0.55); font-size: 0.8125rem; }

@media (max-width: 767px) {
  .page-hero { padding: calc(var(--ls-header-h) + var(--ls-space-7)) 0 var(--ls-space-7); }
  /* Centre the artwork once the copy is full width, so the scrim is even. */
  .page-hero__media { background-position: center; }
  .page-hero__media::after {
    background: linear-gradient(180deg, rgba(20, 27, 51, 0.88) 0%, rgba(20, 27, 51, 0.94) 100%);
  }
}

/* ==========================================================================
   15. CONTACT PAGE
   ========================================================================== */
/* Tinted band so the white form card has something to sit on. */
.contact-main {
  padding: var(--ls-section) 0;
  background-color: var(--ls-surface);
  border-block: 1px solid var(--ls-border);
}
.contact-main__grid { display: flex; gap: clamp(32px, 5vw, 72px); align-items: flex-start; }
.contact-details__intro { max-width: 38ch; margin: 0 0 var(--ls-space-6); }

.contact-card {
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  padding: clamp(24px, 3vw, 40px);
}

/* Two fields per row, message full width. The old form stacked five identical
   boxes and hid every label, leaving placeholders as the only cue - which
   disappear the moment someone starts typing. */
.ls-form__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ls-space-4) var(--ls-space-5);
}
.ls-form__grid-full { grid-column: 1 / -1; }
.ls-form__note { margin: var(--ls-space-3) 0 0; font-size: 0.8125rem; color: var(--ls-muted); }

.contact-next { padding: var(--ls-section) 0; }
.contact-details { flex: 0 0 320px; }
.contact-details__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 22px; }
.contact-details__label {
  display: block;
  font-family: var(--ls-font-body);
  font-size: 0.75rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow);
  text-transform: uppercase;
  color: var(--ls-muted);
  margin-bottom: var(--ls-space-1);
}
.contact-details__list a { color: var(--ls-text); }
.contact-details__list a:hover { color: var(--ls-primary-deep); }
.contact-form-wrap { flex: 1 1 auto; }

@media (max-width: 1024px) {
  .contact-main__grid { flex-direction: column; gap: var(--ls-space-7); }
  .contact-details { flex: 0 0 auto; width: 100%; }
  .contact-details__intro { max-width: none; }
}
@media (max-width: 560px) {
  .ls-form__grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   16. SERVICE PAGES
   ========================================================================== */
.services-overview__lead { max-width: 62ch; margin-bottom: var(--ls-space-7); }
/* --- Services index: copy + artwork beside a divided list -----------------
   A 2x2 grid of bordered cards here was the third card grid on the site, after
   the Why cards and the home service carousel. A hairline-divided list reads as
   a different kind of thing - a contents page rather than another set of tiles -
   and gives the descriptions a full line length instead of a narrow column. */
.services-overview { padding: var(--ls-section) 0; }
.services-overview__layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}
.services-overview__intro { max-width: 46ch; }
.services-overview__media { margin: var(--ls-space-7) 0 0; }
.services-overview__media img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--ls-radius-lg);
  background-color: var(--ls-surface);
  display: block;
}

.service-list { list-style: none; margin: 0; padding: 0; }
.service-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--ls-space-5);
  padding: var(--ls-space-6) 0;
  border-top: 1px solid var(--ls-border);
}
.service-row:first-child { border-top: 0; padding-top: 0; }
.service-row:last-child { padding-bottom: 0; }

/* The same four SVGs the home carousel uses - the two lists are the same set
   of services, so they should not look like two different inventories. */
.service-row__icon {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: 50%;
  background-color: var(--ls-lilac-tint);
  transition: background-color var(--ls-ease);
}
.service-row__icon svg {
  width: 26px; height: 26px;
  fill: none; stroke: var(--ls-primary-deep);
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  transition: stroke var(--ls-ease);
}
.service-row:hover .service-row__icon { background-color: var(--ls-primary); }
.service-row:hover .service-row__icon svg { stroke: var(--ls-accent); }

.service-row__title { margin: 0 0 var(--ls-space-2); font-size: var(--ls-h3); }
.service-row__title a { color: var(--ls-ink); }
.service-row__title a:hover { color: var(--ls-primary-deep); }
.service-row__text { margin: 0 0 var(--ls-space-3); }
.service-row__more {
  font-family: var(--ls-font-body);
  font-size: 0.875rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: 0.02em;
  color: var(--ls-primary-deep);
}
.service-row__more:hover { color: var(--ls-ink); }
/* Intro row. The artwork sits in a tinted panel rather than as a bare
   rectangle, matching how illustrations are framed elsewhere on the site, and
   the copy ends in a chip row - a lighter way to list scope than another card
   grid or tick list, both of which this page already uses further down. */
.service-intro {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
}
.service-intro__text { max-width: 54ch; }
.service-intro__text p { margin: 0 0 var(--ls-space-4); }

.service-intro__media {
  margin: 0;
  padding: var(--ls-space-4);
  background-color: var(--ls-lilac-tint);
  border-radius: var(--ls-radius-lg);
}
.service-intro__media img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--ls-radius-md);
  display: block;
}

.service-intro__chips-label {
  margin: var(--ls-space-5) 0 var(--ls-space-3) !important;
  font-family: var(--ls-font-body);
  font-size: 0.75rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow);
  text-transform: uppercase;
  color: var(--ls-muted);
}
.chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ls-space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.chip-list li {
  padding: 6px 14px;
  border-radius: var(--ls-radius-pill);
  background-color: var(--ls-lilac-tint);
  color: var(--ls-ink);
  font-family: var(--ls-font-body);
  font-size: 0.8125rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: 0.01em;
}

.service-found { display: flex; align-items: center; gap: 60px; }
.service-found img { flex: 0 0 auto; max-width: 100%; height: auto; }
.service-found > div { flex: 1 1 auto; }

.stat-band { background-color: var(--ls-lilac-tint); padding: var(--ls-section) 0; }
.stat-band__grid { display: flex; align-items: center; gap: 50px; }
.stat-band__text { flex: 1 1 auto; }
.stat-band__grid img { flex: 0 0 auto; max-width: 45%; height: auto; }
.stat-band__figure { font-size: var(--ls-display); margin: 0; color: var(--ls-ink); }
.stat-band__sub { font-size: var(--ls-h2); margin: 0; }
.stat-band__line { font-size: var(--ls-body); margin: 10px 0 20px; }
.stat-band__text > p { max-width: 56ch; }
.stat-band__ask, .stat-band__help { font-size: var(--ls-h3); margin: 0; }
.stat-band__ask { margin-top: var(--ls-space-5); }
.stat-band__help { color: var(--ls-ink); margin-bottom: var(--ls-space-5); }
.stat-band__actions { display: flex; flex-wrap: wrap; gap: var(--ls-space-3); }
/* On the tint a transparent ghost button needs a real surface to read against. */
.stat-band__actions .btn-ghost-ls { background-color: var(--ls-accent); }
.stat-band__actions .btn-ghost-ls:hover { background-color: var(--ls-accent); border-color: var(--ls-ink); }

.tick-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.tick-list li { position: relative; padding-left: 32px; }
.tick-list li::before {
  content: "";
  position: absolute; left: 0; top: 6px;
  width: 16px; height: 16px;
  border: 2.5px solid var(--ls-primary-deep);
  border-top: 0; border-left: 0;
  transform: rotate(45deg) translateY(-2px);
  border-radius: 2px;
}
.reasons .tick-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 40px; }
.included__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ls-space-3) var(--ls-space-6); }
/* Ten short rows, so they come in from the right quickly rather than drifting. */
.included__list.js-reveal { --reveal-from: 28px; --reveal-dur: 260ms; }

/* --- FAQ ------------------------------------------------------------------
   Was five detached rounded boxes with a chevron - the Bootstrap default, and it
   read as a stack of pills floating in space. Now one bordered block divided by
   hairlines, so the set reads as a single considered object.

   The toggle is a plus in a circle rather than a chevron, and the open state
   rotates it 45deg into a cross: one glyph, two meanings, and the rotation makes
   the state change legible without a second icon.
   ------------------------------------------------------------------------- */
.ls-accordion {
  margin-top: var(--ls-space-7);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  background-color: var(--ls-accent);
  overflow: hidden;
}
.ls-accordion .accordion-item {
  border: 0;
  border-top: 1px solid var(--ls-border);
  border-radius: 0;
  margin: 0;
  background-color: transparent;
}
.ls-accordion .accordion-item:first-of-type { border-top: 0; }

.ls-accordion .accordion-button {
  padding: var(--ls-space-5) var(--ls-space-6);
  font-family: var(--ls-font-heading);
  font-size: 1.0625rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-lg);
  color: var(--ls-ink);
  background-color: transparent;
  box-shadow: none;
  transition: color var(--ls-ease), background-color var(--ls-ease);
}
.ls-accordion .accordion-button:hover { background-color: var(--ls-surface); }
.ls-accordion .accordion-button:not(.collapsed) {
  color: var(--ls-brand);
  background-color: transparent;
  box-shadow: none;
}
.ls-accordion .accordion-button:focus-visible {
  box-shadow: none;
  outline: 2px solid var(--ls-brand);
  outline-offset: -2px;
}

/* Bootstrap ships a chevron as a background-image on ::after. Blank the image
   and use the box as a glyph well instead. */
.ls-accordion .accordion-button::after {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  /* auto, not a fixed gap: this is what pushes the toggle to the right edge,
     so every one of them lines up in a column regardless of question length. */
  margin-left: auto;
  background-image: none;
  background-color: var(--ls-brand-soft);
  border-radius: 50%;
  content: "+";
  display: grid;
  place-items: center;
  font-size: 1.125rem;
  font-weight: var(--ls-fw-bold);
  line-height: 1;
  color: var(--ls-brand);
  transform: none;
  transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1),
              background-color var(--ls-ease), color var(--ls-ease);
}
/* 45deg turns the plus into a cross - same glyph, opposite meaning. */
.ls-accordion .accordion-button:not(.collapsed)::after {
  transform: rotate(45deg);
  background-color: var(--ls-brand);
  color: #fff;
}

.ls-accordion .accordion-body {
  padding: 0 var(--ls-space-6) var(--ls-space-6);
  font-family: var(--ls-font-body);
  font-size: 0.9375rem;
  line-height: var(--ls-lh-body);
  color: var(--ls-text);
  max-width: 68ch;
}

@media (prefers-reduced-motion: reduce) {
  .ls-accordion .accordion-button::after { transition: none; }
}

@media (max-width: 575px) {
  .ls-accordion .accordion-button { padding: var(--ls-space-4) var(--ls-space-5); font-size: 1rem; }
  .ls-accordion .accordion-body { padding: 0 var(--ls-space-5) var(--ls-space-5); }
}

.plans__lead { max-width: 62ch; margin-bottom: var(--ls-space-7); }

/* --- Services page: process, inclusions, closing CTA ---------------------- */
.process { padding: var(--ls-section) 0; background-color: var(--ls-surface); border-block: 1px solid var(--ls-border); }
.process__head { max-width: 56ch; margin: 0 auto var(--ls-space-7); text-align: center; }
.process__head p { margin: 0; }
/* Reuses .plan, minus the button - a numbered card is a numbered card.
   .plans__grid is built for the three pricing plans, so on its own it left the
   fourth step orphaned on a second row. Four across here; the 1024px and 767px
   .plans__grid rules come later in the file and still take over below those
   widths, dropping it to two and then one. */
.process__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ls-space-5);
  list-style: none;
  margin: 0;
  padding: 0;
}
.process__grid .plan { background-color: var(--ls-accent); padding: var(--ls-space-5); }
.process__grid .plan p:last-child { font-size: 0.9375rem; line-height: 1.6; }

.included { padding: var(--ls-section) 0; }
.included__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}
.included__text { max-width: 46ch; }
.included__list { gap: var(--ls-space-3); }

/* Closing band, painted from --ls-band so the page ends on the same surface
   it opened with. */
.cta-band {
  padding: var(--ls-section) 0;
  background-color: var(--ls-violet-900);
  background-image: var(--ls-band);
}
.cta-band__inner { max-width: 60ch; margin-inline: auto; text-align: center; }
.cta-band__title { color: var(--ls-accent); margin: 0 0 var(--ls-space-4); font-size: var(--ls-h2); letter-spacing: var(--ls-track-lg); }
.cta-band__text { color: rgba(255, 255, 255, 0.82); margin: 0 0 var(--ls-space-6); }
.cta-band__actions { display: flex; flex-wrap: wrap; gap: var(--ls-space-3); justify-content: center; }
/* A violet-900 button would vanish into the band, so the primary inverts. */
.btn-primary-ls--light { background-color: var(--ls-accent); color: var(--ls-violet-900); }
.btn-primary-ls--light:hover { background-color: var(--ls-violet-100); color: var(--ls-violet-900); }

/* Four 280px steps are fine to 1260px; below that they get too narrow, so the
   row halves well before the tablet breakpoint. */
@media (max-width: 1200px) {
  .process__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .process__grid { grid-template-columns: 1fr; }
}
@media (max-width: 1024px) {
  .included__grid { grid-template-columns: 1fr; }
  .included__text { max-width: none; }
}
@media (max-width: 767px) {
  .included__list { grid-template-columns: 1fr; }
}
.plans__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 30px; list-style: none; margin: 0; padding: 0; }
.plan {
  background-color: var(--ls-accent);
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  padding: var(--ls-space-6);
  display: flex; flex-direction: column; gap: var(--ls-space-3);
  transition: border-color var(--ls-ease);
}
.plan:hover { border-color: var(--ls-primary); }
/* The ghost number was set in the tint colour, which is now light enough to be
   invisible on white. Tinted lilac at low emphasis instead. */
.plan__number { font-family: var(--ls-font-heading); font-size: var(--ls-h2); color: var(--ls-primary); opacity: 0.4; margin: 0; line-height: 1; }
.plan__name { font-size: var(--ls-h3); margin: 0; color: var(--ls-ink); }
.plan .btn-primary-ls { margin-top: auto; align-self: flex-start; }

@media (max-width: 1024px) {
  .plans__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .services-overview__layout { grid-template-columns: 1fr; }
  .services-overview__intro { max-width: none; }
  .services-overview__media { max-width: 420px; }
  .service-found, .stat-band__grid { flex-direction: column; gap: var(--ls-space-6); align-items: flex-start; }
  .stat-band__grid img { max-width: 100%; }
  /* .service-intro is a grid now, so flex-direction would do nothing here. */
  .service-intro { grid-template-columns: 1fr; gap: var(--ls-space-6); }
  .service-intro__text { max-width: none; }
  .service-intro__media { max-width: 460px; }
}
@media (max-width: 767px) {
  .plans__grid { grid-template-columns: 1fr; }
  .service-row { grid-template-columns: 1fr; gap: var(--ls-space-3); }
  .reasons .tick-list { grid-template-columns: 1fr; }
}

/* ==========================================================================
   16a. ZIG-ZAG INSIGHTS GRAPH
   A rising line with a dot per benefit. The band has a definite height, which
   is what lets the dots use a percentage `top` that matches the polyline's
   viewBox y - align them any other way and they drift apart on resize.
   ========================================================================== */
.zigzag { --zz-track: 240px; }

.zigzag__band { position: relative; height: var(--zz-track); margin-bottom: var(--ls-space-6); }

.zigzag__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  stroke: var(--ls-primary);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.zigzag__dots {
  position: relative;
  height: 100%;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
.zigzag__col { position: relative; height: 100%; }
.zigzag__dot {
  position: absolute;
  left: 50%;
  top: calc(var(--y) * 1%);
  transform: translate(-50%, -50%);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background-color: var(--ls-accent);
  border: 3px solid var(--ls-primary-deep);
  box-shadow: 0 0 0 4px rgba(139, 111, 210, 0.18);
}

.zigzag__labels {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--ls-space-5);
  list-style: none;
  margin: 0;
  padding: 0;
}
.zigzag__title { margin: 0 0 var(--ls-space-2); font-size: 1rem; }
.zigzag__text { margin: 0; font-size: 0.875rem; line-height: 1.55; }

/* Five columns stop working long before the phone breakpoint. Below this the
   line is dropped - it cannot say anything useful at that width - and the
   points become an ordinary list with their own markers. */
@media (max-width: 900px) {
  .zigzag__band { display: none; }
  .zigzag__labels { grid-template-columns: 1fr; gap: var(--ls-space-4); }
  .zigzag__labels li {
    position: relative;
    padding-left: var(--ls-space-6);
  }
  .zigzag__labels li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 6px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background-color: var(--ls-accent);
    border: 3px solid var(--ls-primary-deep);
  }
}

/* ==========================================================================
   16b. SECTION BANDS (shared utilities)
   Two classes so any section can take a background without a bespoke rule.
   Used to break up runs of white: the GBP service page had five white
   sections back to back below the fold.
   ========================================================================== */

/* Light band. Safe anywhere - it changes nothing but the surface. */
.band-tint {
  background-color: var(--ls-surface);
  border-block: 1px solid var(--ls-border);
}

/* Dark band, same gradient as the heroes.
   NOTE: this inverts every paragraph it contains, so it is only for sections
   whose text sits DIRECTLY on the band. A section built from white cards would
   need the card text left alone - see .services, which does that explicitly. */
.band-dark {
  background-color: var(--ls-violet-900);
  background-image: var(--ls-band);
}
.band-dark .section-label { color: var(--ls-accent); }
.band-dark .section-label span { color: var(--ls-violet-200); }
.band-dark p { color: rgba(255, 255, 255, 0.82); }

/* ==========================================================================
   16c. SCROLL REVEAL
   Paired with section 8 of site.js. .js-reveal is applied by the script, so
   the hidden state only ever exists when something is guaranteed to undo it.
   ========================================================================== */
.js-reveal .reveal-item {
  opacity: 0;
  /* Negative starts left of place, positive starts right of it. */
  transform: translateX(var(--reveal-from, -16px));
  transition: opacity var(--reveal-dur, 420ms) cubic-bezier(0.4, 0, 0.2, 1),
              transform var(--reveal-dur, 420ms) cubic-bezier(0.4, 0, 0.2, 1);
  transition-delay: var(--reveal-delay, 0ms);
}
.js-reveal .reveal-item.is-in {
  opacity: 1;
  transform: none;
}

/* Reduced motion gets the content, not the choreography. This has to null the
   opacity and transform outright: the global reduced-motion block below only
   shortens transition-DURATION, so a staggered delay would still hold each row
   invisible for up to half a second. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .reveal-item {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0ms;
  }
}

/* ==========================================================================
   17. ACCESSIBILITY
   ========================================================================== */
.ls-skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 1000;
  background: var(--ls-ink); color: #fff; padding: 12px 20px;
}
.ls-skip-link:focus { left: 0; color: #fff; }

:focus-visible { outline: 2px solid var(--ls-ink); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   DESIGN SYSTEM v2 — COMPONENTS
   One vocabulary for the whole site. The v1 rules above are still present for
   pages not yet rebuilt; they are removed page by page as each is migrated.

   Rules of the system:
     - Neutrals carry the page. Accent appears on links, one button, and icons.
     - One card. One list. One section rhythm. No per-section inventions.
     - Panels that look like product UI are built here, not shipped as images.
   ========================================================================== */

/* --- Layout -------------------------------------------------------------- */
/* Clipped, because the decoration inside deliberately overhangs: the plate
   glow reaches 72px past its figure, and a figure in a right-hand column sits
   flush to the container edge, so that overhang became page-wide horizontal
   scroll. Safe here - the only sticky element on the site lives in .ways, which
   is not a .section, and overflow:hidden on an ancestor would break it. */
.section { padding: var(--ls-section) 0; overflow: hidden; }
.section--tint { background-color: var(--ls-surface); border-block: 1px solid var(--ls-border); }

/* Same components, inverted surface. The page ran pale from the logo strip all
   the way to the closing CTA; dropping the testimonials onto the dark ground
   breaks that up and gives the quotes some weight.

   Cards here are a translucent lift rather than white boxes - a white card on a
   near-black band punches a hole in it, where a 4.5% white wash reads as the
   same surface, raised. */
.section--dark {
  position: relative;
  overflow: hidden;
  background-color: var(--ls-ink);
  border-block: 1px solid rgba(255, 255, 255, 0.08);
}
.section--dark > .ls-container { position: relative; z-index: 1; }
.section--dark h2 { color: #fff; }
.section--dark .eyebrow { color: var(--ls-brand-light); }
.section--dark .section__lead { color: rgba(255, 255, 255, 0.72); }

.section--dark .card {
  background-color: rgba(255, 255, 255, 0.045);
  border-color: rgba(255, 255, 255, 0.12);
}
.section--dark .quote__text { color: rgba(255, 255, 255, 0.78); }
.section--dark .quote__name { color: #fff; }
.section--dark .quote__role { color: rgba(255, 255, 255, 0.58); }
.section--dark .quote__role a { color: var(--ls-brand-light); }
.section--dark .quote__role a:hover,
.section--dark .quote__role a:focus-visible { color: #fff; }
.section--dark .quote__avatar {
  background-color: rgba(255, 255, 255, 0.10);
  color: var(--ls-brand-light);
}
.ls-container.narrow { max-width: 760px; }

.section__head { max-width: 62ch; margin-bottom: var(--ls-space-7); }
.section__head--center { margin-inline: auto; text-align: center; }
.section__head h2 { margin: 0 0 var(--ls-space-3); }
.section__lead { margin: 0; color: var(--ls-text); }

.eyebrow {
  margin: 0 0 var(--ls-space-3);
  font-size: 0.75rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow);
  text-transform: uppercase;
  color: var(--ls-brand);
}
/* Must live AFTER .eyebrow, not before it. Same specificity, so source order is
   the whole argument - declared above, it silently loses and dark-ground kickers
   fall back to brand purple on near-black. */
.eyebrow--light { color: var(--ls-brand-light); }

/* --- Service hero: web and spider instead of a photograph ------------------
   The five service pages share one mark rather than five stock illustrations.
   The band gradient carries the ground, a large web fills the right, and the
   spider from the logo sits on it.

   The spider is recoloured, not reused as-is: the logo body is #2C3966 navy,
   which on this band is almost the background colour.
   ------------------------------------------------------------------------- */
.page-hero--web {
  background-image: var(--ls-band);
  isolation: isolate;
}
.page-hero--web > .ls-container { position: relative; z-index: 1; }

.page-hero__web {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 0;
  width: clamp(300px, 44vw, 580px);
  aspect-ratio: 1 / 1;
  pointer-events: none;
}

.hero-web {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--ls-brand-light);
  opacity: 0.34;
  /* Fades away from the corner it is anchored to, so it stops rather than
     ends - the same treatment the section accents use. */
  -webkit-mask-image: radial-gradient(circle at 100% 0%, #000 30%, rgba(0,0,0,0.4) 62%, transparent 86%);
          mask-image: radial-gradient(circle at 100% 0%, #000 30%, rgba(0,0,0,0.4) 62%, transparent 86%);
}
.hero-web line,
.hero-web path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.hero-web .cobweb__rings { opacity: 0.75; }

/* Sitting on the web, below and left of the anchor corner. */
.hero-spider {
  position: absolute;
  top: 34%;
  right: 30%;
  width: 30%;
  height: auto;
  overflow: visible;
  filter: drop-shadow(0 6px 18px rgba(20, 27, 51, 0.55));
}
.hero-spider .sp-body { fill: var(--ls-brand-light); }
.hero-spider .sp-legs { fill: #C9C5E8; }
.hero-spider .sp-eye  { fill: #FFFFFF; }
.hero-spider .sp-pale { fill: #E0DEF0; }

@media (max-width: 767px) {
  /* Behind full-width copy it becomes texture, so it drops back and shrinks. */
  .page-hero__web { width: 260px; }
  .hero-web { opacity: 0.22; }
  .hero-spider { width: 34%; }
}


/* A hero with no photograph behind it - the blog and the archives have no
   relevant image, and a stock one would be worse than none. The band gradient
   plus the webs carry it instead. */
.page-hero--plain {
  background-image: var(--ls-band);
  isolation: isolate;
}
.page-hero--plain > .ls-container { position: relative; z-index: 1; }

/* Centred head, for sections whose content is a single column. */
.section__head--center { margin-inline: auto; text-align: center; }

/* --- Buttons: one shape, three weights ----------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ls-space-2);
  min-height: 44px;
  padding: 11px 20px;
  border: 1px solid transparent;
  border-radius: var(--ls-radius-md);
  font-family: var(--ls-font-body);
  font-size: 0.9375rem;
  font-weight: var(--ls-fw-bold);
  line-height: 1.2;
  letter-spacing: -0.005em;
  cursor: pointer;
  text-align: center;
  transition: background-color var(--ls-ease), border-color var(--ls-ease), color var(--ls-ease);
}
.btn--sm { min-height: 38px; padding: 8px 14px; font-size: 0.875rem; }
.btn--primary { background-color: var(--ls-brand); color: #fff; }
.btn--primary:hover { background-color: var(--ls-brand-dark); color: #fff; }
.btn--ghost { background-color: #fff; border-color: var(--ls-border-mid); color: var(--ls-ink); }
.btn--ghost:hover { background-color: var(--ls-surface); border-color: var(--ls-ink); color: var(--ls-ink); }
.btn--light { background-color: #fff; color: var(--ls-ink); }
.btn--light:hover { background-color: var(--ls-surface-2); color: var(--ls-ink); }
.btn--outline-light { background-color: transparent; border-color: rgba(255,255,255,0.28); color: #fff; }
.btn--outline-light:hover { background-color: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.5); color: #fff; }

.link-arrow {
  margin-top: auto;
  padding-top: var(--ls-space-3);
  font-size: 0.875rem;
  font-weight: var(--ls-fw-bold);
  color: var(--ls-brand);
}
.link-arrow::after { content: " \2192"; }
.link-arrow:hover { color: var(--ls-brand-dark); }

/* --- The card. Everything card-shaped on the site is this. ---------------- */
.cards { display: grid; gap: var(--ls-space-5); list-style: none; margin: 0; padding: 0; }
.cards--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cards--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.card {
  display: flex; flex-direction: column;
  height: 100%;
  padding: var(--ls-space-6);
  background-color: #fff;
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  transition: border-color var(--ls-ease), box-shadow var(--ls-ease);
}
.card:hover { border-color: var(--ls-border-mid); box-shadow: var(--ls-shadow-card); }
.card__icon {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  margin-bottom: var(--ls-space-4);
  border-radius: var(--ls-radius-sm);
  background-color: var(--ls-brand-soft);
}
.card__icon svg {
  width: 20px; height: 20px;
  fill: none; stroke: var(--ls-brand);
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.card__title { margin: 0 0 var(--ls-space-2); font-size: 1.0625rem; }
.card__text { margin: 0; font-size: 0.9375rem; color: var(--ls-text); }

/* --- Steps --------------------------------------------------------------- */
/* ==========================================================================
   MOBILE: centred layout
   Below 768px every column is full width, so the left-aligned rhythm that
   works beside a second column has nothing to align to.
   ========================================================================== */
@media (max-width: 767px) {

  .hero__copy,
  .page-hero__inner,
  .section__head,
  .split__copy,
  .card,
  .step,
  .ways__item,
  .svc-row__link,
  .lead-card,
  .process-intro__copy { text-align: center; }

  /* Copy blocks carry a max-width; without an auto margin they stay pinned
     left while their text centres inside them, which looks like a bug. */
  .hero__lead,
  .hero__note,
  .section__lead,
  .split__copy p,
  .card__text,
  .step__text,
  .ways__text,
  .svc-row__text,
  .lead-card__text,
  .bigstat { margin-inline: auto; }

  /* Rows of buttons and the breadcrumb. */
  .hero__actions,
  .page-hero__actions,
  .cta__actions--start,
  .page-hero__breadcrumb ol { justify-content: center; }

  /* Icon tiles and number tiles sit above their copy now, so they centre too. */
  .card__icon,
  .num-tile,
  .step__num,
  .ways__num,
  .svc-row__icon { margin-inline: auto; }

  /* .ways__item and .svc-row__link are two-column grids at desktop - tile or
     icon beside the copy. Stacked and centred they have to become one column. */
  .ways__item,
  .svc-row__link { grid-template-columns: 1fr; justify-items: center; }

  /* The list centres as a block; the items keep their left edge so the ticks
     stay lined up under each other. */
  .ticks { justify-items: center; }
  .ticks li { text-align: left; }

  /* Contact details read as a centred stack. */
  .contact-lines li { justify-items: center; }
}

/* --- Corner cobweb -------------------------------------------------------
   Decoration, and a nod to the name. It sits in the corner of a band rather
   than behind the copy, and it is aria-hidden with pointer-events off, so it
   is invisible to assistive tech and cannot get in the way of a click.

   The mask is the part that matters: a web that simply stops at a hard edge
   looks pasted on, so it dissolves as it travels away from the corner.
   ------------------------------------------------------------------------- */
.section--web { position: relative; overflow: hidden; }
/* Copy has to sit above it - the SVG is a positioned element, so without this
   it would paint over the heading. */
.section--web > .ls-container { position: relative; z-index: 1; }

.cobweb {
  position: absolute;
  top: 0;
  right: 0;
  width: clamp(170px, 24vw, 320px);
  height: auto;
  color: var(--ls-brand);
  opacity: 0.26;
  pointer-events: none;
  z-index: 0;
  -webkit-mask-image: radial-gradient(circle at 100% 0%, #000 25%, rgba(0, 0, 0, 0.35) 55%, transparent 80%);
          mask-image: radial-gradient(circle at 100% 0%, #000 25%, rgba(0, 0, 0, 0.35) 55%, transparent 80%);
}
.cobweb line,
.cobweb path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: round;
  /* Keeps the threads hairline-fine however large the box scales. */
  vector-effect: non-scaling-stroke;
}
/* Radials are the structural threads in a real web, so they carry slightly
   more weight than the spiral. */
.cobweb__rings { opacity: 0.75; }

/* The opposite corner. Smaller and fainter than the top-right one so the pair
   reads as one motif with a lead and an echo, rather than two equal weights
   fighting across the diagonal. Its mask has to be re-aimed at its own corner. */
.cobweb--bl {
  top: auto;
  bottom: 0;
  right: auto;
  left: 0;
  width: clamp(140px, 19vw, 260px);
  opacity: 0.18;
  -webkit-mask-image: radial-gradient(circle at 0% 100%, #000 25%, rgba(0, 0, 0, 0.35) 55%, transparent 80%);
          mask-image: radial-gradient(circle at 0% 100%, #000 25%, rgba(0, 0, 0, 0.35) 55%, transparent 80%);
}

/* On a dark band the brand purple is barely a shade off the ground, so the
   threads switch to the light periwinkle already used for eyebrows on dark. */
/* On dark, the same two weights - the periwinkle carries further than the brand
   purple does on white, so the pair sits a touch lower. */
.cobweb--light { color: var(--ls-brand-light); opacity: 0.20; }
.cobweb--light.cobweb--bl { opacity: 0.14; }

@media (max-width: 767px) {
  /* Not 0: the fades need a minimum run, or the label ends up on a mid-tone. */
  .logos { min-height: 0; padding: var(--ls-space-7) 0; }
  .cobweb { width: 150px; }
  /* Stacked, the copy runs the full width and the bottom corner is where the
     last step sits, so the echo comes out rather than sitting under text. */
  .cobweb--bl { display: none; }
}

.steps {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ls-space-5);
  list-style: none; margin: 0; padding: 0;
  counter-reset: step;
}
.step { padding-top: var(--ls-space-5); border-top: 1px solid var(--ls-border-mid); }
/* Same tile as .ways__num - one device for "this is item N", used in both
   numbered sections so they read as one system rather than two ideas. */
.step__num {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-bottom: var(--ls-space-4);
  border-radius: var(--ls-radius-md);
  background-color: var(--ls-ink);
  background-image: var(--ls-band);
  color: #fff;
  font-size: 0.75rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: normal;
  font-variant-numeric: tabular-nums;
}
.step__title { margin: 0 0 var(--ls-space-2); font-size: 1rem; }
.step__text { margin: 0; font-size: 0.9375rem; color: var(--ls-text); }

/* --- Split: copy beside a panel ------------------------------------------ */
.split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 72px); align-items: center;
}
.split__copy { max-width: 52ch; }
.split__copy h2 { margin: 0 0 var(--ls-space-4); }
.split__copy p:not(.eyebrow):not(.bigstat) { color: var(--ls-text); }
.split__copy .btn { margin-top: var(--ls-space-5); }

.ticks { list-style: none; margin: var(--ls-space-5) 0; padding: 0; display: grid; gap: var(--ls-space-3); }
.ticks li { position: relative; padding-left: 28px; font-size: 0.9375rem; color: var(--ls-text); }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: 7px;
  width: 14px; height: 8px;
  border-left: 2px solid var(--ls-brand); border-bottom: 2px solid var(--ls-brand);
  transform: rotate(-45deg);
}

/* --- Services: process diagram --------------------------------------------
   The source is a 1024 square, but only the middle band carries the diagram -
   the rest is empty ground with a cobweb drawn into the top-right and
   bottom-left corners. This section already wears two of its own webs, so a
   33:10 crop is doing real work: it lifts out the diagram strip and leaves both
   of the baked-in webs outside the frame. A 16:7 crop was not enough - it left
   a sliver of each.
   ------------------------------------------------------------------------- */
/* --- Services: how we work ------------------------------------------------
   The diagram sits beside the opening copy rather than above a list that
   repeats it. Capped at 380px: large enough to read as a process, small enough
   that it supports the heading instead of becoming the section.
   ------------------------------------------------------------------------- */
.process-intro {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.68fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
  margin-bottom: clamp(44px, 5vw, 72px);
}
/* The head already carries its own bottom margin for the stacked case. */
.process-intro__copy { margin-bottom: 0; max-width: 54ch; }

.process-intro__figure { margin: 0; }
.process-intro__figure img {
  display: block;
  width: 100%;
  max-width: 380px;
  height: auto;
  aspect-ratio: 1 / 1;
  margin-left: auto;
  border-radius: var(--ls-radius-lg);
  border: 1px solid var(--ls-border-mid);
}

@media (max-width: 991px) {
  .process-intro { grid-template-columns: 1fr; gap: var(--ls-space-7); }
  .process-intro__copy { max-width: none; }
  .process-intro__figure img { margin-inline: auto; }
}

/* --- Services: full-width rows -------------------------------------------
   Was a 2x2 grid of bordered cards, which is the most generic shape on the web
   and left the longer descriptions cramped into half-width columns. These are
   four rows instead: hairline-separated, no boxes, and the whole row is the
   link rather than a "Read more" buried at the bottom of a card.

   The icon sits in the same dark tile the numbered items use elsewhere, at a
   larger size - one device, consistently applied.
   ------------------------------------------------------------------------- */
.svc-list {
  list-style: none;
  margin: var(--ls-space-7) 0 0;
  padding: 0;
  border-top: 1px solid var(--ls-border);
}
.svc-row { border-bottom: 1px solid var(--ls-border); }

.svc-row__link {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: clamp(18px, 2.5vw, 36px);
  align-items: center;
  /* Negative margin lets the hover tint bleed past the text edge, so the row
     reads as a band rather than a floating rectangle. */
  padding: var(--ls-space-6) var(--ls-space-5);
  margin-inline: calc(var(--ls-space-5) * -1);
  border-radius: var(--ls-radius-md);
  transition: background-color var(--ls-ease);
}
.svc-row__link:hover,
.svc-row__link:focus-visible { background-color: var(--ls-surface); }

.svc-row__num {
  font-size: 0.75rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow);
  font-variant-numeric: tabular-nums;
  color: var(--ls-muted);
  align-self: start;
  padding-top: 6px;
}

.svc-row__icon {
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: var(--ls-radius-md);
  background-color: var(--ls-ink);
  background-image: var(--ls-band);
  transition: background-color var(--ls-ease);
}
.svc-row__icon svg {
  width: 26px; height: 26px;
  fill: none; stroke: #fff; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.svc-row__link:hover .svc-row__icon,
.svc-row__link:focus-visible .svc-row__icon {
  background-color: var(--ls-brand);
  background-image: none;
}

.svc-row__body { display: block; }
.svc-row__title {
  display: block;
  margin-bottom: 6px;
  font-family: var(--ls-font-heading);
  font-size: 1.125rem;
  font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-lg);
  color: var(--ls-ink);
}
.svc-row__text {
  display: block;
  max-width: 72ch;
  font-size: 0.9375rem;
  line-height: 1.7;
  color: var(--ls-text);
}

.svc-row__go {
  font-size: 0.875rem;
  font-weight: var(--ls-fw-bold);
  color: var(--ls-brand);
  white-space: nowrap;
}
.svc-row__go::after {
  content: " \2192";
  display: inline-block;
  transition: transform var(--ls-ease);
}
.svc-row__link:hover .svc-row__go::after,
.svc-row__link:focus-visible .svc-row__go::after { transform: translateX(3px); }

/* Rows arrive from the left like the other numbered lists on the site. */
.svc-list.js-reveal { --reveal-from: -28px; --reveal-dur: 560ms; }

@media (max-width: 767px) {
  /* The index and the trailing link both drop: the tile already marks the row,
     and the whole row is the link, so a second affordance is noise. */
  /* The desktop bleed is 24px, but the container only has 20px of padding at
     this width, so the row ran 4px off-screen and relied on the section clip to
     hide it. Aligned to the text column instead. */
  .svc-row__link {
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--ls-space-4);
    align-items: start;
    margin-inline: 0;
    padding-inline: 0;
  }
  .svc-row__num, .svc-row__go { display: none; }
  .svc-row__icon { width: 44px; height: 44px; }
  .svc-row__icon svg { width: 21px; height: 21px; }
}

/* --- Case study panel ------------------------------------------------------
   Reuses the .panel chrome from the homepage split, with three differences:
   the tag says this is real rather than illustrative, the metrics row is a
   fixed three, and it is NOT aria-hidden. The homepage panel is decorative
   example data so it is hidden from screen readers; these are a client's
   actual numbers and they are the point of the section.
   ------------------------------------------------------------------------- */
.metrics--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* .metrics carries a top border, margin and padding to separate it from
   whatever sits above it in a panel. First in the body there is nothing above
   it, so all three just opened an empty band under the title bar. */
.panel__body > .metrics:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* The stock tag reads "Example" in muted grey. This one is real data, so it
   carries the brand tint instead of disclaiming itself. */
.panel__tag--live {
  background-color: var(--ls-brand-soft);
  color: var(--ls-brand);
}

.panel__caption {
  margin: var(--ls-space-5) 0 var(--ls-space-4);
  font-size: 0.75rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase;
  color: var(--ls-muted);
}
.panel__foot {
  margin: var(--ls-space-5) 0 0;
  padding-top: var(--ls-space-4);
  border-top: 1px solid var(--ls-border);
  font-size: 0.75rem;
  color: var(--ls-muted);
}

/* The base .bar gives its label an 84px column, which is right for "Website"
   and "Calls" but wraps "garage floor coating near me" onto four lines. Search
   queries get a proper column and the track gives up the room - the bar is a
   comparison, and it can make that comparison in less width than the label
   needs to stay readable. */
.bars--queries .bar { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) auto; }
.bars--queries .bar__label { overflow-wrap: anywhere; line-height: 1.35; }
.bars--queries .bar__val { font-variant-numeric: tabular-nums; }

@media (max-width: 575px) {
  /* Below this the three-column row cannot hold: the label takes the full
     width and the bar sits under it. */
  .bars--queries .bar { grid-template-columns: 1fr auto; gap: 4px var(--ls-space-3); }
  .bars--queries .bar__label { grid-column: 1; }
  .bars--queries .bar__val { grid-column: 2; grid-row: 1; }
  .bars--queries .bar__track { grid-column: 1 / -1; }
}

/* Bold inside dark-band copy would otherwise inherit the 0.78 white and lose
   its emphasis - the figures are the reason anyone reads this paragraph. */
.section--dark .split__copy strong { color: #fff; font-weight: var(--ls-fw-bold); }
/* Links in dark-band copy: the brand purple used on white is 2.9:1 here. */
.section--dark .split__copy a { color: var(--ls-brand-light); }
.section--dark .split__copy a:hover,
.section--dark .split__copy a:focus-visible { color: #fff; }

/* --- Collage: photograph + floating panel ----------------------------------
   The photograph sits top-right, the report panel overlaps it bottom-left. The
   panel is absolutely positioned, so the container reserves the overhang with
   padding-bottom rather than letting it escape and collide with the next band.

   The photograph is FIRST in the DOM, so the panel paints over it by source
   order and needs no z-index - which is what went wrong with the hero slab.
   ------------------------------------------------------------------------- */
.collage {
  position: relative;
  /* Room for the part of the panel that hangs below the photograph. */
  padding-bottom: clamp(48px, 6vw, 84px);
}
.collage__media { margin: 0 0 0 auto; width: 80%; }
.collage__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--ls-radius-lg);
  border: 1px solid var(--ls-border);
}
.collage__panel {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 76%;
}
/* A heavier shadow than the stock panel: it is floating over artwork here, not
   sitting on a flat band, and it needs to read as lifted off it. */
.collage__panel .panel { box-shadow: 0 18px 44px rgba(20, 27, 51, 0.18); }

@media (max-width: 1024px) {
  /* Once the split stacks, the column is full width and the overlap stops
     being a composition - it just crowds the panel. Plain stack instead. */
  .collage { padding-bottom: 0; }
  .collage__media { width: 100%; margin-bottom: var(--ls-space-5); }
  .collage__media img { aspect-ratio: 16 / 10; }
  .collage__panel { position: static; width: 100%; }
}

/* --- Lead card: a form on a dark band --------------------------------------
   The stock .ls-form is built for white: white fields, ink labels, a mid-grey
   border. All three vanish or glare on the ink band, so the --dark variant
   inverts them - translucent fields that read as recessed into the card rather
   than pasted onto it.
   ------------------------------------------------------------------------- */
.lead-card {
  padding: clamp(18px, 2.2vw, 26px);
  border-radius: var(--ls-radius-lg);
  background-color: rgba(255, 255, 255, 0.045);
  border: 1px solid rgba(255, 255, 255, 0.12);
}
.lead-card__title { margin: 0 0 var(--ls-space-2); font-size: 1.125rem; color: #fff; }
.lead-card__text {
  margin: 0 0 var(--ls-space-5);
  font-size: 0.875rem; line-height: 1.65;
  color: rgba(255, 255, 255, 0.72);
}

.ls-form--dark .ls-form__label { color: rgba(255, 255, 255, 0.82); }
.ls-form--dark .ls-form__optional {
  font-weight: var(--ls-fw-medium);
  text-transform: none;
  color: rgba(255, 255, 255, 0.52);
}
.ls-form--dark .ls-form__field {
  background-color: rgba(255, 255, 255, 0.07);
  border-color: rgba(255, 255, 255, 0.20);
  color: #fff;
}
.ls-form--dark .ls-form__field::placeholder { color: rgba(255, 255, 255, 0.45); }
.ls-form--dark .ls-form__field:focus {
  border-color: var(--ls-brand-light);
  background-color: rgba(255, 255, 255, 0.10);
  box-shadow: 0 0 0 3px rgba(169, 163, 216, 0.28);
}
/* Chrome paints autofilled fields a solid pale yellow, which on this ground
   looks like a rendering fault. The inset shadow trick repaints the box. */
.ls-form--dark .ls-form__field:-webkit-autofill {
  -webkit-text-fill-color: #fff;
  -webkit-box-shadow: 0 0 0 1000px #232E52 inset;
  caret-color: #fff;
}
.ls-form--dark .ls-form__submit {
  width: 100%;
  background-color: #fff;
  color: var(--ls-ink);
}
.ls-form--dark .ls-form__submit:hover { background-color: var(--ls-brand-light); color: var(--ls-ink); }
.ls-form--dark .ls-form__note { color: rgba(255, 255, 255, 0.55); }
.ls-form--dark .ls-form__field--invalid { border-color: #FF9B96; }
.ls-form--dark .ls-form__message--success { background-color: rgba(255, 255, 255, 0.12); color: #fff; }

/* Compact sizing. The stock field is 54px tall with 16px type - right for the
   contact page, where the form IS the page. Here it sits beside a headline and
   was making the band a full screen tall on its own. */
.ls-form--dark .ls-form__grid { gap: var(--ls-space-3) var(--ls-space-4); }
.ls-form--dark .ls-form__label { margin-bottom: 5px; font-size: 0.8125rem; }
.ls-form--dark .ls-form__field { padding: 9px 13px; font-size: 0.9375rem; }
.ls-form--dark .ls-form__submit { margin-top: var(--ls-space-4); padding: 11px 24px; font-size: 0.9375rem; }
.ls-form--dark .ls-form__note { margin-top: var(--ls-space-3); font-size: 0.75rem; }

@media (max-width: 575px) {
  .ls-form--dark .ls-form__grid { grid-template-columns: 1fr; }
}

/* Bands whose content does not need the full section well. */
.section--compact { padding: clamp(44px, 4.5vw, 72px) 0; }

/* --- Cards: keep the trailing button on the baseline -----------------------
   .card is already a full-height flex column, so the action only needs to be
   told to take the slack. Without it a plan with seven bullets floats its
   button a line higher than a plan with eight. */
.card > .btn { margin-top: auto; }

/* --- Contact page -------------------------------------------------------- */

/* The form is the point of the page, so it takes the wider column rather than
   sharing evenly with the copy. */
.split--form { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr); align-items: start; }
.panel--form { margin-top: 0; }

.contact-lines { list-style: none; margin: var(--ls-space-6) 0 0; padding: 0; display: grid; gap: var(--ls-space-4); }
.contact-lines li { display: grid; gap: 2px; }
.contact-lines__label {
  font-size: 0.75rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase;
  color: var(--ls-muted);
}
.contact-lines a { font-size: 1.0625rem; color: var(--ls-ink); }
.contact-lines a:hover, .contact-lines a:focus-visible { color: var(--ls-brand); }

/* Three steps, not four. */
.steps--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* An accordion reads badly at full container width - the click target runs the
   whole screen and the answer text loses its measure. */
.ls-container--narrow { max-width: 800px; }

@media (max-width: 991px) {
  .split--form { grid-template-columns: 1fr; }
  .steps--3 { grid-template-columns: 1fr; }
}

/* --- Modifiers used by the service pages --------------------------------- */

/* Two columns once there is room; a seven-item list in one column leaves half
   the band empty. */
.ticks--cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ls-space-3) var(--ls-space-6); }
@media (max-width: 767px) { .ticks--cols { grid-template-columns: 1fr; } }

/* Inside a plan card, where the list is the bulk of the content. */
/* Each item is a label plus an explanation rather than a single phrase, so it
   needs the leading of body copy and more air between rows. */
.ticks--facts { gap: var(--ls-space-4); }
.ticks--facts li { line-height: 1.65; }
.ticks--facts strong { color: var(--ls-ink); }

.ticks--sm { margin: var(--ls-space-4) 0; gap: var(--ls-space-2); }
.ticks--sm li { padding-left: 24px; font-size: 0.875rem; }
.ticks--sm li::before { top: 6px; width: 12px; height: 7px; }
.section--tint .ticks--sm li::before,
.section--tint .ticks li::before { border-color: var(--ls-brand); }

/* The headline statistic on the dark band. Tabular figures so the percentage
   sits square, and tight leading because it is display type, not a heading. */
.bigstat {
  margin: 0 0 var(--ls-space-2);
  font-size: clamp(2.75rem, 2rem + 3.4vw, 4.5rem);
  font-weight: var(--ls-fw-heading);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ls-brand-light);
}

.chip-list__label {
  margin: var(--ls-space-6) 0 var(--ls-space-3);
  font-size: 0.75rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase;
  color: var(--ls-muted);
}

/* .cta__actions centres by default; in a split column it should not. */
.cta__actions--start { justify-content: flex-start; margin-top: var(--ls-space-6); }

/* Copy on the dark band. .split__copy p is scoped to body slate, which is a
   fail on ink - the dark section has to win it back. */
.section--dark .split__copy p:not(.eyebrow):not(.bigstat) { color: rgba(255, 255, 255, 0.78); }
.section--dark .split__copy h2 { color: #fff; }
.section--dark .chip-list__label { color: rgba(255, 255, 255, 0.62); }

/* --- Product panels: the site's imagery, built rather than photographed --- */
.panel {
  background-color: #fff;
  border: 1px solid var(--ls-border);
  border-radius: var(--ls-radius-lg);
  box-shadow: 0 12px 32px rgba(20, 27, 51, 0.07);
  overflow: hidden;
}
.panel__bar {
  display: flex; align-items: center; gap: 6px;
  padding: 10px 14px;
  background-color: var(--ls-surface);
  border-bottom: 1px solid var(--ls-border);
}
.panel__dot { width: 8px; height: 8px; border-radius: 50%; background-color: var(--ls-border-mid); }
.panel__title { margin-left: 8px; font-size: 0.8125rem; font-weight: var(--ls-fw-medium); color: var(--ls-text); }
.panel__tag {
  margin-left: auto; padding: 2px 8px;
  border-radius: var(--ls-radius-pill);
  background-color: var(--ls-brand-soft);
  font-size: 0.6875rem; font-weight: var(--ls-fw-bold); color: var(--ls-brand);
}
.panel__body { padding: var(--ls-space-5); }

.grid9 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.grid9__cell {
  display: grid; place-items: center;
  aspect-ratio: 1 / 1;
  border-radius: var(--ls-radius-sm);
  font-size: 0.9375rem; font-weight: var(--ls-fw-bold);
}
.grid9__cell.is-good { background-color: #E7F5EE; color: #10693F; }
.grid9__cell.is-ok   { background-color: #FDF3DF; color: #8A5B00; }
.grid9__cell.is-weak { background-color: #FBE9E9; color: #97231F; }

.metrics {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--ls-space-3);
  margin: var(--ls-space-5) 0 0; padding: var(--ls-space-4) 0 0;
  border-top: 1px solid var(--ls-border);
  list-style: none;
}
.metric__label { display: block; font-size: 0.75rem; color: var(--ls-muted); }
.metric__value { display: block; font-size: 1.25rem; font-weight: var(--ls-fw-heading); color: var(--ls-ink); letter-spacing: var(--ls-track-lg); }

.bars { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--ls-space-4); }
.bar { display: grid; grid-template-columns: 84px 1fr 40px; align-items: center; gap: var(--ls-space-3); }
.bar__label { font-size: 0.8125rem; color: var(--ls-muted); }
.bar__track { height: 8px; border-radius: var(--ls-radius-pill); background-color: var(--ls-surface-2); overflow: hidden; }
.bar__fill { display: block; height: 100%; border-radius: var(--ls-radius-pill); background-color: var(--ls-brand); }
.bar__val { font-size: 0.8125rem; font-weight: var(--ls-fw-bold); color: var(--ls-ink); text-align: right; }

/* --- Hero — full viewport, photographic --------------------------------------
   Sized to the viewport so the whole proposition is visible without scrolling.
   100svh, not 100vh: on mobile 100vh is the height WITHOUT the browser chrome,
   which pushes the buttons under the address bar and makes the hero scroll —
   the exact thing this is meant to prevent.

   Deliberately a different shape to the inner-page heroes: those are compact
   left-aligned bands, this is full-height with the product panel over the
   photograph. Same system, different composition. */
.hero {
  position: relative;
  isolation: isolate;
  min-height: calc(100vh - var(--ls-header-h) - var(--ls-trustbar));
  min-height: calc(100svh - var(--ls-header-h) - var(--ls-trustbar));
  display: flex;
  align-items: center;
  padding: var(--ls-space-8) 0;
  background-color: var(--ls-ink);
  overflow: hidden;
}
/* An accent glow behind the figure instead of a photograph. A cutout on top of
   a photo is two images fighting; a soft light source gives the figure
   something to sit against without competing with it. */
.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  right: -8%;
  top: 50%;
  transform: translateY(-50%);
  width: 62vw;
  max-width: 860px;
  aspect-ratio: 1 / 1;
  background: radial-gradient(circle,
    rgba(91, 81, 160, 0.42) 0%,
    rgba(91, 81, 160, 0.14) 45%,
    rgba(91, 81, 160, 0) 70%);
  pointer-events: none;
}

.hero__figure { margin: 0; position: relative; isolation: isolate; }

/* --- Hero rank map ---------------------------------------------------------
   A local rank grid as it actually looks: numbered pins over a map, not chips
   in a panel. It sits BEHIND the cutout - z-index -1 against the figure, which
   isolates so that -1 cannot fall through to the section background.
   ------------------------------------------------------------------------- */
.hero__map {
  position: absolute;
  z-index: -1;
  left: auto;
  right: -20%;
  bottom: 4%;
  width: 74%;
  height: auto;
  aspect-ratio: 1 / 1;
  pointer-events: none;
  /* Fades out before the edges so it reads as a fragment of a map rather than
     a square tile dropped on the band. */
  -webkit-mask-image: radial-gradient(circle at 48% 46%, #000 46%, rgba(0,0,0,0.55) 66%, transparent 82%);
          mask-image: radial-gradient(circle at 48% 46%, #000 46%, rgba(0,0,0,0.55) 66%, transparent 82%);
}

.map__area {
  fill: rgba(169, 163, 216, 0.05);
  stroke: rgba(169, 163, 216, 0.20);
  stroke-width: 1;
  stroke-dasharray: 5 7;
}
.map__block { fill: rgba(169, 163, 216, 0.06); }

/* Round caps and uneven weights: the point is that it does not look ruled. */
.map__street {
  fill: none;
  stroke: rgba(169, 163, 216, 0.30);
  stroke-width: 2.2;
  stroke-linecap: round;
}
.map__lane {
  fill: none;
  stroke: rgba(169, 163, 216, 0.17);
  stroke-width: 1.4;
  stroke-linecap: round;
}

.map__pin-num {
  font-family: var(--ls-font-body);
  font-size: 15px;
  font-weight: 650;
  text-anchor: middle;
  dominant-baseline: central;
}
/* Tonal, not traffic lights - the artwork is lavender on navy. */
.map__pin.is-good .map__pin-ring { fill: rgba(169, 163, 216, 0.92); stroke: #EEECF8; stroke-width: 1.5; }
.map__pin.is-good .map__pin-num  { fill: #1B2340; }

.map__pin.is-ok .map__pin-ring { fill: rgba(35, 46, 82, 0.92); stroke: rgba(169, 163, 216, 0.75); stroke-width: 1.5; }
.map__pin.is-ok .map__pin-num  { fill: #C9C5E8; }

.map__pin.is-weak .map__pin-ring { fill: rgba(20, 27, 51, 0.85); stroke: rgba(169, 163, 216, 0.32); stroke-width: 1.2; }
.map__pin.is-weak .map__pin-num  { fill: rgba(255, 255, 255, 0.55); }

@media (max-width: 1024px) {
  /* Stacked, the figure is centred and smaller; the map would spill past it. */
  .hero__map { display: none; }
}
}

/* --- Hero silk: the spider on its way down ---------------------------------
   z-index -1, matching the glow. A positioned element at 0 paints above static
   in-flow content, which would put the whole thing over the headline.
   ------------------------------------------------------------------------- */
.hero__silk {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

/* The thread runs in the left gutter - copy starts at 10% of the band, so 5%
   clears it - and fades in from the top edge rather than starting abruptly. */
.hero__thread {
  position: absolute;
  left: 5%;
  top: 0;
  width: 1px;
  height: 58%;
  background: linear-gradient(180deg,
    rgba(169, 163, 216, 0) 0%,
    rgba(169, 163, 216, 0.55) 22%,
    rgba(169, 163, 216, 0.75) 100%);
}

/* Hanging at the end of it. */
.hero-spider--og {
  position: absolute;
  left: 5%;
  top: 58%;
  width: clamp(84px, 8vw, 118px);
  height: auto;
  transform: translate(-50%, -6%);
  overflow: visible;
}
/* The logo's own colours. The navy body is close to the band, so it reads as a
   silhouette - the periwinkle glow behind it is what gives it an edge. */
.hero-spider--og .sp-body { fill: #2C3966; }
.hero-spider--og .sp-legs { fill: #918BC3; }
.hero-spider--og .sp-eye  { fill: #FFFFFF; }
.hero-spider--og .sp-pale { fill: #E0DEF0; }
.hero-spider--og { filter: drop-shadow(0 0 14px rgba(145, 139, 195, 0.45)); }

/* The web it is heading for. */
.hero-web--bl {
  position: absolute;
  /* .hero-web sets inset:0. Overriding only `bottom` leaves `top:0` in place,
     and with an explicit height the top wins - which pinned this to the top of
     the band instead of the bottom. Both unset sides have to be released. */
  top: auto;
  right: auto;
  left: 0;
  bottom: 0;
  width: clamp(220px, 26vw, 360px);
  aspect-ratio: 1 / 1;
  height: auto;
  color: var(--ls-brand-light);
  opacity: 0.20;
  -webkit-mask-image: radial-gradient(circle at 0% 100%, #000 28%, rgba(0,0,0,0.4) 60%, transparent 84%);
          mask-image: radial-gradient(circle at 0% 100%, #000 28%, rgba(0,0,0,0.4) 60%, transparent 84%);
}
.hero-web--bl line,
.hero-web--bl path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.hero-web--bl .cobweb__rings { opacity: 0.75; }

@media (max-width: 1024px) {
  /* Stacked, the copy runs full width and the gutter the thread lived in is
     gone. The web stays as corner texture; the thread and spider come out
     rather than hanging over the headline. */
  .hero__thread, .hero-spider--og { display: none; }
  .hero-web--bl { opacity: 0.14; }
}

.hero__figure img {
  width: 100%;
  max-width: 520px;
  height: auto;
  margin-left: auto;
  display: block;
}

/* Short laptops. Capping the figure was the obvious move and the wrong one -
   measured, the copy column is the taller of the two, so shrinking the artwork
   bought 4px. The padding is the real lever: 64px back, which brings the trust
   bar to the fold line instead of well below it.

   Must sit AFTER the rules above: a media query adds no specificity, so placed
   before them it simply loses. */
@media (min-width: 768px) and (max-height: 820px) {
  .hero { padding: var(--ls-space-6) 0; }
  .hero__lead { margin-bottom: var(--ls-space-5); }
  /* A shorter strip on a short fold - there is no fade to leave room for. */
  :root { --ls-trustbar: 140px; }
}
.hero__grid {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 64px); align-items: center;
  width: 100%;
}
/* NOT ch: a ch max-width on this container resolves against its own 1rem
   font size, not the 52px headline inside it, which squeezed the h1 to 303px
   and seven lines. A rem cap is measured in the same units either way. */
.hero__copy { max-width: 34rem; }
.hero__title { margin: 0 0 var(--ls-space-4); color: #fff; }
.hero__lead { max-width: 46ch; margin: 0 0 var(--ls-space-6); font-size: 1.0625rem; color: rgba(255,255,255,0.78); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--ls-space-3); }
.hero__note { margin: var(--ls-space-4) 0 0; font-size: 0.8125rem; color: rgba(255,255,255,0.55); }


/* --- Client logos -------------------------------------------------------- */
/* A clean band, hard edge. See the note in the design pass: fading a light
   band out of a dark one always reads as a glow. The strip earns its place by
   being quiet and well-spaced, not by dissolving. */
.logos {
  min-height: var(--ls-trustbar);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--ls-space-6) 0;
  overflow: hidden;
  background-color: var(--ls-accent);
}
.logos__label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(12px, 2vw, 22px);
  margin: 0 0 var(--ls-space-5);
  font-size: 0.75rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase; color: var(--ls-ink);
}
/* Rules either side turn a floating caption into a proper divider label, and
   they fade outward so they stop rather than end. Neutral, so they sit with the
   greyed-back logos rather than reintroducing the accent the label just lost. */
.logos__label::before,
.logos__label::after {
  content: "";
  height: 1px;
  flex: 0 1 clamp(28px, 8vw, 104px);
  background-image: linear-gradient(90deg, transparent, var(--ls-border-mid));
}
.logos__label::after {
  background-image: linear-gradient(90deg, var(--ls-border-mid), transparent);
}

/* The strip scrolls itself, forever. --logo-gap is used BOTH inside a track and
   between tracks, so the seam where one copy hands over to the next is spaced
   exactly like every other gap and cannot be spotted. */
.logos__marquee {
  --logo-gap: clamp(36px, 5vw, 72px);
  display: flex;
  gap: var(--logo-gap);
  /* Fade both ends so logos arrive and leave rather than being sliced off. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.logos__marquee:hover .logos__track { animation-play-state: paused; }

.logos__track {
  display: flex; align-items: center; flex: 0 0 auto;
  gap: var(--logo-gap);
  list-style: none; margin: 0; padding: 0;
  animation: logos-scroll 46s linear infinite;
}
/* One track width PLUS one gap: that is the distance to where the next copy
   starts, so copy two lands exactly where copy one began. */
@keyframes logos-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-100% - var(--logo-gap))); }
}

.logos__item { display: flex; align-items: center; }
/* Muted by default, full colour on the one you point at. Seven brands in seven
   unrelated palettes is a lot of noise in a moving strip; greyed back they read
   as one row, and the hover picks out whichever the visitor is looking at.
   The strip already pauses on hover, so there is something steady to point at. */
.logos__track img {
  max-height: 40px; max-width: 170px; width: auto; height: auto;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.68;
  transition: filter var(--ls-ease), opacity var(--ls-ease);
}
.logos__item:hover img { filter: grayscale(0); opacity: 1; }

/* Property Refresh ships as a white knockout, invisible on a light strip, so it
   is inverted to black instead. The artwork is a single flat white - there is no
   colour in the file to reveal - so hover only lifts it out of the dimming. */
.logos__item--knockout img { filter: invert(1) grayscale(1); }
.logos__item--knockout:hover img { filter: invert(1); }

/* Movement is decoration. Without it the strip is a plain centred row, and the
   duplicate tracks would only repeat the same logos, so they come out. */
@media (prefers-reduced-motion: reduce) {
  .logos__marquee {
    flex-wrap: wrap; justify-content: center;
    -webkit-mask-image: none; mask-image: none;
  }
  .logos__track { animation: none; flex-wrap: wrap; justify-content: center; }
  .logos__track[aria-hidden="true"] { display: none; }
}

/* --- Home: what we do ----------------------------------------------------
   Was a dark band with the photograph washed out behind the copy. Two problems:
   the picture was doing nothing at 7% visibility, and it put a third dark mass
   directly under the hero, so the trust strip between them read as a seam.

   Now it is a light section and the photograph is an element in the layout with
   its own edges, rather than a texture behind text. Copy and figure share the
   left column; the four points run down the right.
   ------------------------------------------------------------------------- */
.ways { padding: var(--ls-section) 0; background-color: var(--ls-accent); }

.ways__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: clamp(40px, 6vw, 88px);
  align-items: start;
}

.ways__aside { position: sticky; top: calc(var(--ls-header-h) + var(--ls-space-6)); }
.ways__title { margin: 0 0 var(--ls-space-4); }
.ways__lead { margin: 0; font-size: 1.0625rem; color: var(--ls-text); }

/* The photograph was floating on white with nothing to sit on. A dark plate
   offset behind it gives the light section a piece of real weight, and the
   offset reads as depth rather than as a border.

   isolation + z-index keeps the plate and the glow behind the image without
   either escaping to sit over the copy above. */
.ways__figure,
.media-plate {
  position: relative;
  isolation: isolate;
  margin: var(--ls-space-7) 0 0;
}
.ways__figure::before,
.media-plate::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* top right bottom left - shifted down and to the right of the image. */
  inset: 28px -28px -28px 28px;
  border-radius: var(--ls-radius-lg);
  background-color: var(--ls-ink);
  background-image: var(--ls-band);
}
/* A brand glow bleeding out from under the corner. Sits behind the plate. */
.ways__figure::after,
.media-plate::after {
  content: "";
  position: absolute;
  z-index: -2;
  right: -72px;
  bottom: -72px;
  width: 240px;
  height: 240px;
  background: radial-gradient(circle, rgba(91, 81, 160, 0.28) 0%, rgba(91, 81, 160, 0) 70%);
  pointer-events: none;
}

/* Some artwork is a cutout, not a photograph: no plate, no crop, no frame,
   which would only draw a box around empty transparency. Must sit AFTER the
   two rules above - equal specificity, so source order decides. */
.media-plate--bare::before,
.media-plate--bare::after { content: none; }
.media-plate--bare img {
  aspect-ratio: auto;
  object-fit: contain;
  border: 0;
  border-radius: 0;
}
.ways__figure img,
.media-plate img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  /* The illustration's subject sits low in the frame; a 4:3 crop from the top
     keeps the table and the people and drops the empty ceiling. */
  object-position: center 62%;
  border-radius: var(--ls-radius-lg);
  border: 1px solid var(--ls-border);
}

.ways__list {
  display: grid;
  gap: 0;
  list-style: none; margin: 0; padding: 0;
}
.ways__item {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--ls-space-5);
  padding: var(--ls-space-6) 0;
}

/* The hairline draws itself in from the left as the row arrives, on the same
   delay as the row, so the two read as one movement. */
.ways__item::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, var(--ls-ink), var(--ls-border));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 640ms cubic-bezier(0.4, 0, 0.2, 1);
  transition-delay: var(--reveal-delay, 0ms);
}
/* Drawn when the row animates in - or immediately, when nothing is animating. */
.ways__item.is-in::before,
.ways__list:not(.js-reveal) .ways__item::before { transform: scaleX(1); }

/* Longer and further than the default, because these are big blocks of copy
   rather than list rows and a short hop reads as a twitch. */
.ways__list.js-reveal { --reveal-from: -36px; --reveal-dur: 620ms; }

/* Solid dark tiles rather than small purple numerals. Repeated four times down
   the column they give the right-hand side the dark presence the left side gets
   from the plate, without turning the section into a split. */
.ways__num,
.num-tile {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--ls-radius-md);
  background-color: var(--ls-ink);
  background-image: var(--ls-band);
  color: #fff;
  font-size: 0.8125rem;
  font-weight: var(--ls-fw-bold);
  /* Tracking would push a centred two-digit number off-centre to the left. */
  letter-spacing: normal;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--ls-ease);
}
.ways__item:hover .ways__num { background-color: var(--ls-brand); background-image: none; }

/* .ways__num sits in a grid column beside its copy, so it needs no bottom
   margin there. Stacked above a title inside a card it does - matched to
   .step__num so the one numbering device spaces the same way everywhere. */
.num-tile { margin-bottom: var(--ls-space-4); }
.ways__name { margin: 0 0 var(--ls-space-2); font-size: 1.125rem; }
.ways__text {
  margin: 0 0 var(--ls-space-3);
  font-size: 0.9375rem; line-height: 1.7;
  color: var(--ls-text);
}
.ways__link {
  font-size: 0.875rem; font-weight: var(--ls-fw-bold);
  color: var(--ls-brand);
  transition: color var(--ls-ease);
}
.ways__link::after { content: " \2192"; }
.ways__link:hover, .ways__link:focus-visible { color: var(--ls-brand-dark); }

@media (max-width: 991px) {
  .ways__grid { grid-template-columns: 1fr; }
  /* Nothing to stick to once the columns stack. */
  .ways__aside { position: static; }
  .ways__figure, .media-plate { margin-top: var(--ls-space-6); }
  /* Full width here, so a plate hanging 28px past the right edge would push the
     page sideways. Shift it straight down instead. */
  .ways__figure::before, .media-plate::before { inset: 16px 0 -16px 0; }
  .ways__figure::after, .media-plate::after { display: none; }
}

/* --- Quotes -------------------------------------------------------------- */
.quote__text { margin: 0 0 var(--ls-space-5); font-size: 0.9375rem; line-height: 1.7; color: var(--ls-text); }
.quote__by { display: flex; align-items: center; gap: var(--ls-space-3); margin-top: auto; }
.quote__avatar {
  flex: 0 0 auto; width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: 50%;
  background-color: var(--ls-brand-soft); color: var(--ls-brand);
  font-size: 0.8125rem; font-weight: var(--ls-fw-bold);
}
.quote__name { display: block; font-size: 0.9375rem; color: var(--ls-ink); }
.quote__role { display: block; font-size: 0.8125rem; color: var(--ls-muted); }

/* --- Closing CTA --------------------------------------------------------- */
.cta {
  position: relative;
  overflow: hidden;
  padding: clamp(48px, 4.5vw, 72px) 0;
  background-color: var(--ls-ink);
  background-image: var(--ls-band);
}
.cta > .ls-container { position: relative; z-index: 1; }
.cta__inner { max-width: 56ch; margin-inline: auto; text-align: center; }
.cta__title { margin: 0 0 var(--ls-space-3); color: #fff; }
.cta__text { margin: 0 0 var(--ls-space-6); color: rgba(255,255,255,0.78); }
.cta__actions { display: flex; flex-wrap: wrap; gap: var(--ls-space-3); justify-content: center; }

/* --- Footer -------------------------------------------------------------- */
.site-footer {
  background-color: var(--ls-ink);
  padding: var(--ls-section) 0 var(--ls-space-7);
  border-block: 0;
}
.site-footer__top {
  display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 2fr);
  gap: clamp(32px, 5vw, 72px);
  padding-bottom: var(--ls-space-7);
}
.site-footer__logo img { width: 200px; height: auto; display: block; }
.site-footer__blurb { margin: var(--ls-space-4) 0 0; max-width: 42ch; font-size: 0.875rem; color: rgba(255,255,255,0.62); }
.site-footer__cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ls-space-5); }
.site-footer__heading {
  margin: 0 0 var(--ls-space-4);
  font-size: 0.75rem; font-weight: var(--ls-fw-bold);
  letter-spacing: var(--ls-track-eyebrow); text-transform: uppercase; color: #fff;
}
.site-footer__list { list-style: none; margin: 0 0 var(--ls-space-4); padding: 0; display: grid; gap: var(--ls-space-3); }
.site-footer__link { font-size: 0.875rem; color: rgba(255,255,255,0.72); }
.site-footer__link:hover { color: #fff; }
.site-footer__bottom {
  display: flex; flex-wrap: wrap; gap: var(--ls-space-3);
  align-items: center; justify-content: space-between;
  padding-top: var(--ls-space-5);
  border-top: 1px solid rgba(255,255,255,0.12);
}
.site-footer__copyright, .site-footer__legal { margin: 0; font-size: 0.8125rem; color: rgba(255,255,255,0.55); }

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 1024px) {
  .cards--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hero__grid, .split { grid-template-columns: 1fr; }
  .hero__copy, .split__copy { max-width: none; }
  /* Stacked, the figure would push the hero past the viewport, so it shrinks
     and the fixed height is released. */
  .hero { min-height: 0; padding: var(--ls-space-8) 0; }
  .hero__figure img { max-width: 340px; margin-inline: auto; }
  .hero::after { right: -30%; width: 90vw; }
  .site-footer__top { grid-template-columns: 1fr; }
}
@media (max-width: 767px) {
  .cards--4, .cards--3, .cards--2 { grid-template-columns: 1fr; }
  .steps { grid-template-columns: 1fr; }
  .site-footer__cols { grid-template-columns: 1fr; }
  .bar { grid-template-columns: 70px 1fr 34px; }
}

/* --- Mobile carousels (kept last on purpose) ------------------------------------------------------
   The same lists that are grids at desktop become swipeable rails here. Only
   the track changes; the slides keep their own component styling.

   grid-auto-columns is declared ON THE TRACK on purpose. site.js sets
   --per-view on the carousel ROOT precisely so a direct declaration here wins
   over the inherited value - the comment in the JS spells this out.
   ------------------------------------------------------------------------- */
@media (max-width: 767px) {
  [data-carousel-track] {
    /* .svc-list is a plain bordered list, not a grid - without this every
       grid property below is inert and it never becomes a rail. */
    display: grid;
    grid-template-columns: none;
    grid-template-rows: none;
    grid-auto-flow: column;
    grid-auto-columns: 86%;
    gap: var(--ls-space-4);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* Room for a card's shadow and focus ring, which a clipping track would cut. */
    padding-bottom: var(--ls-space-2);
  }
  [data-carousel-track]::-webkit-scrollbar { display: none; }
  [data-carousel-track] > * {
    scroll-snap-align: start;
    min-width: 0;
  }
  /* Cards stretch to the tallest slide so the rail has one baseline. */
  [data-carousel-track] > .card { height: auto; }

  /* The reveal slides items in horizontally. Inside a horizontal scroll
     container that fights the swipe and can leave a slide parked off-centre,
     so the rail shows its content outright. */
  [data-carousel-track].js-reveal .reveal-item {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* The step hairline is drawn per item; in a rail it reads as a slide edge. */
  [data-carousel-track] > .ways__item::before { transform: scaleX(1); }
}

/* Desktop: the shell is inert. The lists keep their grids and site.js hides
   the dots because nothing overflows. */
.ls-carousel__dots[hidden] { display: none; }


/* --- Floating call button (mobile) -----------------------------------------
   A call icon caught in a web in the corner of the screen. Hidden on desktop:
   the header and every section already carry a CTA there, so a permanent
   overlay would only cover content.
   ------------------------------------------------------------------------- */
.fab { display: none; }

@media (max-width: 767px) {
  .fab {
    display: block;
    position: fixed;
    right: 0;
    bottom: 0;
    width: 94px;
    height: 94px;
    z-index: 1030;              /* under Bootstrap's modal, which is 1055 */
    /* Only the button takes taps - the web must not swallow them. */
    pointer-events: none;
  }

  /* Mirrored: the generator draws bottom-LEFT corners, this one sits bottom-right. */
  .fab__btn {
    pointer-events: auto;
    position: absolute;
    right: 18px;
    bottom: 18px;
    width: 58px;
    height: 58px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background-color: var(--ls-brand);
    color: #fff;
    box-shadow: 0 10px 26px rgba(20, 27, 51, 0.45);
    transition: background-color var(--ls-ease), transform var(--ls-ease);
  }
  .fab__btn:hover,
  .fab__btn:focus-visible { background-color: var(--ls-brand-dark); transform: translateY(-2px); }
  .fab__btn:focus-visible { outline: 3px solid var(--ls-brand-light); outline-offset: 3px; }
  /* Stroke, matching the icons everywhere else on the site. */
  .fab__icon {
    width: 24px; height: 24px;
    fill: none; stroke: currentColor; stroke-width: 1.7;
    stroke-linecap: round; stroke-linejoin: round;
    /* The plane's mass sits low-left; nudge it onto the optical centre. */
    transform: translate(1px, -1px);
  }

  /* Once the modal is open the button is behind it and pointless. */
  body.modal-open .fab { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .fab__btn { transition: none; }
  .fab__btn:hover, .fab__btn:focus-visible { transform: none; }
}

/* --- Get Started modal: a real heading -------------------------------------
   It used to open with a visually-hidden title and a bare form. */
.ls-modal__title { margin: 0 0 var(--ls-space-3); font-size: 1.375rem; }
.ls-modal__intro {
  margin: 0 0 var(--ls-space-6);
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--ls-text);
  max-width: 46ch;
}
