/* =====================================================================
   NORTHERN EDGE — components
   Reusable pieces: loader, type roles, buttons, chips, header, nav, mega
   panel, mobile menu, side rail, badge CTA, plates, forms, footer.

   Colour is taken from the contrast contract (--ne-fg, --ne-fg-soft,
   --ne-rule, --ne-accent) which each surface class rebinds in layout.css.
   That is why there are almost no per-surface overrides in this file.
   ===================================================================== */

/* ============================================================== LOADER
   Construction, not a spinner: the ridge draws in, the wordmark fades up,
   and a hazard-stripe bar fills while a percentage counts. It exits by
   splitting along the brand diagonal.

   Hard rules enforced in loader.js: capped at 2.2s, once per session, and
   a CSS keyframe floor so it clears even if the script never arrives.
   ==================================================================== */
.ne-load {
  position: fixed;
  inset: 0;
  z-index: var(--ne-z-loader);
  display: grid;
  place-items: center;
  background-color: transparent;
  /* THE REAL CEILING, and it is this rule rather than any timer in main.js.
     Measured on a throttled 50KB/s connection, main.js does not arrive until
     ~5s, so a JavaScript cap cannot be the guarantee — it cannot run before
     the script lands. This stylesheet is render-blocking, so the overlay is
     not on screen until this animation's clock has already started: the
     overlay therefore lives for at most 1.2s plus a 300ms fade from the
     visitor's first paint, on any connection, with or without scripting.

     main.js aims to beat it so the diagonal split plays. If it is late, this
     simply clears the overlay and the split is skipped.

     The delay is written onto the element by parts/loader.php, from the same
     setting main.js is given, so raising the minimum time on screen cannot leave
     the stylesheet clearing the overlay mid-animation. The fallback here is what
     applies if that property is ever missing. */
  animation: ne-load-floor 300ms var(--ne-ease) var(--ne-load-floor, 2870ms) forwards;
}
@keyframes ne-load-floor {
  to { opacity: 0; visibility: hidden; }
}
/* The non-GSAP exit, and the resting state after the split has played. */
.ne-load.is-done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 300ms var(--ne-ease), visibility 300ms var(--ne-ease);
}
.ne-load[hidden] {
  display: none;
}

/* Two ink panels split along the brand diagonal. GSAP slides them apart;
   the diagonal edge is what makes it read as our device rather than a
   generic curtain. */
.ne-load__panel {
  position: absolute;
  inset: 0;
  background-color: var(--ne-ink);
}
.ne-load__panel--a {
  clip-path: polygon(0 0, 100% 0, 0 100%);
}
.ne-load__panel--b {
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
}

.ne-load__inner {
  position: relative;
  z-index: 2;
  width: min(420px, 78vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ne-space-6);
  text-align: center;
}

/* Ridge, drawn with stroke-dashoffset. */
.ne-load__ridge {
  width: 132px;
  height: 40px;
  fill: none;
  stroke: var(--ne-white);
  stroke-width: 3;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.ne-load__mark {
  width: 190px;
  height: auto;
  opacity: 0;
}

/* The bar block is the thing the eye follows, so the percentage is the
   largest element in it rather than a caption beside it. The whole overlay
   is gone inside 1.8s — at that speed nothing subtle registers. */
.ne-load__bar {
  width: 100%;
  display: grid;
  gap: 12px;
  justify-items: stretch;
}
.ne-load__pct {
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 6vw, 4rem);
  line-height: 0.9;
  letter-spacing: var(--ne-ls-display);
  font-variant-numeric: tabular-nums;
  color: var(--ne-white);
  /* The overlay is a centred column; a left-aligned figure inside it read as
     a mistake rather than a choice. */
  text-align: center;
}
.ne-load__track {
  position: relative;
  height: 10px;
  width: 100%;
  overflow: hidden;
  background-color: var(--ne-steel);
}
/* Appearance two of three for the hazard stripe. 45°, 12px pitch, amber on
   ink, drifting sideways as the bar fills — thin low-contrast stripes read
   as a plain progress bar and lose the construction cue entirely. */
.ne-load__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background-image: repeating-linear-gradient(
    45deg,
    var(--ne-amber) 0 12px,
    var(--ne-ink) 12px 24px
  );
  background-size: 34px 34px;
  animation: ne-hazard-drift 600ms linear infinite;
}
@keyframes ne-hazard-drift {
  to { background-position: 34px 0; }
}
.ne-load__status {
  display: block;
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ne-mute);
  text-align: center;
}

/* ------------------------------------------------------------- SKIP LINK */
.ne-skip {
  position: absolute;
  left: 16px;
  top: -80px;
  z-index: 200;
  background-color: var(--ne-amber);
  color: var(--ne-ink);
  font-family: var(--ne-font-utility);
  text-transform: uppercase;
  letter-spacing: var(--ne-ls-nav);
  font-size: var(--ne-fs-label);
  padding: 14px 20px;
  text-decoration: none;
  transition: top var(--ne-dur-fast) var(--ne-ease);
}
.ne-skip:focus {
  top: 16px;
}

/* ============================================================ TYPE ROLES */
.ne-eyebrow {
  display: block;
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-accent);
  margin-bottom: var(--ne-r-eyebrow);
}
/* Retained: the un-rebuilt pages still carry this modifier. The contract
   makes it a no-op, which is the point — it can be deleted on propagation. */
.ne-eyebrow--ink {
  color: var(--ne-accent);
}

.ne-accent {
  color: var(--ne-accent);
}

.ne-h1,
.ne-h2,
.ne-h3,
.ne-h4 {
  font-family: var(--ne-font-display);
  text-transform: uppercase;
}
.ne-h1 {
  font-size: var(--ne-fs-hero);
  line-height: var(--ne-lh-display);
  letter-spacing: var(--ne-ls-display);
  font-weight: 700;
}
.ne-h2 {
  font-size: var(--ne-fs-h2);
  line-height: var(--ne-lh-heading);
  letter-spacing: var(--ne-ls-display);
  font-weight: 700;
}
.ne-h3 {
  font-size: var(--ne-fs-h3);
  line-height: var(--ne-lh-heading);
  font-weight: 700;
}
.ne-h4 {
  font-size: var(--ne-fs-h4);
  line-height: 1.2;
  font-weight: 600;
}

.ne-lead {
  font-size: var(--ne-fs-lead);
  max-width: var(--ne-measure);
  color: var(--ne-fg-soft);
}
.ne-prose {
  max-width: var(--ne-measure);
}
.ne-prose + .ne-prose {
  margin-top: var(--ne-space-4);
}
.ne-muted {
  color: var(--ne-fg-soft);
}
.ne-label {
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
}

/* --------------------------------------------------- SECTION HEADING
   One rhythm, decided here rather than improvised per section.
   eyebrow -> 14px -> heading -> 22px -> lead -> 44px -> content */
.ne-shead {
  margin-bottom: var(--ne-r-content);
  text-align: left;
}
.ne-shead > :is(.ne-h1, .ne-h2, .ne-h3) + :is(.ne-lead, .ne-prose) {
  margin-top: var(--ne-r-lead);
}
.ne-shead > .ne-lead + .ne-prose {
  margin-top: var(--ne-space-4);
}
/* Centred variant. about.html already asked for this class and no rule
   existed, so those section heads were quietly left-aligned. */
.ne-shead--center {
  text-align: center;
  margin-inline: auto;
  max-width: 68ch;
}
.ne-shead--center .ne-lead {
  margin-inline: auto;
}
/* Header row: heading left, the section's single primary action right. */
.ne-shead--row {
  display: grid;
  gap: var(--ne-space-6);
  align-items: end;
}
@media (min-width: 900px) {
  .ne-shead--row {
    grid-template-columns: 1fr auto;
    gap: var(--ne-space-8);
  }
}

/* Word mask for the GSAP heading reveal. Overflow hidden clips the incoming
   line; the padding stops tight boxes cropping glyph bottoms once the
   leading is opened up. */
.ne-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-right: 0.18em;
  padding-bottom: 0.08em;
}
.ne-word__i {
  display: inline-block;
}

/* =================================================================== ICONS */
.ne-icon {
  width: 24px;
  height: 24px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ne-icon--lg { width: 34px; height: 34px; }
.ne-icon--sm { width: 18px; height: 18px; }
.ne-icon--xs { width: 15px; height: 15px; }
/* Brand marks and social glyphs are solid, not stroked. */
.ne-icon--solid {
  fill: currentColor;
  stroke: none;
}
/* One arrow symbol, pointed the other way — a "previous" control rather
   than a second path in the sprite. Replaces the two inline transforms that
   were the last style attributes in the build. */
.ne-icon--flip {
  transform: rotate(180deg);
}

/* ========================================================= KINETIC LABEL
   The single biggest "modern" tell on the reference site, and the cheapest
   to build: the label is rendered twice inside a clipped box and the pair
   slides up on hover, so the incoming copy replaces the outgoing one.

   The duplicate is aria-hidden, so it is announced once.
   ==================================================================== */
.ne-kin {
  position: relative;
  display: block;
  overflow: hidden;
}
.ne-kin__a,
.ne-kin__b {
  display: block;
  transition: transform var(--ne-dur) var(--ne-ease-power2);
}
.ne-kin__b {
  position: absolute;
  inset: 0;
  transform: translateY(100%);
  color: var(--ne-amber);
}
:is(a, button):hover > * > .ne-kin > .ne-kin__a,
:is(a, button):focus-visible > * > .ne-kin > .ne-kin__a,
:is(a, button):hover > .ne-kin > .ne-kin__a,
:is(a, button):focus-visible > .ne-kin > .ne-kin__a {
  transform: translateY(-100%);
}
:is(a, button):hover > * > .ne-kin > .ne-kin__b,
:is(a, button):focus-visible > * > .ne-kin > .ne-kin__b,
:is(a, button):hover > .ne-kin > .ne-kin__b,
:is(a, button):focus-visible > .ne-kin > .ne-kin__b {
  transform: translateY(0);
}

/* ================================================================ BUTTONS
   The signature is a notched corner — cut at 45° for 12px — echoing the
   diagonal on the business card. It reads as cut steel and separates these
   instantly from every rounded-rectangle template button.

   Structure is <a class="ne-btn"><span class="ne-btn__face">…</span></a>.
   The clip-path lives on the face, never on the button, so the focus ring
   on the button is not clipped away with the corner.
   ==================================================================== */
.ne-btn:has(.ne-btn__face) {
  position: relative;
  display: inline-block;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.ne-btn__face {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--ne-space-4);
  min-height: 56px;
  padding: 0 12px 0 28px;
  overflow: hidden;
  isolation: isolate;
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-nav);
  letter-spacing: var(--ne-ls-nav);
  text-transform: uppercase;
  white-space: nowrap;
  transition: color var(--ne-dur) var(--ne-ease);
}
/* The sweep panel. Behind the label, above the face's own background. */
.ne-btn__face::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 380ms var(--ne-ease-power3);
}
.ne-btn__ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  transition: background-color var(--ne-dur) var(--ne-ease),
    color var(--ne-dur) var(--ne-ease);
}
.ne-btn__ico .ne-icon {
  transition: transform var(--ne-dur) var(--ne-ease);
}
.ne-btn:hover .ne-btn__ico .ne-icon {
  transform: translateX(4px);
}

/* ------------------------------------------------------------- primary */
.ne-btn--primary .ne-btn__face {
  background-color: var(--ne-amber);
  color: var(--ne-ink);
  clip-path: polygon(
    0 0,
    calc(100% - var(--ne-notch)) 0,
    100% var(--ne-notch),
    100% 100%,
    0 100%
  );
}
.ne-btn--primary .ne-btn__face::before {
  background-color: var(--ne-ink);
}
.ne-btn--primary:hover .ne-btn__face::before,
.ne-btn--primary:focus-visible .ne-btn__face::before {
  transform: scaleX(1);
}
.ne-btn--primary .ne-btn__ico {
  background-color: var(--ne-ink);
  color: var(--ne-amber);
}
.ne-btn--primary:hover .ne-btn__ico,
.ne-btn--primary:focus-visible .ne-btn__ico {
  background-color: var(--ne-amber);
  color: var(--ne-ink);
}
/* On amber ground the primary inverts: ink box, white label. */
.ne-section--amber .ne-btn--primary .ne-btn__face {
  background-color: var(--ne-ink);
  color: var(--ne-white);
}
.ne-section--amber .ne-btn--primary .ne-btn__face::before {
  background-color: var(--ne-white);
}
.ne-section--amber .ne-btn--primary .ne-kin__b {
  color: var(--ne-ink);
}
.ne-section--amber .ne-btn--primary .ne-btn__ico {
  background-color: var(--ne-amber);
  color: var(--ne-ink);
}

/* --------------------------------------------------------------- ghost
   Notch mirrored to the bottom-left so the pair reads as a set. Amber
   fills from the bottom. */
.ne-btn--ghost .ne-btn__face {
  border: 1px solid var(--ne-accent);
  color: var(--ne-accent);
  background-color: transparent;
  clip-path: polygon(
    0 0,
    100% 0,
    100% 100%,
    var(--ne-notch) 100%,
    0 calc(100% - var(--ne-notch))
  );
}
/* The cut corner needs its own hairline. A border follows the element's four
   rectangular edges; the clip-path then slices the bottom-left away, taking
   the fill AND the border with it and leaving that diagonal completely
   open — which is why the corner looked broken rather than cut.

   This draws a line along the diagonal: a gradient band at 45°, which is
   perpendicular to the cut, sized to the notch. The parent's own clip-path
   trims the outer half of the band, so what survives sits exactly on the
   cut edge. */
.ne-btn--ghost .ne-btn__face::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 1;
  width: calc(var(--ne-notch) + 1px);
  height: calc(var(--ne-notch) + 1px);
  background-image: linear-gradient(
    45deg,
    transparent calc(50% - 1px),
    var(--ne-accent) calc(50% - 1px),
    var(--ne-accent) calc(50% + 1px),
    transparent calc(50% + 1px)
  );
  pointer-events: none;
  transition: background-image var(--ne-dur) var(--ne-ease);
}
/* Once the amber has filled the button the line would show as a seam across
   its own corner, so it goes ink to match the label. */
.ne-btn--ghost:hover .ne-btn__face::after,
.ne-btn--ghost:focus-visible .ne-btn__face::after {
  background-image: linear-gradient(
    45deg,
    transparent calc(50% - 1px),
    var(--ne-ink) calc(50% - 1px),
    var(--ne-ink) calc(50% + 1px),
    transparent calc(50% + 1px)
  );
}
.ne-btn--ghost .ne-btn__face::before {
  background-color: var(--ne-accent);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform 380ms var(--ne-ease-power3);
}
.ne-btn--ghost:hover .ne-btn__face,
.ne-btn--ghost:focus-visible .ne-btn__face {
  color: var(--ne-ink);
}
.ne-btn--ghost:hover .ne-btn__face::before,
.ne-btn--ghost:focus-visible .ne-btn__face::before {
  transform: scaleY(1);
}
.ne-btn--ghost .ne-kin__b {
  color: var(--ne-ink);
}
.ne-btn--ghost .ne-btn__ico {
  color: inherit;
}

/* ---------------------------------------------------------------- text
   Label plus arrow, no box. The one action per bento tile and per row. */
.ne-btn--text .ne-btn__face {
  min-height: 0;
  padding: 4px 0;
  gap: 10px;
  color: var(--ne-accent);
  overflow: visible;
}
.ne-btn--text .ne-btn__face::before {
  content: "";
  inset: auto 0 0 0;
  top: auto;
  height: 1px;
  z-index: 0;
  background-color: var(--ne-accent);
  transition: transform var(--ne-dur-fast) var(--ne-ease-out);
}
.ne-btn--text:hover .ne-btn__face::before,
.ne-btn--text:focus-visible .ne-btn__face::before {
  transform: scaleX(1);
}
.ne-btn--text .ne-btn__ico {
  width: auto;
  height: auto;
  background: none;
}
.ne-btn--text:hover .ne-btn__ico .ne-icon {
  transform: translate(3px, -3px);
}
.ne-btn--text .ne-kin__b {
  color: var(--ne-accent);
}

/* Small variant for tight rows. */
.ne-btn--sm .ne-btn__face {
  min-height: 46px;
  padding-left: 20px;
  letter-spacing: 0.08em;
}

.ne-btn:active .ne-btn__face {
  transform: translateY(1px);
}
.ne-btn[aria-disabled="true"] {
  opacity: 0.4;
  pointer-events: none;
}

.ne-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ne-space-4);
  margin-top: var(--ne-space-7);
}
/* Below the small breakpoint every boxed action takes the full row and drops
   a size. Side by side at 390px they either overflowed or wrapped to two
   ragged half-width boxes; full width also gives a thumb a target it cannot
   miss. Text buttons are excluded — they have no box to fill. */
@media (max-width: 559.98px) {
  .ne-btn-row {
    gap: var(--ne-space-3);
    margin-top: var(--ne-space-6);
  }
  .ne-btn-row .ne-btn:not(.ne-btn--text) {
    width: 100%;
  }
  .ne-btn-row .ne-btn:not(.ne-btn--text) .ne-btn__face {
    width: 100%;
    min-height: 50px;
    padding-left: 20px;
    justify-content: space-between;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
  }
  .ne-btn-row .ne-btn:not(.ne-btn--text) .ne-btn__ico {
    width: 28px;
    height: 28px;
  }
}

/* -------------------------------------------------------------- LEGACY
   The ten pages not yet rebuilt still use <span class="ne-btn__i"> inside
   a self-styled .ne-btn. These rules keep them working untouched; the
   :has() above takes the box off .ne-btn only when a face is present.
   Delete this block when the rest of the site is propagated. */
.ne-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 0 26px;
  border: 1px solid transparent;
  border-radius: var(--ne-radius);
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-nav);
  letter-spacing: var(--ne-ls-nav);
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  transition: background-color 150ms var(--ne-ease), color 150ms var(--ne-ease),
    border-color 150ms var(--ne-ease);
}
.ne-btn__i {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.ne-btn__arrow {
  transition: transform 150ms var(--ne-ease);
}
.ne-btn:hover .ne-btn__arrow {
  transform: translateX(4px);
}
.ne-btn--primary:has(.ne-btn__i) {
  background-color: var(--ne-amber);
  color: var(--ne-ink);
}
.ne-btn--ghost:has(.ne-btn__i) {
  border-color: var(--ne-accent);
  color: var(--ne-fg);
  background-color: transparent;
}
.ne-btn--ghost:has(.ne-btn__i):hover {
  background-color: var(--ne-accent);
  color: var(--ne-ink);
}
.ne-btn--dark:has(.ne-btn__i) {
  background-color: var(--ne-ink);
  color: var(--ne-white);
}
.ne-btn--pill:has(.ne-btn__i) {
  border-radius: var(--ne-radius-pill);
}

/* The line that does the work a price would have done. */
.ne-reassure {
  margin-top: var(--ne-space-4);
  font-family: var(--ne-font-utility);
  font-size: var(--ne-fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ne-fg-soft);
}
/* Bordered "best for" chip. Neutral, so a row of them does not shout. */
.ne-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 13px;
  border: 1px solid var(--ne-rule);
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ne-fg-soft);
}
/* The one chip that is meant to be read as a claim rather than a label.
   index.html already used this modifier with no rule behind it. */
.ne-chip--solid {
  border-color: var(--ne-amber);
  background-color: color-mix(in srgb, var(--ne-amber) 14%, transparent);
  color: var(--ne-amber);
  font-weight: 600;
}
.ne-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ne-space-2);
}

/* ------------------------------------------------------ OPEN/CLOSED CHIP
   Computed in Europe/London time from one hours object, so it cannot drift
   out of step with the hours printed elsewhere. For a trade business whose
   visitor's next move is a phone call, "someone will pick up" is worth more
   than any decorative element on the page.

   Ships with the plain hours as its text; status.js replaces them. If it
   never runs, the hours are still correct. */
.ne-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-fg-soft);
  white-space: nowrap;
}
.ne-status__dot {
  position: relative;
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background-color: var(--ne-mute);
}
.ne-status.is-open {
  color: var(--ne-amber);
}
.ne-status.is-open .ne-status__dot {
  background-color: var(--ne-amber);
}
.ne-status.is-open .ne-status__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background-color: var(--ne-amber);
  animation: ne-ping 2200ms var(--ne-ease-out) infinite;
}
@keyframes ne-ping {
  0% { transform: scale(1); opacity: 0.7; }
  70%, 100% { transform: scale(3.4); opacity: 0; }
}
.ne-section--amber .ne-status,
.ne-section--amber .ne-status.is-open {
  color: var(--ne-ink);
}
.ne-section--amber .ne-status .ne-status__dot,
.ne-section--amber .ne-status.is-open .ne-status__dot,
.ne-section--amber .ne-status.is-open .ne-status__dot::after {
  background-color: var(--ne-ink);
}

/* ============================================================= TOP BAR
   Tier 1. Scrolls away — it is not sticky. */
.ne-topbar {
  display: none;
}
@media (min-width: 992px) {
  .ne-topbar {
    display: flex;
    align-items: center;
    height: var(--ne-topbar-h);
    background-color: var(--ne-graphite);
    color: var(--ne-mute);
    border-bottom: 1px solid var(--ne-steel);
  }
}
.ne-topbar__inner {
  display: flex;
  align-items: center;
  gap: var(--ne-space-7);
  width: 100%;
}
.ne-topbar__item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  white-space: nowrap;
}
.ne-topbar__item .ne-icon {
  width: 15px;
  height: 15px;
  color: var(--ne-amber);
}
.ne-topbar__social {
  display: flex;
  align-items: center;
  gap: var(--ne-space-4);
  margin-left: auto;
}
.ne-topbar__social-label {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-mute);
}
.ne-topbar__social a {
  display: inline-flex;
  color: var(--ne-mute);
  transition: color 150ms var(--ne-ease);
}
.ne-topbar__social a:hover {
  color: var(--ne-amber);
}
.ne-topbar__social .ne-icon {
  width: 15px;
  height: 15px;
}

/* ============================================================== HEADER
   Tier 2. Transparent over the hero with a soft ink gradient behind it,
   then docks to a solid bar past 100px.
   ==================================================================== */
/* All three tiers move as one fixed unit. Docking translates the stack up by
   the height of the hazard rule plus the utility bar, so the utility bar
   leaves and the main bar lands flush at the top — one transform rather than
   an animated `top`, and the header can never be caught with a gap above it
   mid-scroll, which is what went wrong the last time these were separate. */
.ne-headstack {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--ne-z-header);
  transition: transform 260ms var(--ne-ease);
}
.ne-headstack.is-docked {
  transform: translateY(calc((var(--ne-hazard-h) + var(--ne-topbar-h)) * -1));
}
@media (max-width: 991.98px) {
  /* No utility bar below 992px, so there is only the hazard rule to lose. */
  .ne-headstack.is-docked {
    transform: translateY(calc(var(--ne-hazard-h) * -1));
  }
}
/* Retreat on scroll-down below the desktop breakpoint. Declared last so it
   wins over the docked transform. */
.ne-headstack.is-hidden {
  transform: translateY(-100%);
}

.ne-header {
  position: relative;
  height: var(--ne-header-h);
  display: flex;
  align-items: center;
  color: var(--ne-white);
  border-bottom: 1px solid transparent;
  transition: height 260ms var(--ne-ease), background-color 260ms var(--ne-ease),
    border-color 260ms var(--ne-ease), backdrop-filter 260ms var(--ne-ease);
}
/* Soft gradient so the bar reads over a bright photograph without being a
   solid block. Sits behind everything in the bar. */
.ne-header::before {
  content: "";
  position: absolute;
  inset: 0 0 -32px 0;
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--ne-ink) 76%, transparent) 0%,
    transparent 100%
  );
  pointer-events: none;
  transition: opacity 260ms var(--ne-ease);
}
.ne-header.is-docked {
  height: var(--ne-header-h-docked);
  background-color: color-mix(in srgb, var(--ne-ink) 97%, transparent);
  backdrop-filter: blur(8px);
  border-bottom-color: var(--ne-steel);
}
.ne-header.is-docked::before {
  opacity: 0;
}
/* Below the desktop breakpoint the bar is solid from the start rather than
   transparent over the hero. On a phone there is no room for the header and
   the headline to share the same ground: the logo landed on top of the H1
   and both became hard to read. A solid bar also gives the wordmark a
   surface of its own, which is the only way it stays legible at this size. */
@media (max-width: 991.98px) {
  .ne-header {
    background-color: color-mix(in srgb, var(--ne-ink) 97%, transparent);
    backdrop-filter: blur(8px);
    border-bottom-color: var(--ne-steel);
  }
  .ne-header::before {
    opacity: 0;
  }
}
/* Scroll progress. Scrubbed across the whole document by animations.js. */
.ne-header__progress {
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: 100%;
  background-color: var(--ne-amber);
  transform: scaleX(0);
  transform-origin: left center;
  opacity: 0;
  transition: opacity 260ms var(--ne-ease);
}
.ne-header.is-docked .ne-header__progress {
  opacity: 1;
}

/* A grid, not a flex row with margins. The previous flex version let the
   logo and the first nav link overlap between 992 and 1200px, and ran the
   phone number into CONTACT: flex items were shrinking past their content
   because nothing declared which cell was allowed to give. Here the nav is
   the only flexible track (minmax(0, 1fr) — the 0 is what permits it to
   compress), everything else is auto and cannot be squeezed, and a real
   column-gap guarantees the cells can never touch. */
/* Above the scrim, not under it.

   .ne-header::before is absolutely positioned with z-index auto, and this
   row is a static in-flow box — so the painting order put that 76% ink
   gradient OVER the logo, the nav, the phone and the button. At the top of
   the page everything in the bar was sitting behind a dark veil, and the
   moment the header docked the scrim faded to zero and it all brightened.
   That was the logo "looking dull until you scroll".

   The scrim still does its job: it sits between the photograph and the bar's
   contents, which is where it belongs. */
.ne-header__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  column-gap: 28px;
  width: 100%;
  height: 100%;
}
/* Full-height dividers between the header's cells — the detail that makes
   it read as a built bar rather than items floating on a strip. */
.ne-header__cell {
  display: flex;
  align-items: center;
  height: 100%;
  min-width: 0;
}
.ne-header__cell--logo {
  flex: none;
  padding-right: clamp(16px, 2.4vw, 34px);
}
@media (min-width: 992px) {
  .ne-header__cell--logo {
    border-right: 1px solid color-mix(in srgb, var(--ne-white) 14%, transparent);
  }
}
.ne-header__cell--nav {
  min-width: 0;
}
.ne-header__cell--actions {
  display: none;
  gap: clamp(16px, 2vw, 28px);
  padding-left: clamp(16px, 2.4vw, 34px);
  border-left: 1px solid color-mix(in srgb, var(--ne-white) 14%, transparent);
}
@media (min-width: 992px) {
  .ne-header__cell--actions {
    display: flex;
    align-items: center;
  }
}
/* The burger's 13px of internal padding held it well inside the container
   line, so it read as floating rather than as the bar's right-hand end. The
   negative margin pulls the glyph out to the optical edge while the touch
   target keeps its full 48px. */
.ne-header__cell--burger {
  justify-content: flex-end;
  margin-right: -10px;
}
@media (min-width: 992px) {
  .ne-header__cell--burger {
    display: none;
  }
}
/* The pressure band. Between 992 and 1200px there is not enough room for
   full-size nav type, the two-line phone block and the button at their
   desktop settings — so the nav gives first, and the phone label goes,
   rather than letting anything collide. Tested at 1024 and 1180. */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .ne-header__inner {
    column-gap: 18px;
  }
  .ne-header__cell--logo,
  .ne-header__cell--actions {
    padding-inline: 16px;
  }
  .ne-nav {
    gap: 18px;
  }
  .ne-nav__link {
    font-size: 0.75rem;
    letter-spacing: 0.08em;
  }
  .ne-tel__k {
    display: none;
  }
  .ne-tel__v {
    font-size: 1.0625rem;
  }
}

/* Two-line phone block. The label sells the call, the number is the action. */
.ne-tel {
  display: inline-flex;
  flex-direction: column;
  gap: 2px;
  text-decoration: none;
  color: var(--ne-white);
  transition: color 150ms var(--ne-ease);
}
.ne-tel__k {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: 0.625rem;
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-amber);
}
.ne-tel__v {
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: 1.1875rem;
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.ne-tel:hover {
  color: var(--ne-amber);
}

/* ---------------------------------------------------------- LOGO LOCKUP
   The client's own artwork, used as artwork. Its wordmark is black on a
   transparent ground, so every dark surface carries the white-wordmark
   version generated from it — main-logo-light.png — with the amber EDGE
   exactly as supplied. No white plate: the transparency does that job.

   The file is cropped to the visible artwork, so the height below IS the
   optical height. Sizing by height alone lets the width/height attributes
   in the markup reserve the right box before the image lands.

   For a light surface use assets/img/main-logo.png, the original. */
.ne-logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: inherit;
  transition: transform 260ms var(--ne-ease);
  transform-origin: left center;
}
.ne-header.is-docked .ne-logo {
  transform: scale(0.86);
}
.ne-logo__img {
  width: auto;
  height: 46px;
  /* Never let the header grid squeeze the artwork. */
  flex: none;
}
.ne-footer .ne-logo__img {
  height: 74px;
}
/* The wordmark carries a two-line lockup — NORTHERN EDGE over PLANT &
   CONSTRUCTION LTD — and below about 42px that subline stops being type and
   becomes texture, which is what made it look wrong on a phone.

   These heights are the largest the bar can hold: the artwork is 2.4:1, so
   48px tall is already 116px wide, and the docked bar is only 64px. Sizing
   it by width instead would make it 60px+ tall and the header would have to
   grow to take it. */
@media (max-width: 479.98px) {
  .ne-logo__img {
    height: 48px;
  }
}
@media (max-width: 359.98px) {
  .ne-logo__img {
    height: 44px;
  }
}

/* ========================================================= PRIMARY NAV */
.ne-nav {
  display: none;
}
@media (min-width: 992px) {
  .ne-nav {
    display: flex;
    align-items: center;
    gap: clamp(18px, 2vw, 28px);
    height: 100%;
  }
}
.ne-nav__item {
  position: relative;
  display: flex;
  align-items: center;
  height: 100%;
}
.ne-nav__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-nav);
  letter-spacing: var(--ne-ls-nav);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ne-white);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
/* The current page — the one thing that legitimately differs between the
   otherwise byte-identical headers. Amber label plus a permanent 2px rule
   under it, so the state survives the kinetic label swap on hover. */
.ne-nav__link.is-current .ne-kin__a {
  color: var(--ne-amber);
}
.ne-nav__link.is-current::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -7px;
  height: 2px;
  background-color: var(--ne-amber);
}
/* On a service page the Services toggle is the current section, and the
   panel's own card says which page inside it you are on. */
.ne-mega__card.is-current .ne-mega__title {
  color: var(--ne-amber);
}
.ne-mega__card.is-current .ne-mega__media {
  outline: 1px solid var(--ne-amber);
  outline-offset: -1px;
}
.ne-nav__chev {
  width: 14px;
  height: 14px;
  transition: transform var(--ne-dur-fast) var(--ne-ease);
}
.ne-nav__link[aria-expanded="true"] .ne-nav__chev {
  transform: rotate(180deg);
}

/* ------------------------------------------------------------ MEGA PANEL
   Full-width, revealed with a clip-path wipe rather than a plain fade.
   Desktop only — below 992px the services sit in the mobile list. */
.ne-mega {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: var(--ne-z-mega);
  background-color: color-mix(in srgb, var(--ne-ink) 97%, transparent);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--ne-steel);
  border-bottom: 3px solid var(--ne-amber);
  padding-block: var(--ne-space-7);
  clip-path: inset(0 0 100% 0);
}
.ne-mega[hidden] {
  display: none;
}
@media (max-width: 991.98px) {
  .ne-mega {
    display: none;
  }
}
.ne-mega__inner {
  display: grid;
  gap: var(--ne-space-6);
  grid-template-columns: repeat(4, 1fr) 0.8fr;
  align-items: start;
}
.ne-mega__card {
  display: flex;
  flex-direction: column;
  gap: var(--ne-space-3);
  text-decoration: none;
  color: var(--ne-white);
}
.ne-mega__media {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  background-color: var(--ne-graphite);
}
.ne-mega__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--ne-dur-slow) var(--ne-ease-out);
}
.ne-mega__card:hover .ne-mega__media img,
.ne-mega__card:focus-visible .ne-mega__media img {
  transform: scale(1.06);
}
.ne-mega__title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-nav);
  letter-spacing: var(--ne-ls-nav);
  text-transform: uppercase;
}
.ne-mega__title .ne-icon {
  width: 14px;
  height: 14px;
  color: var(--ne-amber);
  transition: transform 150ms var(--ne-ease);
}
.ne-mega__card:hover .ne-mega__title .ne-icon {
  transform: translate(3px, -3px);
}
.ne-mega__line {
  font-size: var(--ne-fs-small);
  color: var(--ne-mute);
}
/* Fifth column: the two machines as direct links. */
.ne-mega__fleet {
  border-left: 1px solid var(--ne-steel);
  padding-left: var(--ne-space-6);
  align-self: stretch;
}
.ne-mega__fleet-h {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-amber);
  margin-bottom: var(--ne-space-4);
}
.ne-mega__machine {
  display: block;
  padding-block: var(--ne-space-3);
  border-bottom: 1px solid var(--ne-steel);
  text-decoration: none;
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-small);
  color: var(--ne-mute);
  transition: color 150ms var(--ne-ease);
}
.ne-mega__machine:hover {
  color: var(--ne-amber);
}
.ne-mega__machine b {
  display: block;
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: 1rem;
  text-transform: uppercase;
  color: var(--ne-white);
}

/* ========================================================= MOBILE MENU */
.ne-burger {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  width: 48px;
  height: 48px;
  padding: 13px;
  border: 1px solid color-mix(in srgb, var(--ne-white) 22%, transparent);
  background-color: transparent;
  cursor: pointer;
  z-index: calc(var(--ne-z-menu) + 5);
}
@media (min-width: 992px) {
  .ne-burger {
    display: none;
  }
}
.ne-burger__bar {
  display: block;
  height: 2px;
  width: 100%;
  background-color: var(--ne-white);
  transition: transform 240ms var(--ne-ease), opacity 160ms var(--ne-ease);
}
.ne-burger.is-open .ne-burger__bar:nth-child(1) {
  transform: translateY(4.5px) rotate(45deg);
}
.ne-burger.is-open .ne-burger__bar:nth-child(2) {
  transform: translateY(-4.5px) rotate(-45deg);
}

.ne-menu {
  position: fixed;
  inset: 0;
  z-index: var(--ne-z-menu);
  background-color: var(--ne-ink);
  color: var(--ne-white);
  padding: calc(var(--ne-header-h) + 28px) clamp(20px, 6vw, 48px) 48px;
  overflow-y: auto;
  /* Diagonal wipe on the brand angle. */
  clip-path: polygon(0 0, 100% 0, 100% 0, 0 8%);
  transition: clip-path 420ms var(--ne-ease-out);
}
.ne-menu[hidden] {
  display: none;
}
.ne-menu.is-open {
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
.ne-menu__list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--ne-steel);
}
.ne-menu__item {
  border-bottom: 1px solid var(--ne-steel);
}
.ne-menu__link {
  display: flex;
  align-items: baseline;
  gap: var(--ne-space-4);
  padding: 16px 0;
  font-family: var(--ne-font-display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(2rem, 9vw, 3rem);
  line-height: 1;
  letter-spacing: var(--ne-ls-display);
  text-decoration: none;
  color: var(--ne-white);
}
.ne-menu__n {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  color: var(--ne-amber);
  flex: none;
}
.ne-menu__link.is-current {
  color: var(--ne-amber);
}
.ne-menu__foot {
  margin-top: var(--ne-space-8);
  display: grid;
  gap: var(--ne-space-5);
}
.ne-menu__hours {
  font-family: var(--ne-font-utility);
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-mute);
}

/* ============================================================ SIDE RAIL
   Fills the dead left margin, reinforces the services, and gives the page
   the sense of a designed frame. Desktop only, decorative — every word and
   link in it exists elsewhere in the document.
   ==================================================================== */
.ne-rail {
  display: none;
}
@media (min-width: 1200px) {
  .ne-rail {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: var(--ne-z-rail);
    width: var(--ne-rail-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--ne-space-7);
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--ne-dur-slow) var(--ne-ease);
  }
  .ne-rail.is-shown {
    opacity: 1;
  }
  .ne-rail > * {
    pointer-events: auto;
  }
}
.ne-rail__line {
  width: 1px;
  height: clamp(40px, 8vh, 90px);
  background-color: var(--ne-amber);
}
.ne-rail__words {
  writing-mode: vertical-rl;
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: 0.625rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ne-mute);
  white-space: nowrap;
}
.ne-rail__social {
  display: flex;
  flex-direction: column;
  gap: var(--ne-space-4);
}
.ne-rail__social a {
  display: inline-flex;
  color: var(--ne-mute);
  transition: color 150ms var(--ne-ease);
}
.ne-rail__social a:hover {
  color: var(--ne-amber);
}

/* ======================================================= WHATSAPP CHAT
   The client runs a WhatsApp Business account and asked for a direct chat
   link on the site. It joins the floating stack one slot above the
   back-to-top button. The brand mark carries its own green disc, so the
   anchor stays transparent rather than wrapping it in a second circle.
   ==================================================================== */
.ne-whatsapp {
  position: fixed;
  right: clamp(16px, 2.5vw, 40px);
  bottom: clamp(16px, 2.5vw, 40px);
  z-index: var(--ne-z-badge);
  display: inline-flex;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  box-shadow: 0 6px 18px rgb(0 0 0 / 0.22);
  transition: bottom var(--ne-dur) var(--ne-ease-out),
    transform var(--ne-dur) var(--ne-ease-out),
    box-shadow var(--ne-dur) var(--ne-ease);
}
/* The arrow only appears deep into a page. Until it does, the WhatsApp mark
   takes the bottom slot itself instead of hovering above an empty gap, then
   lifts clear once the arrow arrives underneath it. */
.ne-whatsapp.is-raised {
  bottom: calc(clamp(16px, 2.5vw, 40px) + 60px);
}
.ne-whatsapp:hover,
.ne-whatsapp:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgb(0 0 0 / 0.28);
}
.ne-whatsapp svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* Above the sticky call bar, same as the arrow. */
@media (max-width: 991.98px) {
  .ne-whatsapp {
    width: 46px;
    height: 46px;
    bottom: calc(var(--ne-callbar-h) + 14px + env(safe-area-inset-bottom));
  }
  .ne-whatsapp.is-raised {
    bottom: calc(var(--ne-callbar-h) + 14px + 52px + env(safe-area-inset-bottom));
  }
}

/* ========================================================== BACK TO TOP
   These pages run long — the service pages are six or seven sections — and
   a phone visitor at the bottom has no quick way back to the navigation.
   The circle that used to sit here was a fourth copy of the quote button
   and it landed on the right-aligned values of the spec plates.
   ==================================================================== */
.ne-totop {
  position: fixed;
  right: clamp(16px, 2.5vw, 40px);
  bottom: clamp(16px, 2.5vw, 40px);
  z-index: var(--ne-z-badge);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--ne-amber);
  border-radius: 50%;
  background-color: var(--ne-amber);
  color: var(--ne-ink);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity var(--ne-dur) var(--ne-ease),
    transform var(--ne-dur) var(--ne-ease-out),
    background-color var(--ne-dur) var(--ne-ease),
    color var(--ne-dur) var(--ne-ease), visibility var(--ne-dur);
}
.ne-totop.is-shown {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.ne-totop:hover,
.ne-totop:focus-visible {
  background-color: var(--ne-ink);
  color: var(--ne-amber);
}
/* One arrow symbol in the sprite, turned to point up. */
.ne-totop .ne-icon {
  width: 22px;
  height: 22px;
  stroke-width: 2;
  transform: rotate(-90deg);
}
/* Above the sticky call bar, which owns the bottom of a phone screen. */
@media (max-width: 991.98px) {
  .ne-totop {
    width: 42px;
    height: 42px;
    bottom: calc(var(--ne-callbar-h) + 14px + env(safe-area-inset-bottom));
  }
}

/* ===================================================== STICKY CALL BAR */
.ne-callbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--ne-z-callbar);
  display: grid;
  grid-template-columns: 1fr 1fr;
  transform: translateY(110%);
  transition: transform 240ms var(--ne-ease);
  padding-bottom: env(safe-area-inset-bottom);
}
.ne-callbar.is-shown {
  transform: none;
}
/* Out of the way while the menu is open: the call bar sits above the panel in
   the stack, and the menu has its own call button in its foot. */
html.is-locked .ne-callbar,
html.is-locked .ne-callbar.is-shown {
  transform: translateY(110%);
}
@media (min-width: 992px) {
  .ne-callbar {
    display: none;
  }
}
.ne-callbar__btn {
  min-height: var(--ne-callbar-h);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-nav);
  letter-spacing: var(--ne-ls-nav);
  text-transform: uppercase;
  text-decoration: none;
}
.ne-callbar__btn--call {
  background-color: var(--ne-amber);
  color: var(--ne-ink);
}
.ne-callbar__btn--quote {
  background-color: var(--ne-graphite);
  color: var(--ne-white);
  border-left: 1px solid var(--ne-steel);
}


/* =========================================================== BREADCRUMBS
   The service page heroes no longer carry one — they were reading as a copy
   of the homepage hero — but the three legal pages still do, where a way
   back up is genuinely useful. */
.ne-crumbs {
  font-family: var(--ne-font-utility);
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-fg-soft);
  margin-bottom: var(--ne-space-5);
}
.ne-crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 0;
}
.ne-crumbs a {
  text-decoration: none;
}
.ne-crumbs a:hover {
  color: var(--ne-accent);
}

/* ============================================== HAIRLINE PLATE (shared)
   The one data-display pattern on the site: hairline rows, no boxes, no
   icons. Used by the fleet spec plates, the includes plate, the opening
   hours and the comparison rows. */
.ne-plate {
  border-top: 1px solid var(--ne-rule);
}
.ne-plate__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ne-space-4);
  min-height: 46px;
  padding-block: 11px;
  border-bottom: 1px solid var(--ne-rule);
}
.ne-plate__k {
  font-family: var(--ne-font-utility);
  font-weight: 500;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-fg-soft);
}
.ne-plate__v {
  font-size: var(--ne-fs-small);
  font-weight: 500;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ne-plate__v a {
  text-decoration: none;
}
.ne-plate__v a:hover {
  color: var(--ne-accent);
}
/* The plate as a standalone panel: bordered, with a 2px amber top edge so
   it reads as a spec plate rather than a stray table adrift in a column. */
.ne-plate--panel {
  border: 1px solid var(--ne-rule);
  border-top: 2px solid var(--ne-amber);
  padding-inline: clamp(16px, 2vw, 24px);
  background-color: color-mix(in srgb, var(--ne-graphite) 55%, transparent);
}
.ne-section--light .ne-plate--panel {
  background-color: var(--ne-white);
}
.ne-plate--panel .ne-plate__row:last-child {
  border-bottom: 0;
}
.ne-platenote {
  margin-top: var(--ne-space-4);
  font-family: var(--ne-font-utility);
  font-size: var(--ne-fs-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ne-fg-soft);
}

/* =============================================================== FORM
   A white panel on the light section, with an amber top edge so it still
   reads as the page's one piece of business. It rebinds the contrast
   contract to the light reading rather than inheriting the dark one — a
   panel cannot be white and keep white labels.

   Every field below states its own colours explicitly. Inheriting them is
   what produced near-black inputs inside a dark panel: legible enough in a
   screenshot, but nothing like a form you want to fill in. */
.ne-form {
  border: 1px solid color-mix(in srgb, var(--ne-ink) 12%, transparent);
  border-top: 3px solid var(--ne-amber);
  background-color: var(--ne-white);
  box-shadow: 0 14px 38px rgba(11, 11, 12, 0.07);
  --ne-fg: var(--ne-ink);
  --ne-fg-soft: var(--ne-mute-ink);
  --ne-rule: color-mix(in srgb, var(--ne-ink) 12%, transparent);
  --ne-accent: var(--ne-amber-deep);
  color: var(--ne-fg);
  padding: clamp(20px, 4vw, 40px);
}
.ne-field {
  margin-bottom: var(--ne-space-5);
}
.ne-field--wide {
  grid-column: 1 / -1;
}
.ne-field__label {
  display: block;
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-fg);
  margin-bottom: 8px;
}
.ne-field__opt {
  color: var(--ne-fg-soft);
  font-weight: 500;
}
.ne-field__hint {
  margin-top: 6px;
  font-size: 0.8125rem;
  color: var(--ne-fg-soft);
}
.ne-input,
.ne-textarea,
.ne-select {
  width: 100%;
  min-height: 52px;
  padding: 13px 15px;
  font-family: var(--ne-font-body);
  /* 16px stops iOS zooming the viewport on focus. */
  font-size: 16px;
  color: var(--ne-ink);
  /* A hair off white, so the field reads as an inset rather than as a hole
     cut in the panel. */
  background-color: #fbfbfa;
  border: 1px solid color-mix(in srgb, var(--ne-ink) 20%, transparent);
  border-radius: var(--ne-radius);
  transition: border-color 150ms var(--ne-ease), box-shadow 150ms var(--ne-ease),
    background-color 150ms var(--ne-ease);
}
.ne-input::placeholder,
.ne-textarea::placeholder {
  color: color-mix(in srgb, var(--ne-ink) 42%, transparent);
}
.ne-textarea {
  min-height: 132px;
  line-height: 1.6;
  resize: vertical;
}
.ne-input:focus,
.ne-textarea:focus,
.ne-select:focus {
  outline: none;
  background-color: var(--ne-white);
  border-color: var(--ne-amber-deep);
  box-shadow: 0 0 0 3px rgba(245, 166, 35, 0.22);
}
.ne-input[aria-invalid="true"],
.ne-textarea[aria-invalid="true"],
.ne-select[aria-invalid="true"] {
  border-color: var(--ne-error-ink);
}
/* The error text is hidden by the hidden attribute until a submit fails —
   and the display: block below was overriding that attribute, so every
   message on the contact form was on screen from the moment the page
   loaded. The guard is what makes the attribute win again. */
.ne-error {
  display: block;
  margin-top: 6px;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ne-error-ink);
}
.ne-error[hidden] {
  display: none;
}
.ne-fieldset {
  border: 1px solid var(--ne-rule);
  padding: var(--ne-space-4);
  margin: 0 0 var(--ne-space-5);
}
.ne-fieldset legend {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-fg);
  padding-inline: 8px;
}
.ne-choice {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  /* The row must be allowed to shrink inside the fieldset, or a wide option
     pushes the whole group past the panel edge. */
  min-width: 0;
}
/* Every option takes an equal share of the row rather than sizing itself to its
   own label, so a group reads as one control instead of three loose boxes. The
   basis is the wrap point: below it they stack, and each row still divides
   evenly. */
.ne-choice__opt {
  display: inline-flex;
  flex: 1 1 11rem;
  align-items: center;
  gap: 12px;
  min-width: 0;
  min-height: 52px;
  padding: 0 18px;
  border: 1px solid color-mix(in srgb, var(--ne-ink) 20%, transparent);
  background-color: #fbfbfa;
  cursor: pointer;
  font-size: 0.9375rem;
  line-height: 1.3;
  transition:
    border-color 150ms var(--ne-ease),
    background-color 150ms var(--ne-ease),
    box-shadow 150ms var(--ne-ease);
}
.ne-choice__opt:hover {
  border-color: var(--ne-amber-deep);
  background-color: var(--ne-white);
}
/* The checked option should be readable as chosen without hunting for the mark.
   The weight is deliberately NOT changed: bolding the label made the tile wider
   and nudged the others along the row every time the choice changed. The second
   ring does that job instead, drawn with a shadow so it takes no space. */
.ne-choice__opt:has(input:checked) {
  border-color: var(--ne-amber-deep);
  background-color: color-mix(in srgb, var(--ne-amber) 10%, var(--ne-white));
  box-shadow: inset 0 0 0 1px var(--ne-amber-deep);
}
/* The ring goes around the tile being chosen, not around the small input inside
   it, because the tile is what the pointer and the eye treat as the control. */
.ne-choice__opt:has(input:focus-visible) {
  outline: 2px solid var(--ne-amber-deep);
  outline-offset: 2px;
}
.ne-choice__opt > span {
  min-width: 0;
  overflow-wrap: break-word;
}

/* --------------------------------------------------------------- the marks

   Drawn here rather than handed to accent-color. The browser's own rendering of
   accent-color is a thick filled donut, which read as the heaviest thing on the
   form; these are a hairline circle and a small amber centre, at the weight the
   rest of the form is drawn at. */
.ne-choice__opt input,
.ne-check input {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  border: 1.5px solid color-mix(in srgb, var(--ne-ink) 32%, transparent);
  background-color: var(--ne-white);
  cursor: pointer;
  transition:
    border-color 150ms var(--ne-ease),
    background-color 150ms var(--ne-ease),
    box-shadow 150ms var(--ne-ease);
}
.ne-choice__opt input {
  border-radius: 50%;
}
.ne-choice__opt input:checked {
  border-color: var(--ne-amber-deep);
  /* A white gap, then the amber centre. Insets rather than a pseudo-element, so
     it works on a replaced element. */
  box-shadow:
    inset 0 0 0 3px var(--ne-white),
    inset 0 0 0 20px var(--ne-amber-deep);
}
.ne-choice__opt input:focus-visible,
.ne-check input:focus-visible {
  /* The tile carries the ring. */
  outline: none;
}
.ne-choice__opt:hover input:not(:checked),
.ne-check:hover input:not(:checked) {
  border-color: var(--ne-amber-deep);
}

.ne-check {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  min-height: 48px;
  font-size: 0.9375rem;
  cursor: pointer;
}
.ne-check input {
  margin-top: 3px;
  border-radius: 3px;
}
/* The tick is a mask rather than a background image, so it takes its colour from
   the box and there is no second asset to keep in step. */
.ne-check input:checked {
  border-color: var(--ne-amber-deep);
  background-color: var(--ne-amber-deep);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}
.ne-check:has(input:focus-visible) {
  outline: 2px solid var(--ne-amber-deep);
  outline-offset: 3px;
}

/* Below 450px the options were breaking out of the panel: each one sized itself
   to its label with nowhere to wrap, so "Not sure — please advise" ran past the
   form's right edge. Each option takes the full row there, at a smaller size
   with tighter padding. */
@media (max-width: 449.98px) {
  .ne-choice {
    gap: 8px;
  }
  .ne-choice__opt {
    flex: 1 1 100%;
    max-width: 100%;
    min-height: 46px;
    padding: 10px 14px;
    gap: 10px;
    font-size: 0.875rem;
  }
  .ne-choice__opt input,
  .ne-check input {
    width: 18px;
    height: 18px;
  }
  .ne-fieldset {
    padding: 12px;
  }
  .ne-fieldset legend {
    padding-inline: 6px;
  }
  .ne-check {
    font-size: 0.875rem;
  }
}
.ne-grid-2 {
  display: grid;
  gap: var(--ne-space-4);
}
@media (min-width: 640px) {
  .ne-grid-2 {
    grid-template-columns: 1fr 1fr;
  }
}
.ne-optional {
  border-top: 1px solid var(--ne-rule);
  margin-top: var(--ne-space-6);
  padding-top: var(--ne-space-6);
}
.ne-success {
  border: 1px solid var(--ne-amber);
  border-left: 3px solid var(--ne-amber);
  background-color: color-mix(in srgb, var(--ne-amber) 8%, var(--ne-white));
  padding: var(--ne-space-6);
  margin-bottom: var(--ne-space-5);
}
/* Honeypot. Hidden by class, and kept out of the a11y tree's way without
   display:none so bots still fill it. */
.ne-hp {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Contact rail — retained for contact.html. */
.ne-rail__tel {
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: var(--ne-fs-tel);
  line-height: 1;
  color: var(--ne-accent);
  text-decoration: none;
  display: inline-block;
  font-variant-numeric: tabular-nums;
}
.ne-rail__block {
  padding-block: var(--ne-space-5);
  border-bottom: 1px solid var(--ne-rule);
}
.ne-rail__block:last-child {
  border-bottom: 0;
}

/* ============================================================== FOOTER */
.ne-footer {
  position: relative;
  background-color: var(--ne-ink);
  --ne-fg: var(--ne-white);
  --ne-fg-soft: var(--ne-mute);
  --ne-rule: var(--ne-steel);
  --ne-accent: var(--ne-amber);
  color: var(--ne-fg);
  border-top: 1px solid var(--ne-steel);
  padding-block: var(--ne-space-9) 0;
}
.ne-footer__grid {
  display: grid;
  gap: var(--ne-space-7);
}
@media (min-width: 768px) {
  .ne-footer__grid {
    grid-template-columns: 1.5fr 1fr 1.2fr 1fr;
  }
}
.ne-footer__h {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-amber);
  margin: 0 0 var(--ne-space-5);
}
.ne-footer__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.ne-footer__list a {
  text-decoration: none;
  color: var(--ne-mute);
  transition: color 150ms var(--ne-ease);
}
.ne-footer__list a:hover {
  color: var(--ne-amber);
}
.ne-footer__contact {
  display: grid;
  gap: 14px;
}
.ne-footer__contact a,
.ne-footer__contact p {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  color: var(--ne-mute);
  text-decoration: none;
}
.ne-footer__contact a:hover {
  color: var(--ne-amber);
}
/* The email must wrap at the @ and the dots, never mid-word and never
   overflowing. anywhere would break it at an arbitrary letter; this keeps
   the local part and the domain intact wherever it can. */
.ne-footer__mail span {
  word-break: break-word;
  overflow-wrap: break-word;
  hyphens: none;
}
.ne-social {
  display: flex;
  gap: 10px;
}
.ne-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--ne-steel);
  color: var(--ne-mute);
  transition: color 150ms var(--ne-ease), border-color 150ms var(--ne-ease);
}
.ne-social a:hover {
  color: var(--ne-amber);
  border-color: var(--ne-amber);
}
/* Oversized wordmark closing the page. Decorative — the footer already
   names the company in the contact column and the legal line. */
.ne-footer__word {
  margin-top: var(--ne-space-9);
  font-family: var(--ne-font-display);
  font-weight: 700;
  text-transform: uppercase;
  font-size: clamp(3rem, 13vw, 11rem);
  line-height: 0.82;
  letter-spacing: -0.03em;
  color: color-mix(in srgb, var(--ne-white) 8%, transparent);
  user-select: none;
}
.ne-footer__legal {
  margin-top: var(--ne-space-7);
  padding-top: var(--ne-space-5);
  padding-bottom: var(--ne-space-7);
  border-top: 1px solid var(--ne-steel);
  display: flex;
  flex-wrap: wrap;
  gap: var(--ne-space-4);
  justify-content: space-between;
  font-size: 0.8125rem;
  color: var(--ne-mute);
}
.ne-footer__legal a {
  text-decoration: none;
}
.ne-footer__legal a:hover {
  color: var(--ne-amber);
}
/* Appearance three of three: the rule that closes the page the way the
   header opened it. */
.ne-footer__hazard {
  height: 2px;
  background-image: var(--ne-hazard);
  opacity: 0.85;
}

/* ========================================================== FACT STRIP
   A horizontal bar of three or four facts that OVERLAPS the page hero's
   bottom edge. It is the first thing on an inner page to cross a boundary,
   and crossing a boundary is most of what separates a designed page from a
   stacked one.

   It is a sibling of the hero, never a child: the hero clips its own
   overflow to hold the diagonal image mask, so anything inside it that
   tried to hang out of the bottom would be cut off.
   ==================================================================== */
.ne-factstrip-wrap {
  position: relative;
  z-index: 3;
  margin-top: -28px;
}
@media (min-width: 992px) {
  .ne-factstrip-wrap {
    margin-top: -40px;
  }
}
.ne-factstrip {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  background-color: var(--ne-graphite);
  border: 1px solid var(--ne-steel);
  border-top: 1px solid var(--ne-amber);
  --ne-fg: var(--ne-white);
  --ne-fg-soft: var(--ne-mute);
  --ne-rule: var(--ne-steel);
  --ne-accent: var(--ne-amber);
  color: var(--ne-fg);
}
@media (min-width: 860px) {
  .ne-factstrip {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
.ne-fact {
  display: flex;
  align-items: center;
  gap: var(--ne-space-4);
  padding: clamp(16px, 2vw, 24px) clamp(16px, 2vw, 28px);
  border-bottom: 1px solid var(--ne-rule);
}
.ne-fact:nth-child(odd) {
  border-right: 1px solid var(--ne-rule);
}
@media (min-width: 860px) {
  .ne-fact,
  .ne-fact:nth-child(odd) {
    border-bottom: 0;
    border-right: 1px solid var(--ne-rule);
  }
  .ne-fact:last-child {
    border-right: 0;
  }
}
/* The icon sits in its own bordered square. Loose in the cell it read as a
   stray glyph next to the text; framed, each fact reads as a badge — which
   is what the row is. */
.ne-fact .ne-icon {
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--ne-amber);
}
.ne-fact__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ne-fact__v {
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: clamp(0.9375rem, 1.4vw, 1.125rem);
  line-height: 1.15;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  overflow-wrap: break-word;
}
/* Below 560px the four facts stack the icon above the value and centre, so
   each one gets the full width of its cell to say its piece. Side by side in
   a 160px cell the values were wrapping to three cramped lines. */
@media (max-width: 559.98px) {
  .ne-fact {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    padding: 18px 12px;
  }
  .ne-fact .ne-icon {
    width: 26px;
    height: 26px;
  }
  .ne-fact__v {
    font-size: 0.8125rem;
    letter-spacing: 0.02em;
  }
}

/* Anchor targets clear the fixed header. */
.ne-anchor {
  scroll-margin-top: calc(var(--ne-header-h-docked) + 24px);
}

/* ============================================================== MATRIX
   A real table, because this is tabular data and a screen reader needs the
   structure. The previous build crammed both options into one right-aligned
   cell as run-on text — on the page whose single most important content is
   the comparison.
   ==================================================================== */
.ne-matrix {
  border: 1px solid var(--ne-rule);
  background-color: color-mix(in srgb, var(--ne-graphite) 60%, transparent);
}
.ne-section--light .ne-matrix {
  background-color: var(--ne-white);
}
.ne-matrix__table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.ne-matrix__table th,
.ne-matrix__table td {
  text-align: left;
  vertical-align: top;
  padding: 15px clamp(14px, 1.6vw, 22px);
  border-bottom: 1px solid var(--ne-rule);
}
.ne-matrix__table tbody tr:last-child th,
.ne-matrix__table tbody tr:last-child td {
  border-bottom: 0;
}
.ne-matrix__table thead th {
  background-color: var(--ne-graphite);
  border-bottom: 1px solid var(--ne-amber);
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-white);
  white-space: nowrap;
}
.ne-section--light .ne-matrix__table thead th {
  background-color: var(--ne-ink);
}
.ne-matrix__table tbody th {
  font-family: var(--ne-font-utility);
  font-weight: 600;
  font-size: var(--ne-fs-label);
  letter-spacing: var(--ne-ls-label);
  text-transform: uppercase;
  color: var(--ne-fg-soft);
  width: 30%;
}
.ne-matrix__table td {
  font-size: var(--ne-fs-small);
  color: var(--ne-fg);
  min-height: 52px;
}
.ne-matrix__val {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}
.ne-matrix__val .ne-icon {
  width: 17px;
  height: 17px;
  margin-top: 3px;
}
.ne-matrix__yes .ne-icon {
  color: var(--ne-accent);
}
.ne-matrix__no .ne-icon {
  color: var(--ne-fg-soft);
}
/* The recommended column, said quietly: a 6% amber wash and a 2px amber top
   edge. It is the higher-value service and the table may say so. */
.ne-matrix .is-rec {
  background-color: rgba(245, 166, 35, 0.06);
}
/* The 2px amber top edge is a background layer rather than a pseudo-element
   so GSAP can draw it left to right — a pseudo-element cannot be tweened.
   It rests at full width, and the script sets the from-state at runtime, so
   the highlight never depends on a script arriving. */
.ne-matrix thead .is-rec {
  color: var(--ne-amber);
  background-color: color-mix(in srgb, var(--ne-amber) 14%, var(--ne-graphite));
  background-image: linear-gradient(var(--ne-amber), var(--ne-amber));
  background-repeat: no-repeat;
  background-position: left top;
  background-size: 100% 2px;
}
.ne-section--light .ne-matrix thead .is-rec {
  background-color: color-mix(in srgb, var(--ne-amber) 18%, var(--ne-ink));
}
/* The per-cell column label. Hidden on desktop where the header row does
   the job, shown in the stacked cards below 768px. A span rather than a
   data- attribute, because this build carries no data- attributes. */
.ne-matrix__lab {
  display: none;
}
@media (max-width: 767.98px) {
  .ne-matrix {
    border: 0;
    background: none;
  }
  .ne-matrix__table,
  .ne-matrix__table tbody,
  .ne-matrix__table tr,
  .ne-matrix__table th,
  .ne-matrix__table td {
    display: block;
    width: auto;
  }
  .ne-matrix__table thead {
    display: none;
  }
  .ne-matrix__table tbody tr {
    border: 1px solid var(--ne-rule);
    background-color: color-mix(in srgb, var(--ne-graphite) 60%, transparent);
    margin-bottom: var(--ne-space-4);
  }
  .ne-section--light .ne-matrix__table tbody tr {
    background-color: var(--ne-white);
  }
  .ne-matrix__table tbody tr:last-child {
    margin-bottom: 0;
  }
  /* The attribute becomes the card's title bar. Ink on either surface, so it
     reads as a header rather than as a washed-out grey band. */
  .ne-matrix__table tbody th {
    width: auto;
    border-bottom: 1px solid var(--ne-rule);
    background-color: var(--ne-graphite);
    color: var(--ne-white);
  }
  .ne-matrix__table tbody tr:last-child th {
    border-bottom: 1px solid var(--ne-rule);
  }
  .ne-matrix__table td {
    border-bottom: 1px solid var(--ne-rule);
  }
  .ne-matrix__table tr td:last-child {
    border-bottom: 0;
  }
  .ne-matrix__lab {
    display: block;
    margin-bottom: 6px;
    font-family: var(--ne-font-utility);
    font-weight: 600;
    font-size: 0.625rem;
    letter-spacing: var(--ne-ls-label);
    text-transform: uppercase;
    color: var(--ne-fg-soft);
  }
}

/* =================================================== CHECKLIST — ROWS
   The inclusion / requirement pattern: hairline rows, a 56px square icon
   with a 1px border, a title and one line. No boxes and no icons in filled
   amber circles, which read cheap at small sizes.
   ==================================================================== */
.ne-checklist--rows {
  display: grid;
  gap: 0;
  margin: 0;
  border-top: 1px solid var(--ne-rule);
}
.ne-checklist--rows .ne-checklist__item {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: var(--ne-space-5);
  align-items: start;
  padding-block: clamp(20px, 2.4vw, 28px);
  border-bottom: 1px solid var(--ne-rule);
  font-weight: 400;
  font-size: var(--ne-fs-body);
}
.ne-checklist--rows.ne-checklist--2 {
  border-top: 0;
}
@media (min-width: 768px) {
  .ne-checklist--rows.ne-checklist--2 {
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(24px, 3vw, 48px);
  }
  .ne-checklist--rows.ne-checklist--2 .ne-checklist__item {
    border-top: 1px solid var(--ne-rule);
    border-bottom: 0;
  }
}
.ne-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  flex: none;
  border: 1px solid var(--ne-rule);
  color: var(--ne-accent);
}
.ne-tick .ne-icon {
  width: 24px;
  height: 24px;
  stroke-width: 1.75;
}
.ne-checklist__t {
  display: block;
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: 1.125rem;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: var(--ne-ls-heading);
  color: var(--ne-fg);
  margin-bottom: 7px;
}
.ne-checklist__d {
  display: block;
  font-weight: 400;
  color: var(--ne-fg-soft);
  max-width: 56ch;
}

/* ======================================================= PROMISE PLATE
   With no prices and no reviews on the site, four policy statements we can
   actually stand behind are what carries the trust. Nothing here is a
   rating, a badge, a client logo or a years-in-business claim.
   ==================================================================== */
.ne-promise {
  border: 1px solid var(--ne-rule);
  border-left: 3px solid var(--ne-amber);
  background-color: var(--ne-graphite);
  --ne-fg: var(--ne-white);
  --ne-fg-soft: var(--ne-mute);
  --ne-rule: var(--ne-steel);
  --ne-accent: var(--ne-amber);
  color: var(--ne-fg);
}
.ne-promise__row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: var(--ne-space-5);
  align-items: start;
  padding: clamp(20px, 2.4vw, 28px) clamp(20px, 2.6vw, 34px);
  border-bottom: 1px solid var(--ne-rule);
}
.ne-promise__row:last-child {
  border-bottom: 0;
}
.ne-promise__t {
  display: block;
  font-family: var(--ne-font-display);
  font-weight: 700;
  font-size: 1.125rem;
  line-height: 1.15;
  text-transform: uppercase;
  margin-bottom: 6px;
}
.ne-promise__d {
  display: block;
  color: var(--ne-fg-soft);
  max-width: 52ch;
}

/* ==================================================== RELATED SERVICES
   Three image cards to the other services, so no page dead-ends into the
   CTA. Same anatomy as the homepage service tiles — the tile component is
   reused wholesale, only the grid is new.
   ==================================================================== */
.ne-related {
  display: grid;
  gap: var(--ne-space-4);
  grid-auto-rows: minmax(280px, auto);
}
@media (min-width: 700px) {
  .ne-related {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1080px) {
  .ne-related {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--ne-space-5);
  }
  .ne-related--2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* On amber the accent word cannot be white — white on amber is 2:1 and
   fails at any size. The split-heading pattern is carried by an ink
   underline instead, which passes and still reads as deliberate. */
.ne-section--amber .ne-accent {
  text-decoration: underline;
  text-decoration-thickness: 3px;
  text-underline-offset: 0.08em;
}
@keyframes ne-pre-out {
  to { opacity: 0; visibility: hidden; }
}
@keyframes ne-pre-fill {
  from { width: 0; }
  to { width: 100%; }

}