/* ═══════════════════════════════════════════════════════════
   CaritaHub — the "newlook" register grammar
   Loaded by every page, after revamp.css (and after each page's
   own legacy stylesheet, where one still exists). Everything
   authored here is namespaced `nl-`; overrides of shared `rv-`
   chrome and of each page's pre-existing legacy classes are
   scoped under `body.nl` so a page that has not yet opted in
   is untouched.

   Design system is pinned: CaritaHub tokens, Lexend, Phosphor.
   What changed is the deployment — a ruled duty-roster register
   instead of a card-and-shadow SaaS grid, colour as wayfinding
   instead of garnish, navy as a field instead of a footer.

   Section 1 is the original index.html grammar. Section 2
   ("INTERIOR PAGES") extends the same grammar to about,
   how-it-works, solutions, contact, news-info and 404 — partly
   with new `nl-` components, partly by retinting each page's
   existing legacy classes through the CSS custom properties
   they already read from (`--navy`, `--blue`, `--border`, `--bg`,
   `--r-md`, `--shadow-sm`, …), so a card grid becomes a flat
   ruled list without every rule being rewritten by hand.
   ═══════════════════════════════════════════════════════════ */

body.nl {
  /* ── Motion. The register wipe. ── */
  --nl-ease:    cubic-bezier(.23, 1, .32, 1);      /* enter / exit */
  /* Pure deceleration, no snap at the end: the reveal curve. */
  --nl-ease-out: cubic-bezier(.16, .84, .32, 1);
  --nl-ease-io: cubic-bezier(.77, 0, .175, 1);     /* on-screen move */
  --nl-wipe:    260ms;

  /* ── The register grid ────────────────────────────────── */
  /* One section-heading size for the whole page. It was written out three
     times and had drifted into three different scales, so the section headings
     no longer read as one rank. Change it here, not at a call site. */
  --nl-h2: clamp(1.75rem, 3.1vw, 2.6rem);
  --nl-max: 1240px;
  --nl-gut: clamp(20px, 3.2vw, 44px);
  --nl-rule: var(--rv-border);
  --nl-rule-strong: #C3CEE6;
  --nl-ink-2: #45536F;              /* tinted from the ink, never gray */
  --nl-paper: #FBFCFE;
  /* The device stage: a light field one step above paper, so the frame
     reads as its own surface without going dark. */
  --nl-stage: #EDF1FA;

  /* ── Corner radius. The register stays ruled and square in its
     structure — rows, grids and hairlines are unchanged — but every
     discrete object (a button, a field, a framed shot, a card) gets
     a soft edge. Two steps only: the surface and its controls. */
  --nl-r:    6px;                   /* framed surfaces, panels, cards */
  --nl-r-sm: 4px;                   /* buttons, fields, tags, tiles */

  /* Label inks. The palette hues are field and mark colours; at 11-13px
     they do not clear 4.5:1, so the text gets a darkened sibling and the
     hue stays on the dot beside it. */
  --nl-ink-live: #14734F;           /* 5.4:1 on paper */
  --nl-ink-soon: #99500C;           /* 5.2:1 on paper */
  --nl-ink-link: var(--rv-primary-dark);

  /* On the navy field, secondary text is tinted from the field's
     own hue — a gray here would read as dead pixels. */
  --nl-on-navy:   #EAEFFB;
  --nl-on-navy-2: #A9B8DE;
  --nl-navy-rule: rgba(174, 192, 236, .22);

  /* ── Colour as wayfinding: one palette role per setting ── */
  --nl-aac:  var(--rv-teal);
  --nl-day:  var(--rv-primary);
  --nl-home: var(--rv-accent);
  --nl-ccms: var(--rv-purple);
  --nl-res:  var(--rv-success);
  /* Vietnam's ISHC clubs: the sixth setting needed a hue of its own, and
     the five palette roles were already spoken for. Rose reads clearly
     against teal, blue, orange, purple and green at rule weight. */
  --nl-ishc: #C2496A;

  background: var(--nl-paper);
  letter-spacing: -.011em;
}


/* ── Browser surfaces. The parts we did not draw still carry
      the design — selection, caret, scrollbar, focus, figures. */
body.nl ::selection            { background: #CFDFFF; color: var(--rv-navy); }
body.nl                        { caret-color: var(--rv-primary); accent-color: var(--rv-primary); }
body.nl                        { scrollbar-color: var(--nl-rule-strong) var(--nl-paper); scrollbar-width: thin; }
:root:has(body.nl) { scrollbar-color: #C3CEE6 #FBFCFE; scrollbar-width: thin; }
:root:has(body.nl)::-webkit-scrollbar,
body.nl ::-webkit-scrollbar          { width: 12px; height: 12px; }
:root:has(body.nl)::-webkit-scrollbar-track,
body.nl ::-webkit-scrollbar-track    { background: #FBFCFE; }
:root:has(body.nl)::-webkit-scrollbar-thumb,
body.nl ::-webkit-scrollbar-thumb    { background: #C3CEE6; border: 3px solid #FBFCFE; border-radius: 8px; }
:root:has(body.nl)::-webkit-scrollbar-thumb:hover,
body.nl ::-webkit-scrollbar-thumb:hover { background: #9FAECC; }
body.nl :focus-visible {
  outline: 2px solid var(--rv-primary);
  outline-offset: 3px;
  border-radius: var(--nl-r-sm);
}
body.nl .nl-field :focus-visible { outline-color: #8FB0FF; }
body.nl a:not([class]) { text-underline-offset: .18em; text-decoration-thickness: 1px; }

/* Every figure in this design is data. Tabular, always. */
.nl-fig { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* ── Shared shell ─────────────────────────────────────────── */
.nl-wrap {
  max-width: var(--nl-max);
  margin: 0 auto;
  padding: 0 var(--nl-gut);
}

/* The register marker. The section labels the existing copy carries, set
   in the document's left margin — a mark on the rule beside the text
   block, never a kicker stacked on top of its own heading. `.nl-reg`
   below is what puts it in the margin; on narrow viewports where there is
   no margin to occupy, it falls back to a ruled label above the block. */
.nl-marker {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: .6875rem; font-weight: 600; line-height: 1.5;
  letter-spacing: .11em; text-transform: uppercase;
  color: var(--nl-ink-2);
  text-wrap: balance;
}
/* The dash registers with the label's first line, not its centre — a long
   label wrapping in the margin column must not float its own mark. */
.nl-marker::before {
  content: ""; flex: none;
  width: 26px; height: 3px; margin-top: .52em;
  background: var(--nl-ink-2);
}
.nl-field .nl-marker { color: var(--nl-on-navy-2); }
.nl-field .nl-marker::before { background: var(--nl-on-navy-2); }

/* The register block. One rule per boundary — a rule earns its place only
   where it separates two things that would otherwise touch.

   `.nl-reg` is a full-width section head: the section it opens already
   carries a full-bleed boundary rule ~50px above, so a second hairline
   here only drew a parallel line with nothing between the two. The head
   keeps the 26px marker dash — that mark opens the block, not a rule.

   `.nl-reg-top` is a column head inside a split block, where nothing else
   draws the boundary and the rule registers with the sibling column's list
   rule. It keeps its line. Currently unused — index.html's certification
   panel, its last caller, now mirrors about.html's label-led layout. */
.nl-reg > .nl-marker,
.nl-reg-top > .nl-marker {
  width: 100%;
  margin-bottom: 4px;
}
.nl-reg-top > .nl-marker {
  padding-top: 14px;
  border-top: 1px solid var(--nl-rule-strong);
}
.nl-field .nl-reg-top > .nl-marker,
.nl-reg-top > .nl-marker { border-top-color: var(--nl-navy-rule); }
.nl-reg > .nl-marker::before,
.nl-reg-top > .nl-marker::before { margin-top: .5em; }

/* Display type. Weight and size carry the emphasis — never a gradient. */
.nl-h1 {
  font-size: clamp(2.3rem, 4.6vw, 3.6rem);
  font-weight: 600; line-height: 1.03;
  letter-spacing: -.038em;
  text-wrap: balance;
}
.nl-h2 {
  font-size: var(--nl-h2);
  font-weight: 600; line-height: 1.11;
  letter-spacing: -.032em;
  text-wrap: balance;
}
.nl-h3 {
  font-size: 1.125rem; font-weight: 600; line-height: 1.28;
  letter-spacing: -.02em;
}
.nl-lead {
  font-size: .9375rem;
  font-weight: 300; line-height: 1.62;
  color: var(--nl-ink-2);
  max-width: 66ch;
}
.nl-body { font-size: .9375rem; font-weight: 300; line-height: 1.62; color: var(--nl-ink-2); max-width: 68ch; }
.nl-field .nl-lead,
.nl-field .nl-body { color: var(--nl-on-navy-2); }

/* Arrow link, on paper and on the navy field. */
.nl-link {
  display: inline-flex; align-items: baseline; gap: 7px;
  font-size: .875rem; font-weight: 600;
  color: var(--nl-ink-link);
  padding-bottom: 3px;
  border-bottom: 1px solid rgba(45,94,199,.34);
  transition: border-color 140ms var(--nl-ease), color 140ms var(--nl-ease);
}
.nl-link i { font-size: 13px; transition: transform 180ms var(--nl-ease); }
/* Zalo has no Phosphor glyph, so this link carries a raster/SVG mark
   instead of an <i>. `align-items: baseline` on the flex row takes a
   replaced element's own bottom edge as its baseline (vertical-align
   has no effect on flex items), which sits visibly higher than the
   Phosphor glyphs' baseline at the same 18px source size — hence the
   translateY nudge, measured to match the WhatsApp/email rows exactly. */
.nl-link img { width: 14px; height: 14px; transform: translateY(1.5px); }
.nl-field .nl-link { color: #A9C4FF; border-bottom-color: rgba(169,196,255,.34); }
@media (hover: hover) and (pointer: fine) {
  .nl-link:hover   { color: #24509F; border-bottom-color: var(--rv-primary-dark); }
  .nl-link:hover i { transform: translateX(3px); }
  .nl-field .nl-link:hover { color: #CBDBFF; border-bottom-color: #A9C4FF; }
}

/* ═══════════════════════════════════════════════════════════
   HEADER — scoped overrides only. The partial itself is
   generated by tools/build-partials.py and is not edited.
   ═══════════════════════════════════════════════════════════ */
body.nl .rv-header {
  background: rgba(251,252,254,.86);
  backdrop-filter: blur(18px) saturate(1.4);
  border-bottom: 1px solid var(--nl-rule);
}
/* The One Left Edge Rule applies to the chrome too. The shared layer
   gutters the header at --rv-gutter (max 32px) while every rule, marker
   and heading on the page sits at --nl-gut (max 44px), so the logo hung
   12px left of the content it sits above. Re-gutter the header — and the
   language row and drawer that share its inner width — onto the page edge. */
body.nl .rv-header-inner,
body.nl .rv-lang-row,
body.nl .rv-drawer {
  padding-left: var(--nl-gut);
  padding-right: var(--nl-gut);
}
body.nl .rv-nav a {
  border-radius: 0; padding: 8px 12px; position: relative;
  font-weight: 400; color: var(--nl-ink-2);      /* 8.0:1, was 4.3:1 */
}
body.nl .rv-nav a:hover,
body.nl .rv-nav a[aria-current="page"] { background: none; color: var(--rv-navy); }
/* The active mark is a register rule under the label, matching the page. */
body.nl .rv-nav a[aria-current="page"]::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px;
  height: 2px; background: var(--rv-primary);
}
body.nl .rv-nav a:hover::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px;
  height: 2px; background: var(--nl-rule-strong);
}
body.nl .rv-nav a[aria-current="page"]:hover::after { background: var(--rv-primary); }
body.nl .rv-nav-dd-inner { border-radius: var(--nl-r); box-shadow: 0 14px 34px rgba(12,27,80,.13); }

/* ═══════════════════════════════════════════════════════════
   1 · HERO — copy on a light ground beside a composed stage.
   The content never depends on a fade or photo contrast.
   ═══════════════════════════════════════════════════════════ */
.nl-hero {
  position: relative;
  background: var(--rv-bg);
  border-bottom: 1px solid var(--nl-rule);
  overflow: clip;
}
.nl-hero .nl-h1 { color: var(--rv-navy); }
.nl-hero .nl-hero-copy .nl-lead { color: var(--nl-ink-2); }
.nl-hero .nl-link { color: var(--rv-primary); border-bottom-color: rgba(46,103,223,.28); }
@media (hover: hover) and (pointer: fine) {
  .nl-hero .nl-link:hover { color: var(--rv-navy); border-bottom-color: var(--rv-primary); }
}
.nl-hero :focus-visible { outline-color: var(--rv-primary); }
.nl-hero-inner {
  position: relative;
  min-height: clamp(610px, 51vw, 680px);
}
.nl-hero-copy {
  position: absolute;
  z-index: 2;
  left: max(var(--nl-gut), calc((100% - var(--nl-max)) / 2 + var(--nl-gut)));
  bottom: clamp(24px, 2.8vw, 38px);
  width: min(503px, calc(100vw - (var(--nl-gut) * 2)));
  padding: clamp(22px, 2.4vw, 30px);
  display: flex; flex-direction: column;
  align-items: flex-start;
  background: #fff;
  opacity: .9;
  border-top: 4px solid var(--rv-primary);
  box-shadow: 0 22px 54px rgba(12,27,80,.22);
}
.nl-hero-copy .nl-h1 { font-size: clamp(3rem, 4.8vw, 4.15rem); }
.nl-hero-copy .nl-lead { margin-top: 14px; max-width: 50ch; line-height: 1.55; }

/* The three markets, drawn as coins, spaced on one baseline, each mark
   reading on its own. On the light ground the white halves of the Singapore
   and Indonesian marks would dissolve into the page, so each coin gets a
   white bed and a hairline to close its edge. */
.nl-hero-flags { display: flex; align-items: center; gap: 10px; margin-top: 18px; }
.nl-hero-flags img {
  width: 36px; height: 36px;
  border-radius: 50%;
  object-fit: cover;
  background: #fff;
  /* The ring is drawn OUTSIDE the coin: an inset shadow on a replaced
     element paints behind the image, so the flag's own pixels would hide it. */
  box-shadow: 0 0 0 1px rgba(12, 27, 80, .13), 0 1px 2px rgba(12, 27, 80, .05);
}

.nl-hero-btns {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 12px 22px;
  margin-top: clamp(18px, 2vw, 24px);
}
.nl-hero-btns .nl-btn { padding: 14px 20px; }

.nl-hero-media {
  position: absolute;
  z-index: 0;
  inset: 0;
}

/* ═══════════════════════════════════════════════════════════
   2 · THE SETTING ROUTER — the page's spine. Five ruled cards on
   one sheet, each carrying its own palette role, capability count,
   sample capabilities, region and device.
   ═══════════════════════════════════════════════════════════ */
.nl-router { border-bottom: 1px solid var(--nl-rule); }
.nl-router-head { padding: clamp(18px, 1.9vw, 24px) 0 clamp(8px, 1vw, 12px); }
.nl-router-head.nl-reg > .nl-marker { padding-top: 11px; margin-bottom: 2px; }
/* One column: marker, heading, then the standfirst directly beneath it.
   The head was a two-column grid with the standfirst set beside the
   heading; read in place it separated the framing from the title it
   frames, so both now stack on the page's left edge. */
.nl-router-head-aside { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }
/* Held deliberately below the page's other h2s: the card sheet directly
   beneath it is the section's real headline, and this must not push it
   past the fold. */
/* Measure only — the size comes from `--nl-h2` like every other section
   heading. This used to set its own smaller scale. */
.nl-router-head .nl-h2 { max-width: 22ch; }
/* Two lines, and the measure is what holds it there. A full-width line of
   14px body across 1240px is ~150 characters — unreadable as prose and
   three lines the moment the copy grows. 78ch keeps it to two on every
   desktop width and reads as a standfirst rather than a paragraph. */
.nl-router-head .nl-body { max-width: 78ch; font-size: .9375rem; margin-top: 12px; }

/* ── The setting sheet ────────────────────────────────────────
   Five cards on one ruled sheet, no tabs. The section's job is to
   show breadth, so a register that hid four fifths of it behind a
   click was arguing against itself: every setting is now readable
   at once, each carrying its own count, samples, region and device.

   Three columns, two rows. Five settings do not divide, so a sixth
   card closes the sheet with the route to the full solutions page —
   every card is then the same object at the same width, and the row
   that would otherwise sit half empty finishes the thought.       */
.nl-setgrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 2.2vw, 30px);
  padding: clamp(24px, 2.6vw, 36px) 0 clamp(38px, 4.6vw, 66px);
}
/* The setting's hue arrives as the card's opening rule — the role the
   tab's 3px mark used to play — so colour stays wayfinding. Only the
   closing corners are eased; the accented edge is the straight one. */
.nl-set {
  display: flex; flex-direction: column;
  min-width: 0;
  background: #fff;
  /* A real border, not an inset ring: the inset shadow paints under the
     child backgrounds, so the media stage covered it on three edges and
     the shot's own grey field ran straight into the page's grey. */
  border: 1px solid var(--nl-rule);
  border-top: 3px solid var(--nl-c);
  border-radius: 0 0 var(--nl-r) var(--nl-r);
  box-shadow: 0 14px 34px rgba(12,27,80,.07);
  overflow: hidden;
}
/* Arrived here from a #setting=… link. The mark is the setting's own hue
   thickened into a ring, held for a beat by newlook.js — enough to answer
   "which card was I sent to?" without becoming a permanent selected state. */
.nl-set { transition: box-shadow 260ms var(--nl-ease), border-color 260ms var(--nl-ease); }
.nl-set[data-called] {
  border-color: var(--nl-c);
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--nl-c) 26%, transparent),
    0 14px 34px rgba(12,27,80,.10);
}

/* The stage becomes the card's own top edge: square, flush under the
   setting's rule, and ruled off the copy instead of floated over it.

   Doubled class on purpose. `.nl-shot` is declared further down this file and
   carries the same specificity, so a plain `.nl-set-shot` lost on source order
   and every declaration in here was silently dead — the stage kept its 6px
   radius and its own shadow inside a card that already draws one. Keep the
   `.nl-shot` prefix, or move this block below `.nl-shot`. */
.nl-shot.nl-set-shot {
  flex: none;
  border-radius: 0;
  box-shadow: none;
  border-bottom: 1px solid var(--nl-rule);
}
/* The art is authored for a half-page slot and now renders at a third of
   it, so its cast shadow is scaled down to match — at card size the
   original reads as a smudge under the device. */
.nl-shot.nl-set-shot img { filter: drop-shadow(0 14px 18px rgba(12,27,80,.22)); }
/* The card's 3px top border already states the setting's hue. Keep the media
   field completely flat so the product cut-outs sit on one consistent surface. */
.nl-shot.nl-set-shot::after {
  background: none;
}

.nl-set-body {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; align-items: flex-start;
  padding: clamp(16px, 1.6vw, 22px) clamp(16px, 1.6vw, 22px) clamp(14px, 1.4vw, 18px);
}
.nl-set-name {
  font-size: clamp(1.0625rem, 1.35vw, 1.25rem);
  font-weight: 600; line-height: 1.22; letter-spacing: -.024em;
  color: var(--rv-navy);
  text-wrap: balance;
}
.nl-set-desc {
  margin-top: 11px;
  font-size: .875rem; font-weight: 300; line-height: 1.58;
  color: var(--nl-ink-2);
}
/* The shared chip body lives further down the sheet, so the card's tone
   shift is scoped to out-specify it rather than relying on order. */
.nl-set-body .nl-caps { margin-top: 15px; }
.nl-set-body .nl-cap { color: var(--nl-ink-2); }
.nl-set-body .nl-avail { margin-top: 18px; }

/* The closing card is the route onward, and it now says so with the same
   anatomy as the five settings rather than as a lone button in an empty
   field: against five dense cards, that empty field read as a gap in the
   grid. It keeps no stage — the index below is its stage. */
.nl-set--cta {
  /* Not a care setting, so it has no palette role of its own — it takes
     the brand blue, which the sheet otherwise reserves for actions. This
     must be the custom property, not border-top-color: .nl-set builds its
     rule out of var(--nl-c), and an undefined --nl-c invalidates the whole
     border-top shorthand, leaving the card with no rule at all. */
  --nl-c: var(--rv-primary);
  background: var(--nl-paper);
}
/* The homepage-only family artwork uses the same flat field as the five
   setting cards. Its soft product shadows are already authored in the PNG. */
.nl-set-shot--family::before,
.nl-set-shot--family::after { content: none; }
.nl-set-shot--family { background: var(--nl-stage); }
.nl-shot.nl-set-shot--family img {
  object-fit: contain;
  object-position: 50% 50%;
  filter: none;
}

/* The closing card's slot carries how-it-works' care-network mark, on the
   same light stage as the five setting cards so the row reads as one set.
   The composition is authored for the hero's navy ground, so everything
   that relied on that contrast is re-tinted below: the routes darken to
   brand blue, the hub's white ring becomes a hairline, and the glow the
   dark field carried is dropped rather than left to muddy the stage. */
.nl-set-shot--network::before,
.nl-set-shot--network::after { content: none; }
.nl-set-shot--network .nl-care-network {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  padding: 0 6%;
}
/* The hero's halo reads as depth on navy and as a smudge on the stage. */
.nl-set-shot--network .nl-care-network::before { content: none; }
/* Pale-on-navy routes go invisible on a light ground; brand blue at low
   alpha keeps them legible without competing with the icons. */
.nl-set-shot--network .nl-care-network-line { stroke: rgba(61, 114, 232, .45); }
.nl-set-shot--network .nl-care-network-line:nth-child(even) {
  stroke: rgba(47, 160, 175, .45);
}
.nl-set-shot--network .nl-care-network-pulse { border-color: rgba(61, 114, 232, .3); }
/* `.nl-shot.nl-set-shot img` repaints every image in a card slot with the
   product drop-shadow the device cut-outs need. The discs carry their own
   depth, restated here at a weight that wins — two classes plus one beats
   two classes plus an element — and softened for the lighter ground. */
.nl-shot.nl-set-shot--network .nl-care-network-icon {
  filter: drop-shadow(0 8px 14px rgba(12, 27, 80, .18));
}
/* The hub and icons are sized with clamp() floors tuned to the hero's 680px
   stage. In a card slot barely half that, the floors stop being floors and
   start crowding the centre, so here the ring is purely proportional. */
.nl-shot.nl-set-shot--network .nl-care-network-hub {
  width: 21%;
  /* The 8px white ring separated hub from navy; on the light stage it is
     invisible as fill and only inflates the mark, so it drops to a hairline
     that still holds the hub off the routes running under it. */
  border: 2px solid #fff;
}
.nl-shot.nl-set-shot--network .nl-care-network-icon { width: 15%; }
.nl-shot.nl-set-shot--network .nl-care-network-hub > img {
  filter: brightness(0) invert(1);
}

/* The paper tint would swallow the register's own hairlines, so the index
   sits on white — one step up, the way the device stage does on the others. */
.nl-setlist {
  width: 100%;
  margin-top: auto;
  padding-top: clamp(18px, 2vw, 26px);
  list-style: none;
}
.nl-setlist li + li { border-top: 1px solid var(--nl-rule); }
.nl-setlist li:first-child { border-top: 1px solid var(--nl-rule-strong); }
.nl-setlist a {
  display: grid;
  grid-template-columns: 6px minmax(0, 1fr) auto;
  align-items: center;
  gap: 11px;
  padding: 10px 8px 10px 0;
  color: inherit;
  text-decoration: none;
  /* Transform, not padding: the nudge on hover must not relayout the row. */
  transition: background-color 160ms var(--nl-ease), transform 160ms var(--nl-ease);
}
/* The setting's own hue, carried down from the card above it: the index is
   wayfinding, so each row is marked the way its card is. */
.nl-setlist a::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--nl-c);
}
.nl-setlist b {
  min-width: 0;
  font-size: .8125rem; font-weight: 500;
  color: var(--rv-navy);
}
.nl-setlist span {
  font-size: .75rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--nl-ink-2);
}
@media (hover: hover) and (pointer: fine) {
  .nl-setlist a:hover { background: rgba(12, 27, 80, .04); transform: translateX(4px); }
  .nl-setlist a:hover span { color: var(--rv-primary); }
}
.nl-setlist a:active { background: rgba(12, 27, 80, .07); }
/* Every card closes on the same action: a full-width brand-blue button on the
   foot rule, so the six feet form one line of buttons across the row instead
   of five quiet links and one button. */
.nl-set-foot .nl-btn {
  width: 100%;
  justify-content: center;
  padding: 14px 22px;
}
/* On the closing card both the index and the foot take `auto`, so when a
   taller sibling stretches it the slack splits between them instead of
   collecting in one gap above the index. */
.nl-set--cta .nl-set-foot {
  margin-top: auto;
  padding-top: clamp(18px, 2vw, 26px);
}

/* Two columns. Six cards divide here too, so the sheet stays complete. */
@media (max-width: 1023px) {
  .nl-setgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Availability, read off what the site already states. */
/* The spec register: a real column head, and rows that carry every fact
   the site already states about this setting. */
.nl-avail { margin-top: 26px; width: 100%; }
/* The free space in a short card collects HERE, above the spec register, so the
   register and the foot below it sit on the card's bottom edge. That is what
   makes a row of unequal cards close on one line — `.nl-set-foot` says it is
   "anchored to the card's foot" but carries no `auto`, so it never was: every
   card happened to list exactly one region, and the day one listed two the
   whole row went out of step. Padding, not margin, so the 26px gap survives on
   a card with no slack left. Doubled selector beats the base rule above. */
.nl-set-body > .nl-avail { margin-top: auto; padding-top: 26px; }
.nl-avail-head {
  display: flex; justify-content: space-between; gap: 12px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--nl-rule-strong);
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--nl-ink-2);
}
.nl-avail-row {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--nl-rule);
  font-size: .8125rem;
}
.nl-avail-row img { width: 20px; height: 20px; border-radius: var(--nl-r-sm); flex: none; }
.nl-avail-row > i { width: 20px; font-size: 15px; color: var(--nl-c); flex: none; text-align: center; }
.nl-avail-row b { font-weight: 500; color: var(--rv-navy); }
.nl-avail-row span { margin-left: auto; color: var(--nl-ink-2); font-size: .75rem; letter-spacing: .03em; text-transform: uppercase; }
.nl-avail-row--live span { color: var(--nl-ink-live); font-weight: 600; }
/* Anchored to the card's foot, so a row of unequal cards still closes
   on one line and the route onward is always in the same place. */
.nl-set-foot {
  margin-top: clamp(22px, 2.4vw, 32px);
  padding-top: clamp(20px, 2.2vw, 30px);
  width: 100%;
  border-top: 1px solid var(--nl-rule);
}

/* The screenshot sits on a light field at real scale — not
   thumbnailed into a rounded card. The stage is one step above paper
   and held by a hairline, so it reads as its own surface. */
.nl-shot {
  position: relative;
  background: var(--nl-stage);
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  /* One frame size for every setting — the media never changes shape
     between tabs, so switching reads as a swap inside a fixed window
     rather than the page resizing under the reader. */
  aspect-ratio: 10 / 9;
  border-radius: var(--nl-r);
  box-shadow: inset 0 0 0 1px var(--nl-rule), 0 18px 44px rgba(12,27,80,.12);
}
/* The stage light: a setting-tinted pool the device stands in, so a
   cut-out product shot reads as photographed on the field rather than
   pasted onto it. Sits UNDER the image; ::after keeps the register marks. */
.nl-shot::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
}
.nl-shot::after {                  /* register marks on the field */
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to right, var(--nl-c) 0 42px, transparent 42px) top left / 100% 3px no-repeat,
    linear-gradient(180deg, rgba(12,27,80,0) 62%, rgba(12,27,80,.10) 100%);
  pointer-events: none;
}
/* The device art is authored at the stage's own 659×593 (10:9) frame,
   with the composition and its margins already baked into the canvas.
   So the image fills the frame 1:1 — no re-scaling, no re-centring, no
   negative offsets. Anything the CSS did here would fight the artwork.
   `contain` (not `cover`) guarantees the authored edges stay intact if
   the frame's ratio ever drifts. */
.nl-shot img {
  position: relative;
  width: 100%; height: 100%;
  object-fit: contain; object-position: 50% 50%;
}




/* ═══════════════════════════════════════════════════════════
   3 · PROOF — the named peers. Quiet after the router's density.
   ═══════════════════════════════════════════════════════════ */
.nl-proof {
  /* The logo grid draws its own closing hairline, so without this the last
     row of logos sat flush against the next section's edge. */
  padding-bottom: clamp(40px, 4.5vw, 64px);
  border-bottom: 1px solid var(--nl-rule);
  background: #fff;
  /* The logo grid draws its own closing hairline, so without this the last
     row of logos sat flush against the next section's edge. */
  padding-bottom: clamp(40px, 4.5vw, 64px);
}
/* Proof now sits directly under the hero, which already carries its own
   bottom padding and hairline — the old clamp(52,6vw,88) top figure
   (sized for when this section trailed the dense router) left a top gap
   roughly double the bottom one. Matched down to the section's own
   bottom padding so the heading reads as evenly spaced from both the
   hero above and the logo marquee below. */
.nl-proof-head { padding: clamp(40px, 4.5vw, 60px) 0 clamp(26px, 3vw, 38px); }
.nl-proof-head > .nl-body { max-width: 700px; }
/* The display lead holds one line at full scale — 700px clipped it by ~20px
   and pushed "mind." onto a second row. Wider measure than the body copy
   below it, and still bounded so it never runs the container's full width;
   narrow viewports wrap it against the gutter as usual. */
.nl-proof-head > .nl-proof-lead { max-width: 820px; }
/* Typographically this IS an h2 — same size, and now the same
   leading, tracking and balanced wrap, so it does not read as a
   slightly different display size beside every other section head. */
.nl-proof-lead {
  margin-top: 16px;
  font-size: var(--nl-h2);
  font-weight: 600; line-height: 1.11; letter-spacing: -.032em;
  text-wrap: balance;
  color: var(--rv-navy);
}
.nl-proof-lead b { font-weight: 600; }
.nl-proof-head .nl-body { margin-top: 15px; font-size: .9375rem; }

.nl-marquee {
  position: relative;
  /* Opening rule only. Two marquees stacked (Singapore, Indonesia) drew
     four hairlines inside 350px when each boxed itself top and bottom;
     the next group's label — or the section's own boundary — closes the
     last one. */
  border-top: 1px solid var(--nl-rule);
  /* Held to the page measure rather than the viewport, so the strip begins and
     ends exactly where the heading above it does. `max-width` alone is not
     enough: below the measure a block still fills its container, so the gutter
     has to come out of the width at every size. Both values subtract the gutter
     that `.nl-wrap` applies as padding, which is what makes the two line up. */
  width: calc(100% - var(--nl-gut) * 2);
  max-width: calc(var(--nl-max) - var(--nl-gut) * 2);
  margin-inline: auto;
  /* No horizontal padding here on purpose: `overflow` clips at the padding box,
     so padding would not inset a track that is wider than its container — the
     logos would still run under it. The mask below does that job instead. */
  padding: 26px 0;
  overflow: hidden;
  /* Feather to the container edge rather than fading to a hard cut. */
  mask-image: linear-gradient(to right, transparent, #000 5%, #000 95%, transparent);
}
.nl-marquee-track { display: flex; width: max-content; animation: nlMarquee 250s linear infinite; }
@media (hover: hover) and (pointer: fine) {
  .nl-marquee:hover .nl-marquee-track { animation-play-state: paused; }
}
/* WCAG 2.2.2: hover is a bonus, not the mechanism. The paused state is
   held on the section so touch and keyboard reach it too. */
.nl-marquee[data-paused] .nl-marquee-track { animation-play-state: paused; }
.nl-marquee-run { display: flex; align-items: center; }

.nl-logo-item {
  /* Width is the logo's own. A fixed cell gave every logo the same slot, so the
     air around one was a function of how wide it happened to be — a round badge
     sat in 130px of emptiness where Montfort Care had 17px, and read as small
     however correctly it was sized. Constant padding instead of a constant slot
     means constant air.

     Padding rather than `gap` on the run: the track is two identical runs and
     the seam between them gets no gap, so a gap would stutter once per loop.
     Each item carrying its own air keeps the seam identical to every other
     junction. */
  flex: none;
  padding: 0 24px;
  /* Fixed, so the row keeps one height whatever mix of logos it holds — the
     Singapore and Indonesia strips would otherwise sit at different heights.
     Matches MAX_BOX_H in tools/build-logo-scale.py; raise both together. */
  height: 78px;
  display: flex; align-items: center; justify-content: center;
}
.nl-logo-item img {
  width: auto; max-width: 100%;
  /* Fallback only. One height on every logo makes a wide wordmark cover several
     times the ink of a square emblem, so each file's real height is set below —
     computed from its own aspect ratio, not guessed. */
  height: 38px;
  object-fit: contain;
  opacity: 1;
  transition: opacity 200ms var(--nl-ease);
}

/* @component:logo-scale — generated from the logo files themselves by
   tools/build-logo-scale.js. Every value here is derived; edit the formula in
   that script, never these numbers. See tools/README.md. */
.nl-logo-item img[src$="/images/rv/logos/id/arunala.png"] { height: 51.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/atedia.png"] { height: 68px; }
.nl-logo-item img[src$="/images/rv/logos/id/dinsos-jatim.png"] { height: 73.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/filantropi.png"] { height: 37.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/insite.png"] { height: 56px; }
.nl-logo-item img[src$="/images/rv/logos/id/kanopi-group.png"] { height: 68.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/rskia-sadewa.png"] { height: 67px; }
.nl-logo-item img[src$="/images/rv/logos/id/rukun-dementia.png"] { height: 45.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/rukun-group-home.png"] { height: 46px; }
.nl-logo-item img[src$="/images/rv/logos/id/rukun-home-care.png"] { height: 44px; }
.nl-logo-item img[src$="/images/rv/logos/id/rukun-senior-care.png"] { height: 45.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/rukun.png"] { height: 45.5px; }
.nl-logo-item img[src$="/images/rv/logos/id/spark.png"] { height: 42px; }
.nl-logo-item img[src$="/images/rv/logos/id/ssi.png"] { height: 56.5px; }
.nl-logo-item img[src$="/images/rv/logos/partners/aws.png"] { height: 40.5px; }
.nl-logo-item img[src$="/images/rv/logos/partners/imin.png"] { height: 37px; }
.nl-logo-item img[src$="/images/rv/logos/partners/tencent.png"] { height: 29px; }
.nl-logo-item img[src$="/images/rv/logos/partners/yuwell.png"] { height: 35px; }
.nl-logo-item img[src$="/images/rv/logos/sg/4s.png"] { height: 74.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/active-global.png"] { height: 55px; }
.nl-logo-item img[src$="/images/rv/logos/sg/all-saints-home.png"] { height: 52.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/allkin.png"] { height: 38.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/apex-day-rehab.png"] { height: 69px; }
.nl-logo-item img[src$="/images/rv/logos/sg/becare.png"] { height: 63.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/ccss.png"] { height: 66.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/chcsa.png"] { height: 78px; }
.nl-logo-item img[src$="/images/rv/logos/sg/chee-hoon-kog.png"] { height: 66.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/chong-hua-tong.png"] { height: 77.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/econ-healthcare.png"] { height: 60.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/fei-yue.png"] { height: 78px; }
.nl-logo-item img[src$="/images/rv/logos/sg/geha.png"] { height: 43.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/jalan-besar.png"] { height: 46.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/jamiyah.png"] { height: 69.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/jia-ying.png"] { height: 78px; }
.nl-logo-item img[src$="/images/rv/logos/sg/kampung-senang.png"] { height: 44.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/lions-befrienders.png"] { height: 76px; }
.nl-logo-item img[src$="/images/rv/logos/sg/montfort-care.png"] { height: 28.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/muhammadiyah.png"] { height: 72px; }
.nl-logo-item img[src$="/images/rv/logos/sg/new-life.svg"] { height: 37px; }
.nl-logo-item img[src$="/images/rv/logos/sg/reach.png"] { height: 40.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/ren-ci.png"] { height: 77.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/rotary.png"] { height: 46px; }
.nl-logo-item img[src$="/images/rv/logos/sg/salvation-army.png"] { height: 74.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/sparkle-care.png"] { height: 78px; }
.nl-logo-item img[src$="/images/rv/logos/sg/touch.png"] { height: 34px; }
.nl-logo-item img[src$="/images/rv/logos/sg/tsao-foundation.png"] { height: 45.5px; }
.nl-logo-item img[src$="/images/rv/logos/sg/tzu-chi.png"] { height: 78px; }
/* @end:logo-scale */
@media (hover: hover) and (pointer: fine) {
  .nl-logo-item:hover img { opacity: 1; }
}
@keyframes nlMarquee { from { transform: translate3d(0,0,0); } to { transform: translate3d(-50%,0,0); } }

/* Static variant: every logo shown at once — no loop, no seamless
   duplicate, no pause control to offer. Not a wrapped flex row but a
   ruled grid: equal cells divided by hairlines, each logo in its own
   colours — the grid's hairlines are what hold the list together. */
.nl-marquee--static { overflow: visible; mask-image: none; padding: 0; }
.nl-marquee--static .nl-marquee-track { display: block; width: auto; animation: none; }
.nl-marquee--static .nl-marquee-run {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  border-top: 1px solid var(--nl-rule);
  border-left: 1px solid var(--nl-rule);
}
.nl-marquee--static .nl-marquee-run[aria-hidden] { display: none; }
.nl-marquee--static .nl-logo-item {
  height: 108px;
  padding: 0 18px;
  border-right: 1px solid var(--nl-rule);
  border-bottom: 1px solid var(--nl-rule);
  background: transparent;
}
.nl-marquee--static .nl-logo-item img {
  filter: none;
  opacity: 1;
}
@media (max-width: 1080px) {
  .nl-marquee--static .nl-marquee-run { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .nl-marquee--static .nl-marquee-run { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .nl-marquee--static .nl-logo-item {
    height: 92px; padding: 0 12px;
  }
}
@media (max-width: 480px) {
  .nl-marquee--static .nl-marquee-run { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Pair variant: a small, fixed set of logos too few to fill even the
   static grid's narrowest breakpoint — stretching them to `1fr` columns
   would blow each cell up to half the row. Ruled hairlines stay, matching
   the static variant, but cells size to their own content and sit at the
   start of the row instead of stretching to fill it. */
.nl-marquee--pair { overflow: visible; mask-image: none; padding: 0; border-top: none; }
.nl-marquee--pair .nl-marquee-track { display: block; width: auto; animation: none; }
.nl-marquee--pair .nl-marquee-run {
  display: flex;
  width: fit-content;
  border-top: 1px solid var(--nl-rule);
  border-left: 1px solid var(--nl-rule);
}
.nl-marquee--pair .nl-marquee-run[aria-hidden] { display: none; }
.nl-marquee--pair .nl-logo-item {
  flex: none;
  height: 78px;
  padding: 0 28px;
  border-right: 1px solid var(--nl-rule);
  border-bottom: 1px solid var(--nl-rule);
  background: transparent;
}
.nl-marquee--pair .nl-logo-item img { filter: none; opacity: 1; }
/* ═══════════════════════════════════════════════════════════
   4 · CERTIFICATION — the navy field. Compliance read as a
   ruled dossier, because this is what procurement checks.
   ═══════════════════════════════════════════════════════════ */
.nl-field { background: var(--rv-navy); color: var(--nl-on-navy); }
/* Two navy fields in a row (about.html: certification, then the technology
   partner) would otherwise read as one undivided block. Separate them the
   way the rest of the system separates anything — with a rule, at the
   field's own rule weight, held inside the page measure. */
.nl-field + .nl-field > .nl-wrap { border-top: 1px solid var(--nl-navy-rule); }
/* about.html: matches .nl-proof's own #fff, so the technology-partner
   section reads the same paper-white as the network section above it
   instead of the page's default (slightly cooler) paper tone. */
.nl-weeswares-sec { background: #fff; border-bottom: 1px solid var(--nl-rule); }
/* about.html: same #fff as .nl-proof/.nl-weeswares-sec, replacing the
   --nl-paper tint (indistinguishable from the page's own default here). */
.nl-careers-sec { background: #fff; }
.nl-cert-inner {
  display: grid;
  grid-template-columns: minmax(0, 46fr) minmax(0, 54fr);
  gap: clamp(32px, 4.6vw, 76px);
  padding: clamp(56px, 6.4vw, 96px) 0;
  align-items: start;
}
.nl-cert-copy .nl-h2 { margin-top: 14px; }
.nl-field .nl-cert-copy .nl-h2 { color: #fff; }
.nl-weeswares-logo {
  display: flex; align-items: center; gap: 12px;
  margin-top: 18px;
}
.nl-weeswares-logo span {
  font-size: var(--nl-h2); font-weight: 600;
  line-height: 1.11; letter-spacing: -.032em;
}
.nl-weeswares-logo img { display: block; height: 56px; width: auto; }
.nl-cert-copy .nl-body { margin-top: 18px; }
.nl-cert-copy .nl-link { margin-top: 26px; }

.nl-scopes { border-top: 1px solid var(--nl-rule-strong); }
.nl-field .nl-scopes { border-top-color: rgba(174,192,236,.4); }
.nl-scope {
  display: grid;
  grid-template-columns: 26px 1fr;
  align-items: center; gap: 16px;
  padding: 17px 2px;
  border-bottom: 1px solid var(--nl-rule);
}
.nl-field .nl-scope { border-bottom-color: var(--nl-navy-rule); }
.nl-scope i { font-size: 18px; color: var(--rv-primary); }
.nl-field .nl-scope i { color: var(--nl-on-navy-2); }
.nl-scope-name { font-size: .9688rem; font-weight: 400; color: var(--rv-navy); letter-spacing: -.014em; }
.nl-field .nl-scope-name { color: #fff; }

/* A footnote under the qualified scopes: ASG and AAC are Singapore
   terms, and a reader in Indonesia or Vietnam has no way to decode
   them from the heading alone. Small, ruled off, never a callout —
   it explains the scope of the certification, it does not sell it. */
.nl-cert-note {
  display: flex; gap: 10px;
  margin-top: 20px;
  font-size: .875rem; font-weight: 300; line-height: 1.6;
  color: var(--nl-ink-2); max-width: 62ch;
}
.nl-cert-note i { flex: none; font-size: 15px; margin-top: 3px; color: var(--rv-primary); }
.nl-cert-note strong { font-weight: 600; color: var(--rv-navy); }
.nl-field .nl-cert-note { color: var(--nl-on-navy-2); }
.nl-field .nl-cert-note i { color: var(--nl-on-navy-2); }
.nl-field .nl-cert-note strong { color: #fff; }

/* ═══════════════════════════════════════════════════════════
   5 · REGIONS — four ruled entries. "Coming Soon" is designed
   with the same care as "Live", never greyed out as leftovers.
   ═══════════════════════════════════════════════════════════ */
.nl-regions { border-bottom: 1px solid var(--nl-rule); }
.nl-regions-head { padding: clamp(52px, 6vw, 88px) 0 clamp(26px, 3vw, 36px); }
.nl-regions-head > .nl-h2 { max-width: 18ch; }
.nl-regions-head > .nl-body { max-width: 56ch; }
.nl-regions-head .nl-h2 { margin-top: 16px; }
.nl-regions-head .nl-body { margin-top: 15px; }

/* Three regions, three columns — the register reads left to right
   instead of stacking, with hairline rules standing in for the row
   dividers the single-column layout used. */
.nl-region-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--nl-rule-strong);
  border-bottom: 1px solid var(--nl-rule);
}
.nl-region {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  padding: clamp(24px, 2.8vw, 34px) clamp(18px, 2.2vw, 28px);
  border-left: 1px solid var(--nl-rule);
}
.nl-region:first-child { border-left: 0; }
.nl-region-id { display: flex; align-items: center; gap: 12px; }
.nl-region-id img { width: 30px; height: 30px; border-radius: var(--nl-r-sm); flex: none; }
.nl-region-id h3 { font-size: 1.0625rem; font-weight: 600; letter-spacing: -.022em; color: var(--rv-navy); }
.nl-region p { font-weight: 300; line-height: 1.6; color: var(--nl-ink-2); font-size: .9375rem; max-width: 62ch; }

.nl-status {
  display: inline-flex; align-items: center; gap: 7px;
  margin-top: 8px;
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase;
  white-space: nowrap;
}
.nl-status--live { color: var(--nl-ink-live); }
.nl-status--soon { color: var(--nl-ink-soon); }
.nl-dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.nl-status--live .nl-dot { background: var(--rv-success); box-shadow: 0 0 0 3px rgba(45,175,123,.22); }
.nl-status--soon .nl-dot { background: none; border: 1.5px solid var(--rv-accent); }
/* Sitting inline after a heading rather than stacked beneath it. */
.nl-region-id h3 .nl-status { margin-top: 0; margin-left: 9px; vertical-align: 2px; }

/* The whole card mutes — not just the label — so "not yet" reads at a
   glance against the three live/expanding cards beside it. */
.nl-region--soon { opacity: .55; filter: grayscale(.6); }
.nl-region--soon .nl-region-id h3 .nl-status { opacity: 1; filter: none; }

.nl-region-side { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }
/* .nl-cap is the same chip as .nl-vert, used inside a router panel to sample
   what a setting actually carries — the register's meta line states the count,
   these make it concrete. Static labels, so none of the a.nl-vert hover/press
   states below apply. */
.nl-verts, .nl-caps { display: flex; flex-wrap: wrap; gap: 6px; }
.nl-vert, .nl-cap {
  font-size: .75rem; font-weight: 500;
  padding: 5px 10px;
  color: var(--rv-navy);
  background: #F1F4FB;
  border-left: 1px solid var(--nl-c, var(--rv-primary));
}
/* On interior pages the chip is a link into the filtered solutions view,
   not a static label. A chip that navigates has to say so: the left border
   thickens into the setting's hue on hover, the ground steps up, and the
   press scales. Home keeps the <span> and none of this applies to it. */
a.nl-vert {
  text-decoration: none;
  transition: background 140ms var(--nl-ease), color 140ms var(--nl-ease);
}
@media (hover: hover) and (pointer: fine) {
  /* The ground steps up and the label darkens; the hue border stays at
     1px. Widening it was tried and rejected — a 3px setting-hue rule is
     the tab register's selected-state mark, and borrowing it here made a
     chip look like a selected tab. */
  a.nl-vert:hover {
    background: #E7EDF9;
    color: var(--rv-primary-dark);
  }
}
a.nl-vert:active { transform: scale(.97); }
a.nl-vert:focus-visible {
  outline: 2px solid var(--rv-primary);
  outline-offset: 3px;
}
.nl-regions-foot { padding: clamp(30px, 3.4vw, 44px) 0 clamp(52px, 6vw, 84px); }

/* ═══════════════════════════════════════════════════════════
   6 · MISSION — three even columns, image over copy.
   ═══════════════════════════════════════════════════════════ */
.nl-mission { background: #fff; border-bottom: 1px solid var(--nl-rule); }
.nl-mission-head { padding: clamp(58px, 6.6vw, 100px) 0 clamp(10px, 1vw, 14px); }
.nl-mission-head > .nl-h2 { max-width: 20ch; }
.nl-mission-head .nl-h2 { margin-top: 16px; }
.nl-mission-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(28px, 3.4vw, 48px);
  /* No opening rule. The three figures' own top edges already draw a
     line across the block, and the section's boundary rule sits just
     above the head — a hairline here made three parallel lines inside
     150px. */
  margin-bottom: clamp(58px, 6.6vw, 100px);
}
/* Three even columns: image on top, copy below. Deliberately not a
   ruled roster of rows — each entry reads as its own column. */
.nl-mission-item {
  display: flex;
  flex-direction: column;
  gap: clamp(16px, 1.8vw, 22px);
  padding: 0;
  border-bottom: 0;
}
/* Reuse the hero's blue care symbols with space around each mark. */
.nl-mission-figure {
  position: relative; overflow: hidden; aspect-ratio: 16 / 11;
  display: grid; place-items: center;
  background: #F7F9FC; border-radius: var(--nl-r);
}
.nl-mission-figure img {
  display: block; width: 48%; max-width: 180px; height: auto;
  object-fit: contain;
}
.nl-mission-body { padding-top: 2px; }
.nl-mission-body p { margin-top: 12px; font-weight: 300; line-height: 1.62; color: var(--nl-ink-2); font-size: .9375rem; }
/* The existing site tags, set as ruled tags rather than kickers: they name
   a category, they do not restate the heading. */
.nl-tag {
  display: inline-flex; align-items: center; gap: 7px;
  margin-bottom: 12px;
  padding: 4px 9px 4px 8px;
  background: #F1F4FB;
  border-left: 1px solid var(--nl-ink-2);
  border-radius: 0 var(--nl-r-sm) var(--nl-r-sm) 0;
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--nl-ink-2);
}

/* ═══════════════════════════════════════════════════════════
   7 · MANIFESTO — quiet navy. Honest about what is not ready.
   ═══════════════════════════════════════════════════════════ */
.nl-manifesto-inner {
  display: grid;
  grid-template-columns: minmax(0, 56fr) minmax(0, 44fr);
  gap: clamp(30px, 4.4vw, 72px);
  align-items: center;
  padding: clamp(56px, 6.4vw, 96px) 0;
}
.nl-video {
  position: relative; width: 100%;
  aspect-ratio: 16 / 10;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 18px;
  background:
    repeating-linear-gradient(-45deg, rgba(174,192,236,.05) 0 12px, transparent 12px 24px),
    #0A1642;
  border: 1px solid var(--nl-navy-rule);
  border-radius: var(--nl-r);
  color: var(--nl-on-navy-2);
  cursor: pointer;
  transition: border-color 200ms var(--nl-ease), transform 160ms var(--nl-ease);
}
.nl-video:active { transform: scale(.994); }
.nl-play {
  width: 62px; height: 62px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--rv-primary); color: #fff;
  font-size: 22px; padding-left: 3px;
  box-shadow: 0 10px 30px rgba(61,114,232,.42);
  transition: transform 200ms var(--nl-ease), box-shadow 200ms var(--nl-ease);
}
.nl-video-hint { font-size: .8125rem; letter-spacing: .05em; text-transform: uppercase; }
.nl-video-badge {
  position: absolute; left: 0; top: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 9px 14px;
  background: var(--rv-primary); color: #fff;
  border-radius: var(--nl-r) 0 var(--nl-r-sm) 0;
  font-size: .6875rem; font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
}
@media (hover: hover) and (pointer: fine) {
  .nl-video:hover { border-color: rgba(174,192,236,.5); }
  .nl-video:hover .nl-play { transform: scale(1.06); box-shadow: 0 14px 38px rgba(61,114,232,.55); }
}
#manifesto { background: #fff; border-bottom: 1px solid var(--nl-rule); }
/* The real manifesto embed, replacing the coming-soon placeholder above. */
.nl-video-frame {
  position: relative; width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--nl-rule-strong);
  border-radius: var(--nl-r);
  background: var(--nl-stage);
}
.nl-video-frame iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
}
.nl-manifesto-copy .nl-h2 { margin-top: 14px; }
.nl-field .nl-manifesto-copy .nl-h2 { color: #fff; }
.nl-manifesto-copy .nl-body { margin-top: 18px; }
.nl-manifesto-copy .nl-btn { margin-top: 28px; }

/* ═══════════════════════════════════════════════════════════
   8 · CLOSE — a real anchor, not a repeated banner.
   ═══════════════════════════════════════════════════════════ */
.nl-close {
  background: var(--rv-primary-dark);
  color: #fff;
  border-top: 1px solid rgba(255,255,255,.16);
}
.nl-close-inner {
  padding: clamp(60px, 7vw, 108px) 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(28px, 4vw, 64px);
  align-items: end;
}
.nl-close h2 {
  font-size: clamp(1.9rem, 3.6vw, 3.05rem);
  font-weight: 600; line-height: 1.06; letter-spacing: -.036em;
  max-width: 22ch; text-wrap: balance;
}
.nl-close p {
  margin-top: 20px; max-width: 52ch;
  font-weight: 300; line-height: 1.6;
  color: #EAF0FF;                    /* 5.6:1 on #2D5EC7 */
}
.nl-close-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 24px; }

/* ═══════════════════════════════════════════════════════════
   BUTTONS — square-cornered register buttons in this world's
   own vocabulary. Scoped so rv-btn elsewhere is untouched.
   ═══════════════════════════════════════════════════════════ */
.nl-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 15px 26px;
  font-family: inherit; font-size: .9375rem; font-weight: 600;
  line-height: 1; letter-spacing: -.012em;
  border: 1px solid var(--rv-primary);
  border-radius: var(--nl-r-sm);
  background: var(--rv-primary); color: #fff;
  cursor: pointer;
  transition: background 150ms var(--nl-ease), border-color 150ms var(--nl-ease),
              transform 140ms var(--nl-ease), box-shadow 150ms var(--nl-ease);
}
.nl-btn i { font-size: 17px; }
.nl-btn:active { transform: scale(.975); }
.nl-btn:hover { color: #fff; background: var(--rv-primary-dark); border-color: var(--rv-primary-dark); }
.nl-btn--ghost {
  background: none; color: var(--rv-primary);
  border-color: var(--nl-rule-strong);
}
.nl-btn--ghost:hover { background: var(--rv-primary-light); color: var(--rv-primary-dark); border-color: var(--rv-primary); }
.nl-field .nl-btn { border-color: #fff; background: #fff; color: var(--rv-navy); }
.nl-field .nl-btn:hover { background: var(--nl-on-navy); border-color: var(--nl-on-navy); color: var(--rv-navy); }
.nl-close .nl-btn { background: #fff; border-color: #fff; color: var(--rv-primary-dark); }
.nl-close .nl-btn:hover { background: #EBF2FF; border-color: #EBF2FF; color: #24509F; }
.nl-close .nl-btn--ghost {
  background: rgba(255,255,255,.1); color: #fff;
  border-color: rgba(255,255,255,.82);   /* 4.4:1 on #2D5EC7 */
}
.nl-close .nl-btn--ghost:hover { background: rgba(255,255,255,.18); border-color: #fff; color: #fff; }
.nl-close :focus-visible { outline-color: #fff; }

/* ═══════════════════════════════════════════════════════════
   FOOTER + STICKY CTA — scoped to this page's register world.
   ═══════════════════════════════════════════════════════════ */
body.nl .rv-footer { background: var(--rv-navy); }
/* The same "C" watermark that opens every interior hero also closes the page,
   anchored bottom-left so the brand bookends the navy fields. Decorative, so
   it stays in CSS: no markup change, and it lands on every page's footer. */
body.nl .rv-footer {
  position: relative;
  isolation: isolate;
  overflow: clip;
}
body.nl .rv-footer::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 0;
  bottom: 0;
  width: clamp(210px, 22vw, 330px);
  aspect-ratio: 883 / 934;
  /* Unmirrored, as in the hero: the C's opening and inner comma face inward,
     which is what makes it read as the mark rather than a blob. */
  transform: translate(-20%, 22%);
  background: url("images/hero/c-mark.svg") no-repeat center / contain;
  opacity: .1;
  pointer-events: none;
}
@media (max-width: 800px) {
  body.nl .rv-footer::after {
    width: clamp(160px, 40vw, 230px);
    transform: translate(-24%, 24%);
    opacity: .085;
  }
}
body.nl .rv-footer-top { border-bottom: 1px solid var(--nl-navy-rule); }
body.nl .rv-footer-label {
  font-size: .6875rem; letter-spacing: .13em; text-transform: uppercase;
  color: var(--nl-on-navy-2);
}
body.nl.nl-js .rv-sticky {
  opacity: 0; visibility: hidden;
  transform: translateY(10px);
  transition: opacity 220ms var(--nl-ease), transform 260ms var(--nl-ease), visibility 220ms;
}
body.nl.nl-js.nl-sticky-on .rv-sticky {
  opacity: 1; visibility: visible; transform: none;
}
/* Reaching the footer re-hides it — a fixed button sitting over the
   footer's own contact links and copyright line reads as a mistake,
   not a feature. Same three-class specificity as .nl-sticky-on above,
   so source order (this rule after it) decides when both are set. */
body.nl.nl-js.nl-sticky-near-footer .rv-sticky {
  opacity: 0; visibility: hidden; transform: translateY(10px);
}
body.nl .rv-sticky button {
  border-radius: var(--nl-r-sm);
  box-shadow: 0 10px 30px rgba(12,27,80,.3);
  transition: background 150ms var(--nl-ease), transform 140ms var(--nl-ease);
}
body.nl .rv-sticky button:active { transform: scale(.975); }
body.nl .rv-demo-panel { border-radius: var(--nl-r); }
body.nl .rv-input-box,
body.nl .rv-textarea,
body.nl .rv-select { border-radius: var(--nl-r-sm); }
body.nl .rv-btn { border-radius: var(--nl-r-sm); }
body.nl .rv-btn:active { transform: scale(.975); }

/* ═══════════════════════════════════════════════════════════
   SCROLL REVEAL — opacity and 8px, staggered. Content is
   visible by default; JS opts in, so no-JS never hides anything.
   ═══════════════════════════════════════════════════════════ */
.nl-js [data-reveal] {
  opacity: 0;
  /* 14px, not 8px: at 8px over 520ms the element travels so little that the
     easing's long tail is sub-pixel for most of its duration — it reads as a
     fade with a twitch at the start rather than as motion. A slightly longer
     throw lets the same curve resolve into actual movement. */
  transform: translateY(14px);
  /* Opacity leads and finishes early, transform trails: the element is fully
     legible while it is still settling, so the arrival feels damped instead
     of ending on a hard stop. `--nl-ease-out` decelerates without the
     overshoot-adjacent snap of the shared enter curve. */
  transition:
    opacity 420ms var(--nl-ease-out),
    transform 720ms var(--nl-ease-out);
  /* Promote once, up front. Without this the compositor hands the layer off
     mid-transition and the first frame or two of a stagger can hitch. */
  will-change: opacity, transform;
}
.nl-js [data-reveal].nl-in {
  opacity: 1;
  transform: none;
}
/* Dropped after arrival so long pages aren't holding dozens of live layers. */
.nl-js [data-reveal].nl-done { will-change: auto; }

/* ═══════════════════════════════════════════════════════════
   RESPONSIVE
   ═══════════════════════════════════════════════════════════ */
@media (max-width: 1080px) {
  .nl-region-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nl-region:nth-child(odd) { border-left: 0; }
  .nl-region:nth-child(n+3) { border-top: 1px solid var(--nl-rule); }
}
@media (max-width: 980px) {
  .nl-cert-inner,
  .nl-manifesto-inner { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 900px) {
  .nl-hero-inner {
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding-bottom: clamp(28px, 6vw, 48px);
  }
  /* Media first, copy under it. The stage sets its own proportion, and the
     copy sits in the inner's own gutter — the panel it used to overlap, and
     the negative margin that did the overlapping, are both gone. */
  .nl-hero-media {
    position: relative;
    order: 1;
    inset: auto;
  }
  .nl-hero-copy {
    position: relative;
    order: 2;
    left: auto;
    bottom: auto;
    width: auto;
    opacity: 1;
    margin: 0;
  }
  .nl-close-inner { grid-template-columns: minmax(0, 1fr); align-items: start; }
}
@media (max-width: 720px) {
  .nl-setgrid { grid-template-columns: minmax(0, 1fr); gap: clamp(18px, 4vw, 24px); }
  .nl-region-list { grid-template-columns: minmax(0, 1fr); }
  .nl-region { border-left: 0; border-top: 1px solid var(--nl-rule); gap: 14px; }
  .nl-region:first-child { border-top: 0; }
  .nl-mission-list { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .nl-mission-item:first-child .nl-mission-figure,
  .nl-mission-figure { aspect-ratio: 16 / 10; }
  .nl-hero-copy .nl-h1 { font-size: clamp(2.6rem, 11.5vw, 2.85rem); }
  .nl-hero-copy .nl-lead { margin-top: 12px; line-height: 1.5; }
  .nl-hero-flags { margin-top: 16px; }
  .nl-hero-btns { gap: 12px 18px; }
  .nl-btn { padding: 14px 20px; }
}
@media (max-width: 460px) {
  .nl-hero-btns { align-items: stretch; flex-direction: column; }
  .nl-hero-btns .nl-btn { width: 100%; }
}

/* ═══════════════════════════════════════════════════════════
   REDUCED MOTION — fewer and gentler, not zero. Every translation
   goes; opacity, which aids comprehension, stays.
   ═══════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .nl-marquee-track { animation: none; transform: none; }
  .nl-marquee { mask-image: none; overflow-x: auto; }
  .nl-js [data-reveal] {
    transform: none;
    transition: opacity 300ms ease;
    will-change: auto;
  }
  .nl-link i { transition: none; }
}

/* ═══════════════════════════════════════════════════════════
   ═══════════════════════════════════════════════════════════
   INTERIOR PAGES
   The register grammar extended to about, how-it-works,
   solutions, contact, news-info and 404. Two mechanisms:

   (a) NEW `nl-` components below, for shapes index.html never
       needed (breadcrumbs, a page-hero, a ruled note/office/
       article/item list).

   (b) RETINTING each page's existing legacy classes through
       the custom properties they already read from. A card
       grid becomes a flat ruled register by redefining
       `--r-md`/`--shadow-sm`/`--border`/`--bg` on `body.nl`,
       not by rewriting every rule that used them — the same
       mechanism this file already uses on `.rv-header`/
       `.rv-footer`. Where a legacy layout still reads as a
       card grid after retinting (multi-column tile grids), the
       grid itself is flattened to a single ruled column too.
   ═══════════════════════════════════════════════════════════
   ═══════════════════════════════════════════════════════════ */

/* ── Token bridge: legacy custom properties → register tokens.
      Everything under `body.nl` that still says `var(--navy)`,
      `var(--blue)`, `var(--border)`, `var(--r-md)` etc. now
      resolves to the pinned register value instead of the
      pre-register one. This alone removes every stray radius,
      drop shadow and off-brand blue from legacy markup. ── */
body.nl {
  --navy: var(--rv-navy);
  --blue: var(--rv-primary);
  --coral: var(--rv-primary);        /* off-brand red retired; CTAs use brand blue */
  --text: var(--rv-navy);
  --text-mid: var(--nl-ink-2);
  --text-light: var(--nl-ink-2);
  --border: var(--nl-rule);
  --bg: var(--nl-paper);
  --bg-light: #fff;
  --ice: var(--nl-stage);
  --r-sm: 0px;
  --r-md: 0px;
  --shadow-sm: none;
  --shadow-md: none;
}

/* ── Breadcrumb ───────────────────────────────────────────── */
.nl-crumbs {
  display: flex; align-items: center; gap: 8px;
  padding: 16px 0 0;
  font-size: .8125rem; color: var(--nl-ink-2);
}
.nl-crumbs a { color: var(--nl-ink-2); text-decoration: none; }
.nl-crumbs a:hover { color: var(--rv-navy); text-decoration: underline; text-underline-offset: .18em; }
.nl-crumbs span[aria-current] { color: var(--rv-navy); font-weight: 500; }
.nl-crumbs i { font-size: 10px; color: var(--nl-rule-strong); }

/* ── Page hero. index.html's split hero exists once; every
      interior page opens instead with the same marker + h1 +
      lead on one left edge, no photograph competing with it. ── */
.nl-page-hero {
  /* Trimmed ~7% to match the About hero's shorter masthead. Each stop is
     scaled by the same factor, so the viewport range over which the height
     is fluid (1077px–1282px) is unchanged. Reaches solutions/how-it-works/
     news-info only: contact's `--no-media` and About above 801px both set
     `height: auto`, so neither reads this. Floor is the copy — how-it-works
     and news-info run 407px tall inside it, so don't go below ~420px here
     without also trimming `.nl-media-hero-copy`'s 135px of padding. */
  --nl-interior-hero-height: clamp(390px, 36.2vw, 464px);
  border-bottom: 1px solid var(--nl-rule);
}
/* The One Left Edge Rule. `.nl-wrap` centres its 1240px measure with an
   auto margin; narrowing max-width here would centre the 780px block too
   and push the H1 off the page's left edge. Keep the container's width
   and gutter, and hold the reading measure on the children instead. */
.nl-page-hero-inner {
  padding-top: clamp(30px, 4.4vw, 56px);
  padding-bottom: clamp(40px, 5vw, 64px);
}
.nl-page-hero-inner > * { max-width: 780px; }
.nl-page-hero .nl-h1 { margin-top: 14px; font-size: clamp(2.05rem, 4vw, 3.1rem); }
.nl-page-hero .nl-lead { margin-top: 16px; }
/* On the navy field (about, contact, how-it-works, news-info, solutions):
   same grammar, inverted ink, and the same gradient field as index.html's
   hero — one field colour for every page's opening moment. */
.nl-page-hero--field {
  background: linear-gradient(135deg, var(--rv-navy) 0%, #1a306b 55%, #404565 100%);
  color: var(--nl-on-navy);
  border-bottom: 0;
}
.nl-page-hero--field .nl-h1 { color: #fff; }
.nl-page-hero--field .nl-lead { color: var(--nl-on-navy-2); }
.nl-page-hero--field .nl-marker { color: var(--nl-on-navy-2); }
.nl-page-hero--field .nl-marker::before { background: var(--nl-on-navy-2); }
.nl-page-hero--field .nl-crumbs,
.nl-page-hero--field .nl-crumbs a { color: var(--nl-on-navy-2); }
.nl-page-hero--field .nl-crumbs span[aria-current] { color: #fff; }
.nl-page-hero--field :focus-visible { outline-color: #8FB0FF; }

/* The CaritaHub "C" as a watermark on every interior navy field. It is
   decorative only, so it lives in CSS: no page markup changes, and every
   interior hero inherits it from the one field class. Anchored to the hero's
   bottom-right corner and bled off it so the mark reads as a fragment of the brand
   rather than a floating logo, and held under the copy at low opacity so
   contrast on the H1 and lead is untouched. */
.nl-page-hero--field {
  position: relative;
  isolation: isolate;
  overflow: clip;
}
.nl-page-hero--field::before {
  content: "";
  position: absolute;
  z-index: -1;
  bottom: 0;
  right: 0;
  width: clamp(300px, 30vw, 420px);
  aspect-ratio: 883 / 934;
  transform: translate(22%, 30%);
  background: url("images/hero/c-mark.svg") no-repeat center / contain;
  opacity: .14;
  pointer-events: none;
}
@media (max-width: 800px) {
  /* Stacked layout: the copy block is shorter and the photograph sits below
     it, so the mark is pulled further off-edge and dimmed to stay a texture. */
  .nl-page-hero--field::before {
    width: clamp(220px, 48vw, 300px);
    transform: translate(28%, 32%);
    opacity: .11;
  }
}

/* Primary interior pages share the same image-led opening as About. Each
   page controls only its crop; the structure, edge bleed, and fade remain
   consistent so the navigation journey feels like one enterprise system. */
.nl-media-hero {
  overflow: clip;
  --nl-media-position: 50% 50%;
  --nl-media-position-mobile: var(--nl-media-position);
}
.nl-media-hero-inner,
.nl-about-hero-inner {
  height: var(--nl-interior-hero-height);
  min-height: 0;
}
.nl-media-hero-inner {
  padding-top: 0;
  padding-bottom: 0;
  display: grid;
  grid-template-columns: minmax(0, 53fr) minmax(0, 47fr);
  align-items: stretch;
  gap: clamp(24px, 2.6vw, 44px);
}
.nl-page-hero-inner.nl-media-hero-inner > * { max-width: none; }
.nl-media-hero-copy {
  position: relative;
  z-index: 1;
  align-self: center;
  min-width: 0;
  padding: clamp(42px, 5vw, 68px) 0;
}
.nl-media-hero-copy .nl-h1 { max-width: 18ch; }
.nl-media-hero-copy .nl-lead { max-width: 62ch; }
/* The photograph is part of the navy field, not a panel beside it: an eased
   alpha mask lets the real background gradient show through its left edge,
   so no guessed overlay colour can leave a seam where image and field meet.
   The image also runs 18% back under the copy column, which is the width
   the fade needs to finish before the photograph reads. */
.nl-media-hero-media {
  min-width: 0;
  margin: 0 calc(((100vw - min(100vw, var(--nl-max))) / -2) - var(--nl-gut)) 0 -18%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0%, rgba(0,0,0,.12) 10%, rgba(0,0,0,.5) 28%,
    rgba(0,0,0,.9) 48%, #000 64%);
  mask-image: linear-gradient(90deg,
    transparent 0%, rgba(0,0,0,.12) 10%, rgba(0,0,0,.5) 28%,
    rgba(0,0,0,.9) 48%, #000 64%);
}
.nl-media-hero-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--nl-media-position);
}
.nl-media-hero--solutions {
  --nl-media-position: 54% 48%;
  --nl-media-position-mobile: 52% 44%;
}
.nl-media-hero--how {
  --nl-media-position: 52% 50%;
  --nl-media-position-mobile: 52% 48%;
}

/* How It Works opens on the system itself: six care roles orbit a single
   CaritaHub hub, with visible routes showing that every role shares the same
   information network. This composition deliberately opts out of the shared
   photographic hero fade above. */
.nl-media-hero--how .nl-care-network {
  position: relative;
  display: grid;
  place-items: center;
  margin: 0 calc(((100vw - min(100vw, var(--nl-max))) / -2) - var(--nl-gut)) 0 -10%;
  overflow: visible;
  -webkit-mask-image: none;
  mask-image: none;
}
.nl-care-network::before {
  content: "";
  position: absolute;
  width: min(76%, 520px);
  aspect-ratio: 1;
  border: 1px solid rgba(126, 174, 255, .13);
  border-radius: 50%;
  box-shadow: 0 0 90px rgba(56, 115, 226, .2), inset 0 0 70px rgba(46, 105, 216, .08);
}
.nl-care-network-stage {
  position: relative;
  width: min(100%, 680px);
  aspect-ratio: 16 / 11;
}
.nl-care-network-links {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.nl-care-network-line {
  stroke: rgba(115, 174, 255, .78);
  stroke-width: 1.5;
  stroke-dasharray: 5 8;
  vector-effect: non-scaling-stroke;
  animation: nl-network-flow 5s linear infinite;
}
.nl-care-network-line:nth-child(even) {
  stroke: rgba(47, 190, 204, .68);
  animation-direction: reverse;
}
@keyframes nl-network-flow { to { stroke-dashoffset: -52; } }
.nl-care-network-hub {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  width: clamp(104px, 20%, 142px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  transform: translate(-50%, -50%);
  border: 2px solid #fff;
  border-radius: 31%;
  background: #3D72E8;
  box-shadow: none;
}
.nl-care-network-hub > img {
  position: relative;
  z-index: 1;
  display: block;
  width: 67%;
  height: auto;
  filter: brightness(0) invert(1);
}
.nl-care-network-pulse {
  position: absolute;
  inset: -18%;
  border: 1px solid rgba(119, 175, 255, .5);
  border-radius: 34%;
  animation: nl-network-pulse 3.2s ease-out infinite;
}
.nl-media-hero--how .nl-care-network-pulse { display: none; }
@keyframes nl-network-pulse {
  0% { opacity: .7; transform: scale(.78); }
  75%, 100% { opacity: 0; transform: scale(1.18); }
}
.nl-care-network .nl-care-network-icon {
  position: absolute;
  z-index: 3;
  width: clamp(58px, 14%, 92px);
  height: auto;
  filter: drop-shadow(0 12px 20px rgba(3, 22, 72, .35));
}
.nl-care-network-icon--family { left: 13%; top: 11%; }
.nl-care-network-icon--ai { right: 13%; top: 11%; }
.nl-care-network-icon--compassion { left: 5%; top: 37%; }
.nl-care-network-icon--health { right: 5%; top: 37%; }
.nl-care-network-icon--home { left: 16%; bottom: 5%; }
.nl-care-network-icon--profile { right: 16%; bottom: 5%; }

@media (prefers-reduced-motion: reduce) {
  .nl-care-network-line,
  .nl-care-network-pulse { animation: none; }
  .nl-care-network-pulse { opacity: .24; }
}
.nl-media-hero--news {
  --nl-media-position: 52% 50%;
  --nl-media-position-mobile: 48% 48%;
}

/* News opens on the work itself rather than on a single staged photograph:
   five field shots — a partnership visit, a conference handover, a signing,
   a working session, the ATEDIA fair — crossfading in the same frame the
   other interior heroes use. The slides are stacked in one grid cell rather
   than positioned absolutely, so the figure still takes its height from the
   hero grid on desktop and from its own aspect-ratio when stacked; nothing
   here has to restate that height.

   Only the active layer is opaque. Every slide keeps `object-fit: cover` and
   the hero's own focal point, so a slide can override just its own crop
   (--nl-media-position, set inline on the one portrait shot) without
   touching the shared frame. */
.nl-media-hero-slides {
  display: grid;
  /* The row must be pinned to the frame, not left to size itself. An auto
     row takes the slides' own intrinsic height (988px for these at this
     width), the figure clips the overflow, and each image then exactly fills
     an over-tall box — so `cover` has nothing to crop and the focal point
     below goes inert. 100% of the frame's own definite height keeps the crop
     doing the work. */
  grid-template-rows: 100%;
}
.nl-media-hero-slides .nl-media-hero-slide {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity 1.1s ease;
}
.nl-media-hero-slides .nl-media-hero-slide.is-active { opacity: 1; }

/* The script holds on slide one for these visitors, so this only has to make
   sure the layer it holds on is not mid-fade when the timer never fires. */
@media (prefers-reduced-motion: reduce) {
  .nl-media-hero-slides .nl-media-hero-slide { transition: none; }
}
/* The device-family photograph runs the health station's arch close to its top
   edge, so the crop is held high to keep the tallest device whole. */
.nl-media-hero--contact {
  --nl-media-position: 56% 22%;
  --nl-media-position-mobile: 54% 30%;
}
/* Temporary text-only state while a hero image is pending replacement —
   collapses the media column so the copy runs full width instead of
   leaving a blank gap. Drop this modifier once a new image is chosen. */
.nl-media-hero--no-media .nl-media-hero-inner {
  grid-template-columns: minmax(0, 1fr);
  height: auto;
}
.nl-media-hero--no-media .nl-media-hero-copy {
  padding: clamp(48px, 6vw, 84px) 0;
}
/* Same collapse for the About hero, which isn't on the shared nl-media-hero
   system (it has its own copy/roadmap grid) but hit the same problem: no
   roadmap image, so the copy needs to run full width instead of leaving a
   blank second column. */
.nl-about-hero--no-media .nl-about-hero-inner {
  grid-template-columns: minmax(0, 1fr);
  height: auto;
  padding-top: 0;
  padding-bottom: 0;
}
.nl-about-hero--no-media .nl-about-hero-copy {
  padding: clamp(48px, 6vw, 84px) 0;
}

@media (max-width: 800px) {
  .nl-media-hero-inner {
    height: auto;
    min-height: 0;
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
  .nl-media-hero-copy {
    padding: clamp(36px, 7vw, 54px) 0 clamp(28px, 5vw, 40px);
  }
  .nl-media-hero-copy .nl-h1 { max-width: 18ch; }
  /* Stacked: the fade turns with the layout — top edge into the copy above. */
  .nl-media-hero-media {
    min-height: 0;
    aspect-ratio: 16 / 10;
    margin: -6% calc(var(--nl-gut) * -1) 0;
    -webkit-mask-image: linear-gradient(180deg,
      transparent 0%, rgba(0,0,0,.28) 14%, rgba(0,0,0,.82) 34%, #000 52%);
    mask-image: linear-gradient(180deg,
      transparent 0%, rgba(0,0,0,.28) 14%, rgba(0,0,0,.82) 34%, #000 52%);
  }
  .nl-media-hero-media img { object-position: var(--nl-media-position-mobile); }
  .nl-media-hero--how .nl-care-network {
    aspect-ratio: auto;
    margin: -2% calc(var(--nl-gut) * -1) 0;
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .nl-care-network-stage { width: min(100%, 620px); }
}

@media (max-width: 480px) {
  .nl-care-network-stage { width: 108%; }
  .nl-care-network .nl-care-network-icon { width: 13.5%; }
  .nl-care-network-hub { border-width: 2px; }
}

/* About opens on the product journey itself. The roadmap is live HTML so its
   claims can be translated, selected, and read by assistive technology. Its
   light field meets the navy hero with a deliberate hard edge: no image mask
   or blue fade is needed. */
.nl-about-hero { overflow: clip; }
.nl-about-hero-copy {
  position: relative;
  z-index: 1;
  padding: clamp(42px, 5vw, 68px) 0;
}
.nl-about-hero-copy .nl-h1 { max-width: 16ch; }
.nl-about-hero-copy .nl-lead { max-width: 62ch; }
.nl-about-map {
  position: relative;
  align-self: center;
  width: 100%;
  min-width: 0;
  /* Matches the artwork's own trimmed ink ratio (1352x839), so `contain`
     below has no empty band to fit — a 16/9 frame letterboxed it and made
     the map read smaller than the column it sits in. Re-derive this whenever
     the artwork is replaced; the PNG carries transparent padding out of the
     generator, so trim it first and use the trimmed dimensions. */
  aspect-ratio: 1352 / 839;
  margin: 0;
  isolation: isolate;
}
.nl-about-map-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.nl-about-roadmap-overlay { position: absolute; inset: 0; padding: clamp(46px, 7vw, 74px) 0; color: #fff; font-family: 'Lexend', sans-serif; }
.nl-about-roadmap-kicker { color: #A9B8DE; text-transform: uppercase; letter-spacing: .14em; font-size: .68rem; font-weight: 600; margin-bottom: 8px; }
.nl-about-roadmap-overlay h2 { margin: 0 0 18px; font-size: clamp(1.35rem, 2.4vw, 2rem); letter-spacing: -.04em; }
.nl-about-roadmap-overlay ol { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(2, auto); position: relative; row-gap: 28px; }
.nl-about-roadmap-overlay ol::before { content: ""; position: absolute; left: 7%; right: 7%; top: 19px; height: calc(100% - 38px); border-top: 1px solid rgba(174,192,236,.55); border-right: 1px solid rgba(174,192,236,.55); border-bottom: 1px solid rgba(174,192,236,.55); pointer-events: none; }
.nl-about-roadmap-overlay li { display: grid; grid-template-rows: 40px auto auto; gap: 6px; padding: 0 7px; font-size: clamp(.62rem, .78vw, .76rem); line-height: 1.3; position: relative; z-index: 1; text-align: center; }
.nl-about-roadmap-overlay li i { justify-self: center; width: 40px; height: 40px; display: grid; place-items: center; border: 1px solid #AFC2F0; border-radius: 50%; color: #fff; background: #24498F; font-size: 17px; box-shadow: 0 0 0 5px #1a3471; }
.nl-about-roadmap-overlay strong { color: #C6D5FF; font-weight: 600; font-variant-numeric: tabular-nums; }
.nl-about-roadmap-overlay span { color: #F3F6FF; }
.nl-about-roadmap-overlay li:nth-child(4) { grid-column: 3; grid-row: 2; }
.nl-about-roadmap-overlay li:nth-child(5) { grid-column: 2; grid-row: 2; }
.nl-about-roadmap-overlay li:nth-child(6) { grid-column: 1; grid-row: 2; }
@media (min-width: 801px) {
  .nl-about-hero-inner { display: grid; grid-template-columns: minmax(0, 44fr) minmax(360px, 56fr); align-items: stretch; gap: clamp(28px, 4vw, 64px); }
  .nl-about-hero-inner > * { max-width: none; }
  .nl-about-hero-copy { align-self: center; }
  /* Height comes from the content, not the shared interior-hero height.
     `.nl-page-hero-inner` already pads this block top and bottom, so the
     copy's own vertical padding was a second helping: 244px of chrome around
     333px of text pushed the natural height to 577px, which the fixed 500px
     then clipped (hidden by `overflow: clip` on the section). Dropping the
     duplicate and freeing the height lands at 508px with nothing cut, and
     lets the map — the taller of the two columns — set the height while the
     copy centres against it. Scoped to this hero: `.nl-media-hero-inner`
     on solutions/contact/how-it-works/news-info keeps the shared height. */
  /* Padding trimmed back from the shared interior-hero 56/64. With the map —
     not the copy — setting this hero's height, the wrapper's padding is the
     only lever that shortens the section without shrinking the artwork, so
     take it from there rather than capping the map. */
  .nl-about-hero-inner {
    height: auto;
    padding-top: clamp(24px, 3.2vw, 40px);
    padding-bottom: clamp(28px, 3.6vw, 44px);
  }
  .nl-about-hero-copy { padding-block: 0; }
}
@media (max-width: 800px) {
  .nl-about-hero-inner {
    height: auto;
    padding-top: 0;
    padding-bottom: clamp(24px, 6vw, 42px);
  }
  .nl-about-hero-copy { padding-bottom: clamp(18px, 3vw, 28px); }
  .nl-about-map { width: min(100%, 620px); margin-inline: auto; }
  .nl-about-map-label { padding: 6px 8px 7px; }
}
@media (max-width: 480px) {
  .nl-about-map { width: 112%; margin-inline: -6%; }
  .nl-about-map-pin { gap: 5px; }
  .nl-about-map-dot { width: 13px; height: 13px; border-width: 2px; box-shadow: 0 0 0 3px rgba(109, 145, 226, .25); }
  .nl-about-map-label { padding: 5px 6px 6px; }
}
@media (max-width: 800px) { .nl-about-roadmap-overlay ol { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 22px; } .nl-about-roadmap-overlay ol::before { top: 19px; left: 14%; right: 14%; } }
@media (max-width: 440px) { .nl-about-roadmap-overlay ol { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ── Section head, reused verbatim from the router/regions/
      mission pattern: a marker on the opening rule, h2, body. ── */
.nl-sec { border-bottom: 1px solid var(--nl-rule); padding: clamp(46px, 5.4vw, 80px) 0; }
.nl-sec--tint { background: var(--nl-paper); }
.nl-sec-head { margin-bottom: clamp(26px, 3vw, 40px); }
.nl-sec-head .nl-h2 { margin-top: 15px; max-width: 26ch; }
.nl-sec-head .nl-body { margin-top: 14px; max-width: 60ch; }

/* ── Generic ruled note: a tag, a heading, a paragraph, an
      optional link — for content that is one voice, not a
      figure + body pair. CSR entries, careers copy, any prose
      row that would otherwise become an unnecessary card. ── */
.nl-note-list { border-top: 1px solid var(--nl-rule-strong); }
.nl-note {
  padding: clamp(22px, 2.4vw, 30px) 0;
  border-bottom: 1px solid var(--nl-rule);
}
.nl-note .nl-h3 { margin-top: 2px; }
.nl-note p { margin-top: 10px; font-weight: 300; line-height: 1.62; color: var(--nl-ink-2); font-size: .9375rem; max-width: 68ch; }
.nl-note .nl-link { margin-top: 14px; }
/* A note that carries a picture: media left, copy right, on the same
   hairline register as the plain notes. The frame is authored at 16:10 so
   the placeholder and the real photograph occupy identical space — swapping
   one for the other must not move the copy beside it. */
.nl-note--media { display: grid; gap: clamp(16px, 2vw, 28px); }
@media (min-width: 760px) {
  .nl-note--media { grid-template-columns: minmax(0, 34fr) minmax(0, 66fr); align-items: start; }
}
.nl-note--media > .nl-note-copy > *:first-child { margin-top: 0; }
/* Photo placeholder — deliberately plain: a ruled empty frame that reads as
   "not yet supplied" rather than as a broken image. Replace the whole
   element with an <img> in the same frame when the photograph arrives. */
.nl-ph {
  aspect-ratio: 16 / 10;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px;
  background: var(--nl-stage);
  border: 1px dashed var(--nl-rule-strong);
  border-radius: var(--nl-r);
  color: var(--nl-ink-2);
}
.nl-ph i { font-size: 26px; opacity: .55; }
.nl-ph span {
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
}
/* The real photograph, in the same 16:10 frame the placeholder used —
   swapping one for the other must not move the copy beside it. */
.nl-note-photo {
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: var(--nl-r);
}
.nl-note-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (min-width: 700px) {
  /* Two entries side by side where the content is short and
     paired (e.g. a Singapore / Indonesia careers note) — still
     one register, two columns of the same row. */
  .nl-note-list--pair { display: grid; grid-template-columns: 1fr 1fr; column-gap: clamp(24px, 3vw, 48px); }
  .nl-note-list--pair .nl-note:nth-child(odd) { border-right: 1px solid var(--nl-rule); padding-right: clamp(24px, 3vw, 48px); }
  .nl-note-list--pair .nl-note:nth-child(even) { padding-left: clamp(24px, 3vw, 48px); }
  /* Side by side, the left note is no longer the row above the
     right one — its bottom rule closes nothing and reads as a
     stray hairline under one column. The final row is closed by
     the section boundary, same as :last-child elsewhere. */
  .nl-note-list--pair .nl-note:nth-last-child(-n+2) { border-bottom: 0; }
}

/* ── Generic ruled item: a name column and a description
      column on one row — the AI-capability list, the solution
      capability groups, the integration badges. The region
      list's id/description/side mechanism, without the flag. ── */
.nl-item-list { border-top: 1px solid var(--nl-rule-strong); }
.nl-item {
  display: grid;
  grid-template-columns: minmax(0, 230px) minmax(0, 1fr);
  gap: clamp(16px, 2.4vw, 40px);
  align-items: baseline;
  padding: 16px 0;
  border-bottom: 1px solid var(--nl-rule);
}
.nl-item-name { font-size: .9688rem; font-weight: 600; letter-spacing: -.015em; color: var(--rv-navy); }
.nl-item p { font-weight: 300; line-height: 1.6; color: var(--nl-ink-2); font-size: .9375rem; max-width: 62ch; }
.nl-item p + .nl-chip,
.nl-item-name + .nl-chip { margin-top: 8px; }
.nl-field .nl-item-name { color: #fff; }
.nl-field .nl-item p { color: var(--nl-on-navy-2); }
.nl-field.nl-item-list,
.nl-field .nl-item-list { border-top-color: var(--nl-navy-rule); }
.nl-field .nl-item { border-bottom-color: var(--nl-navy-rule); }

/* A small inline note — "On our roadmap", "In collaboration with
   Tzu Chi Foundation" — sentence case, quiet, never a saturated
   badge competing with the row's own hue. */
.nl-chip {
  display: inline-flex; align-items: baseline; gap: 5px;
  font-size: .8125rem; color: var(--nl-ink-2);
}
.nl-chip b { font-weight: 600; color: var(--rv-navy); }
.nl-chip i { font-size: 12px; color: var(--rv-accent); }

/* Sub-register label inside a section (e.g. "Care-Centred AI"
   ahead of a group of .nl-item rows) — the marker mechanism at
   a lighter weight, without opening a new bordered block. */
.nl-sub {
  margin-top: clamp(30px, 3.4vw, 44px); margin-bottom: 4px;
  font-size: .75rem; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--nl-ink-2);
}
.nl-sub:first-child { margin-top: 0; }
/* A sub-label introducing a marquee: the marquee draws its own top rule
   immediately below, so this one must not double it. */
.nl-sub--marquee { border-bottom: 0; }
/* A sub-label opening a column that already sits under a section rule. */
.nl-sub--flush { margin-top: 0; }
.nl-field .nl-sub { color: var(--nl-on-navy-2); }

/* ── Ruled offices — for the contact page. Flag/name pair,
      address, mail link: the region-id mechanism minus status. ── */
.nl-office-list { border-top: 1px solid var(--nl-rule-strong); display: grid; gap: 0; }
.nl-office { padding: clamp(20px, 2.2vw, 26px) 0; border-bottom: 1px solid var(--nl-rule); }
.nl-office-id { display: flex; align-items: center; gap: 10px; }
/* Inside a note the office row is a heading, so it needs the gap a
   heading would take before its body copy. `.nl-h3--sm` is a no-op on
   size — `.nl-office-id h3` below already sets the step — and exists so
   the markup names the intent rather than carrying a literal. */
.nl-note .nl-office-id { margin-bottom: 6px; }
.nl-h3--sm { font-size: 1rem; letter-spacing: -.018em; }
.nl-office-id img { width: 26px; height: 26px; border-radius: var(--nl-r-sm); flex: none; }
.nl-office-id h3 { font-size: 1rem; font-weight: 600; letter-spacing: -.018em; color: var(--rv-navy); }
.nl-office address { margin-top: 10px; font-style: normal; font-weight: 300; line-height: 1.6; color: var(--nl-ink-2); font-size: .9375rem; }
.nl-office .nl-link {
  display: flex;
  width: fit-content;
  margin-top: 10px;
}

/* Contact page's two-column body: form on the left, offices
   and "what happens next" on the right — the certification
   section's 46/54 split, reused for a form instead of copy. */
.nl-contact-inner {
  display: grid;
  grid-template-columns: minmax(0, 58fr) minmax(0, 42fr);
  gap: clamp(32px, 4.4vw, 72px);
  /* Longhands only — this element also carries .nl-wrap, and a padding
     shorthand here would zero out that gutter and run the form to the
     viewport edge on tablet and mobile. */
  padding-top: clamp(40px, 4.6vw, 64px);
  padding-bottom: clamp(58px, 6.4vw, 90px);
  align-items: start;
}
.nl-contact-form h2 { margin-top: 0; margin-bottom: 24px; }
.nl-contact-form > p { margin-top: 8px; margin-bottom: 22px; color: var(--nl-ink-2); font-size: .9375rem; }
@media (max-width: 900px) { .nl-contact-inner { grid-template-columns: minmax(0, 1fr); } }

/* ── Article grid — news-info. Three columns of stacked cards,
      with the lead story spanning the full measure above them:
      never a different card, only a different span. ── */
.nl-article-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 2.4vw, 32px);
  border-top: 1px solid var(--nl-rule-strong);
  padding-top: clamp(22px, 2.6vw, 32px);
}
.nl-article {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0;
  min-width: 0;
  background: #fff;
  border-radius: var(--nl-r);
  box-shadow: inset 0 0 0 1px var(--nl-rule);
  overflow: hidden;
}
/* The text block fills the card so a row's feet line up regardless of how
   long each excerpt runs. */
.nl-article > div:last-child {
  display: flex; flex-direction: column;
  padding: clamp(16px, 1.6vw, 22px);
}
.nl-article .nl-article-foot { margin-top: auto; padding-top: 14px; }
/* The lead runs the full width and keeps the side-by-side split — the
   only thing that changes at the top of the grid is the span. */
.nl-article:first-child {
  grid-column: 1 / -1;
  grid-template-rows: auto;
  grid-template-columns: minmax(0, 46fr) minmax(0, 54fr);
}
.nl-article-figure { position: relative; overflow: hidden; aspect-ratio: 16 / 10; background: var(--nl-stage); }
/* The lead image fills its column rather than holding a ratio — a fixed ratio
   would size it off its own width and overrun the text beside it. Capped
   with max-height too: a portrait source (tall relative to the 46fr column's
   width) otherwise inflates the whole row to its own intrinsic height
   instead of being cropped by object-fit to match the text column. */
.nl-article:first-child .nl-article-figure {
  aspect-ratio: auto; height: 100%;
  min-height: clamp(220px, 24vw, 300px);
  max-height: clamp(220px, 24vw, 340px);
}
.nl-article-figure img { width: 100%; height: 100%; object-fit: cover; }
.nl-article-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; font-size: .75rem; color: var(--nl-ink-2); }
.nl-article-meta time { letter-spacing: .01em; }
.nl-article h3 { margin-top: 10px; font-size: 1.0625rem; font-weight: 600; line-height: 1.3; letter-spacing: -.018em; color: var(--rv-navy); }
.nl-article:first-child h3 { font-size: 1.3125rem; }
/* The title is the only link in the row; it stretches over the whole card so
   the image and text are clickable, without a second thing to tab to. */
.nl-article.is-linked { position: relative; }
.nl-article-link { color: inherit; text-decoration: none; }
.nl-article-link::after { content: ""; position: absolute; inset: 0; border-radius: var(--nl-r); }
.nl-article.is-linked:hover h3,
.nl-article-link:focus-visible { color: var(--rv-primary); }
.nl-article.is-linked:hover .nl-article-figure img { transform: scale(1.03); }
.nl-article-figure img { transition: transform .4s cubic-bezier(.2, .7, .3, 1); }
.nl-article-link:focus-visible::after { outline: 2px solid var(--rv-primary); outline-offset: 4px; }
@media (prefers-reduced-motion: reduce) {
  .nl-article-figure img { transition: none; }
  .nl-article.is-linked:hover .nl-article-figure img { transform: none; }
}
.nl-article p {
  margin-top: 10px; font-weight: 300; line-height: 1.6; color: var(--nl-ink-2); font-size: .9375rem;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; overflow: hidden;
}
.nl-article-foot { margin-top: 12px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.nl-article-foot > span { font-size: .75rem; color: var(--nl-ink-2); }
@media (max-width: 1040px) {
  .nl-article-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .nl-article-list { grid-template-columns: minmax(0, 1fr); }
  .nl-article:first-child {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
  }
  .nl-article:first-child .nl-article-figure {
    height: auto; min-height: 0; aspect-ratio: 16 / 10;
  }
}

/* ── Filter chips — the `.nl-vert` chip promoted to a real
      toggle button. Selected takes a brand-blue fill rather than
      the tab register's tinted ground: a tab carries a 3px colour
      rule to say which one is live, and a chip has no such rule,
      so the ground has to do the whole job. An 8% tint could not.
      The State-Blue Rule still holds — blue means *selected*. */
.nl-fbtn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: inherit;
  font-size: .8125rem; font-weight: 500;
  line-height: 1.25;
  padding: 8px 14px;
  color: var(--nl-ink-2);
  background: #F1F4FB;
  border: 0; border-left: 1px solid var(--nl-rule-strong);
  border-radius: 0 var(--nl-r-sm) var(--nl-r-sm) 0;
  cursor: pointer;
  transition: background 140ms var(--nl-ease), color 140ms var(--nl-ease),
              border-color 140ms var(--nl-ease), transform 120ms var(--nl-ease);
}
.nl-fbtn-flag {
  width: 18px;
  height: 18px;
  flex: none;
  object-fit: cover;
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--nl-rule-strong);
}
.nl-fbtn[aria-pressed="true"] .nl-fbtn-flag {
  box-shadow: 0 0 0 1px rgba(255,255,255,.72);
}
/* Deep blue rather than brand blue: white on `--rv-primary` measures
   4.4:1, which fails at this size. The darker sibling is 5.9:1 and is
   already this system's link ink and pressed state. */
.nl-fbtn[aria-pressed="true"],
.nl-fbtn.active {
  background: var(--rv-primary-dark);
  border-left-color: var(--rv-navy);
  color: #fff; font-weight: 600;
}
.nl-fbtn:active { transform: scale(.975); }

/* The setting buttons double as the legend for the cover-line dots, so
   they carry the same six hues. The dot survives the pressed state on a
   white ring, which is what ties a chosen filter to the cards below. */
.nl-fbtn[data-filter="vertical"]::before {
  content: ""; flex: none;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--nl-cov-hue, transparent);
  box-shadow: inset 0 0 0 1.5px transparent;
}
.nl-fbtn[data-value="AAC"]              { --nl-cov-hue: var(--nl-aac); }
.nl-fbtn[data-value="Day Care"]         { --nl-cov-hue: var(--nl-day); }
.nl-fbtn[data-value="Home Care"]        { --nl-cov-hue: var(--nl-home); }
.nl-fbtn[data-value="CCMS"]             { --nl-cov-hue: var(--nl-ccms); }
.nl-fbtn[data-value="Residential Care"] { --nl-cov-hue: var(--nl-res); }
.nl-fbtn[data-value="ISHC Clubs"]       { --nl-cov-hue: var(--nl-ishc); }
/* "All settings" is the absence of a hue, matching its ringed cover dot. */
.nl-fbtn[data-filter="vertical"][data-value="all"]::before {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px currentColor;
  opacity: .55;
}
/* On the pressed navy, a saturated dot loses its edge — ring it in white. */
.nl-fbtn[data-filter="vertical"][aria-pressed="true"]::before {
  box-shadow: 0 0 0 1.5px rgba(255,255,255,.85);
}
.nl-fbtn[data-filter="vertical"][data-value="all"][aria-pressed="true"]::before {
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.9);
  opacity: 1;
}
/* Not offered in this region. Four signals stack so the split reads at a
   glance rather than on inspection: the ground drops away, the rule turns
   dashed, the label is struck through in its own ink, and — the one that
   was doing the most damage by staying lit — the setting's cover hue is
   emptied to a hollow ring, since a saturated dot reads as "on". The ink
   softens one step to sit behind the live chips but still clears AA at
   4.9:1, and the control stays focusable so the `title` explaining why
   is reachable. */
.nl-fbtn[aria-disabled="true"] {
  background: none;
  border-left: 1px dashed var(--nl-rule-strong);
  color: #64718B; font-weight: 400;
  text-decoration: line-through;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1px;
  cursor: not-allowed;
}
/* The hue is the legend for the cover lines below, and those lines never
   print a setting the region does not offer — so an unavailable chip must
   not hold a filled swatch. Hollow it out to match. */
.nl-fbtn[data-filter="vertical"][aria-disabled="true"]::before {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px currentColor;
  opacity: .5;
}
.nl-fbtn[aria-disabled="true"]:active { transform: none; }
@media (hover: hover) and (pointer: fine) {
  .nl-fbtn:not([aria-disabled="true"], [aria-pressed="true"]):hover {
    background: color-mix(in srgb, var(--rv-primary) 9%, #F1F4FB);
    border-left-color: var(--rv-primary);
    color: var(--rv-navy);
  }
  .nl-fbtn[aria-pressed="true"]:hover { background: var(--rv-navy); border-left-color: var(--rv-navy); }
}

/* ── 404: a register, not a centred poster. Left-aligned like
      every other page; the suggestion is a single ruled row,
      the fallback nav a row of arrow links. ── */
.nl-404 { padding: clamp(46px, 6vw, 84px) 0 clamp(64px, 7vw, 100px); }
.nl-404-mark { width: 64px; height: 64px; margin-bottom: 22px; }
.nl-404-suggestion {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: clamp(24px, 2.8vw, 32px);
  padding: 16px 18px;
  border-top: 1px solid var(--nl-rule-strong);
  border-bottom: 1px solid var(--nl-rule-strong);
  border-left: 1px solid var(--rv-primary);
  border-radius: 0 var(--nl-r-sm) var(--nl-r-sm) 0;
  text-decoration: none;
  transition: background 140ms var(--nl-ease);
}
.nl-404-suggestion-label { font-size: .6875rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--nl-ink-2); }
.nl-404-suggestion-name { margin-top: 3px; font-size: 1rem; font-weight: 600; color: var(--rv-navy); }
.nl-404-suggestion-desc { margin-top: 2px; font-size: .8438rem; color: var(--nl-ink-2); }
.nl-404-suggestion > i { flex: none; font-size: 18px; color: var(--rv-primary); transition: transform 180ms var(--nl-ease); }
@media (hover: hover) and (pointer: fine) {
  .nl-404-suggestion:hover { background: var(--rv-primary-light); border-color: var(--rv-primary); }
  .nl-404-suggestion:hover > i { transform: translateX(3px); }
}
.nl-404-nav-label { margin-top: clamp(32px, 3.6vw, 44px); }
.nl-404-nav { border-top: 1px solid var(--nl-rule-strong); margin-top: 10px; }
.nl-404-nav .nl-link { display: flex; padding: 13px 0; border-bottom: 1px solid var(--nl-rule); border-bottom-width: 1px; }
.nl-404-nav a.nl-link { width: 100%; justify-content: space-between; border-bottom: 1px solid var(--nl-rule) !important; padding-bottom: 13px !important; }

/* ── Legacy grid → register: flatten the multi-column card
      grids that survive the token retint into single-column
      ruled lists. Each block below targets one page's existing
      class names; none of the HTML, JS or data attributes that
      drive filtering/search change — only the visual mechanism. */

/* about.html — mission cards already migrated to .nl-mission-*
   in markup; region panels, network logos and CSR/careers use
   the new components above. No legacy-grid overrides needed. */

/* how-it-works.html — the audience, capability and integration
   registers. These are native register components, not retinted
   legacy grids: the page's three content blocks were migrated off
   .eco-*/.ai-*/.integ-* markup, which had carried the legacy
   `.container` (1200px / 28px pad) against the register layer's
   `.nl-wrap` (1240px / fluid gutter) and so broke the one-left-edge
   rule at every section boundary. */

/* A section that closes straight into the one below it, where a
   second head would out-weigh the rule between them. */
.nl-sec--tight { padding-top: clamp(38px, 4.2vw, 58px); }

/* ── The audience grid: four equal relationships, presented as a
      compact 2-by-2 system instead of four full-width rows. ───── */
.nl-sec--audiences { padding: clamp(42px, 4.2vw, 60px) 0 clamp(48px, 4.8vw, 68px); }
.nl-sec--audiences .nl-sec-head { margin-bottom: clamp(22px, 2.4vw, 30px); }
.nl-aud {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(18px, 2vw, 28px);
  padding-top: clamp(20px, 2.2vw, 28px);
}
.nl-aud-row {
  display: grid;
  grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
  align-items: stretch;
  min-width: 0;
  min-height: 210px;
  padding: 0;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--nl-rule);
  overflow: hidden;
}
.nl-aud-body {
  align-self: center;
  min-width: 0;
  padding: clamp(20px, 2.1vw, 28px);
}
.nl-aud-shot {
  height: 100%;
  min-height: 210px;
  object-fit: cover;
  width: 100%;
  border-radius: 0;
}
/* The not-yet state, drawn deliberately rather than left broken:
   the shot's own footprint on the stage field, with a hairline and
   a label saying what belongs here. Swapping this div for an <img
   class="nl-aud-shot"> needs no other change. */
div.nl-aud-shot {
  display: flex; align-items: flex-end;
  padding: 12px;
  background: var(--nl-stage);
  box-shadow: inset 0 0 0 1px var(--nl-rule-strong);
}
.nl-aud-shot-label {
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--nl-ink-2);
}
.nl-aud-name {
  font-size: 1.0625rem; font-weight: 600;
  line-height: 1.28; letter-spacing: -.02em;
  color: var(--rv-navy);
}
.nl-aud-name + .nl-body { margin-top: 7px; max-width: 46ch; }

/* ── The capability register: three AI families, each a group
      head on the rule that opens its own register. ──────────── */
.nl-cap-group + .nl-cap-group { margin-top: clamp(30px, 3.4vw, 46px); }
.nl-cap-head {
  font-size: .625rem; font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase;
  color: var(--nl-ink-2);
  padding-bottom: 9px;
}
/* The AI register is drawn as the solutions sheet is drawn: a card
   grid whose cells share hairlines instead of sitting in gutters,
   so a group reads as one ruled sheet rather than as a stack of
   floating boxes. Same opening 3px rule, same eased closing
   corners, same square flat cells. */
.nl-cap-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-left: 1px solid var(--nl-rule);
  border-top: 3px solid var(--nl-rule-strong);
  border-radius: 0 0 var(--nl-r) var(--nl-r);
  overflow: hidden;
}
.nl-cap-row {
  display: flex; flex-direction: column;
  padding: clamp(16px, 1.5vw, 20px) clamp(15px, 1.4vw, 19px) clamp(14px, 1.3vw, 17px);
  background: #fff;
  border-right: 1px solid var(--nl-rule);
  border-bottom: 1px solid var(--nl-rule);
}
/* The capability's glyph, at the size and brand blue the solutions
   card gives it, stacked above the name. */
.nl-cap-name {
  display: block;
  font-size: 1rem; font-weight: 600;
  line-height: 1.28; letter-spacing: -.022em;
  color: var(--rv-navy);
  text-wrap: balance;
}
.nl-cap-name i {
  display: block; margin-bottom: 11px;
  font-size: 21px; line-height: 1;
  color: var(--rv-primary);
}
.nl-cap-row .nl-body {
  margin-top: 8px;
  font-size: .875rem; font-weight: 300; line-height: 1.58;
  color: var(--nl-ink-2);
}
/* Not-yet, at full contrast with its own mark — never greyed out
   as a leftover. The hue sits on the icon; the words take the
   darkened sibling ink so they clear 4.5:1 at this size. As on the
   solutions card, a roadmap position is a fact about the card, so
   it is anchored to the card's foot behind its own rule rather
   than trailing the description. */
.nl-cap-status {
  display: flex; align-items: baseline; gap: 7px;
  margin-top: auto; padding-top: 9px;
  border-top: 1px solid var(--nl-rule);
  font-size: .75rem; font-weight: 400; line-height: 1.45;
  color: var(--nl-ink-soon);
}
.nl-cap-status i { flex: none; font-size: 13px; color: var(--rv-accent); }

/* ── The integration register: one row per market. ─────────── */
.nl-integ { border-top: 1px solid var(--nl-rule-strong); }
.nl-integ-row {
  display: grid;
  grid-template-columns: minmax(0, 250px) minmax(0, 1fr);
  gap: clamp(16px, 2.2vw, 36px);
  align-items: baseline;
  border-bottom: 1px solid var(--nl-rule);
  padding: 15px 0;
}
.nl-integ-market {
  display: flex; align-items: center; gap: 9px;
  font-size: .9688rem; font-weight: 600;
  letter-spacing: -.02em; color: var(--rv-navy);
}
.nl-integ-flag {
  width: 22px; height: 22px; flex: none;
  border-radius: 50%; object-fit: cover;
  box-shadow: 0 0 0 1px var(--nl-rule-strong);
}
/* A chip carries a 1px left border in place of a border-box. */
.nl-integ-list {
  display: flex; flex-wrap: wrap; gap: 8px;
  list-style: none; margin: 0; padding: 0;
}
.nl-integ-list li {
  padding: 5px 10px;
  border-left: 1px solid var(--nl-rule-strong);
  background: #F1F4FB;
  font-size: .8125rem; font-weight: 500;
  letter-spacing: -.005em; color: var(--rv-navy);
}

@media (max-width: 860px) {
  .nl-aud { grid-template-columns: minmax(0, 1fr); }
  .nl-aud-row { grid-template-columns: minmax(0, 260px) minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .nl-aud-row { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .nl-aud-shot { height: auto; min-height: 0; aspect-ratio: 16 / 8; }
  .nl-integ-row { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .nl-integ-row { gap: 12px; }
}

/* ═══════════════════════════════════════════════════════════
   SOLUTIONS — the capability sheet.

   Forty-eight capabilities in one ruled column ran 5,100px on
   desktop and 9,100px on a phone, at a 105ch measure, with no
   way to scan it. They are filing cards now: a card grid, which
   the register world otherwise refuses, drawn as the register
   world would draw one. The cells share hairlines instead of
   sitting in gutters, so a family reads as one ruled sheet
   rather than as forty-eight floating boxes — square, flat, no
   resting shadow, one left edge.

   Every rule here is `.nl-`-namespaced or `body.nl`-scoped for
   a reason: `revamp.css`'s `.rv *, .rv ::before, .rv ::after`
   reset zeroes margin and padding at (0,1,0) and loads after
   `styles.css`, so a bare single-class rule in the legacy sheet
   silently loses its box model on any `.rv` page. That is what
   flattened the old filter bar to bare 18px text.
   ═══════════════════════════════════════════════════════════ */

/* ── Guided opening: the page first explains where the platform
      fits and what changes, then hands the visitor the catalogue. ── */
.nl-solutions-page .nl-media-hero-copy { max-width: 610px; }
.nl-solutions-page .nl-media-hero-copy .nl-h1 { max-width: 12ch; }
.nl-solutions-page .nl-media-hero-copy .nl-lead { max-width: 55ch; }
.nl-sol-start { padding: clamp(52px, 6vw, 86px) 0; background: #fff; border-bottom: 1px solid var(--nl-rule); }
/* Stacked, not a two-column head: the heading holds one line and
   the description reads directly under it, the same register as
   every other section head on the site. */
.nl-sol-start-head {
  margin-bottom: clamp(28px, 3.5vw, 44px);
}
.nl-sol-start-head .nl-h2 { margin-top: 14px; max-width: none; }
.nl-sol-start-head .nl-body { margin-top: 14px; max-width: 62ch; }
.nl-sol-paths {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--nl-rule-strong);
  border-left: 1px solid var(--nl-rule);
}
.nl-sol-path {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  padding: 22px clamp(18px, 2.2vw, 28px);
  font: inherit;
  text-align: left;
  color: var(--rv-navy);
  background: #fff;
  border: 0;
  border-right: 1px solid var(--nl-rule);
  border-bottom: 1px solid var(--nl-rule);
  cursor: pointer;
  transition: background 180ms var(--nl-ease), color 180ms var(--nl-ease);
}
/* Larger than the capability cards' 21px glyphs on purpose: these are
   the page's entry points and sit in a wide two-up cell, where a 21px
   mark read as a speck against the surrounding whitespace. */
.nl-sol-path > i:first-child { font-size: 27px; color: var(--rv-primary); }
.nl-sol-path > i:last-child { font-size: 17px; color: var(--rv-primary); transition: transform 180ms var(--nl-ease); }
.nl-sol-path strong { display: block; font-size: 1.0625rem; font-weight: 600; letter-spacing: -.02em; }
.nl-sol-path small { display: block; max-width: 52ch; margin-top: 4px; font-size: .875rem; font-weight: 300; line-height: 1.45; color: var(--nl-ink-2); }
@media (hover: hover) and (pointer: fine) {
  .nl-sol-path:hover { background: var(--rv-primary-light); }
  .nl-sol-path:hover > i:last-child { transform: translateX(4px); color: var(--rv-primary); }
}
.nl-sol-path:active { background: #e7edfc; }

.nl-sol-catalog-head {
  margin-bottom: 26px;
}
.nl-sol-catalog-head .nl-h2 { margin-top: 14px; max-width: none; }
.nl-sol-catalog-head .nl-body { margin-top: 14px; max-width: 62ch; }
.nl-sol-cat-nav {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  margin-bottom: clamp(34px, 4vw, 52px);
  border-top: 1px solid var(--nl-rule-strong);
  background: #fff;
  border-left: 1px solid var(--nl-rule);
}
/* Folded into the filter panel: same box, the jump list as its lower band.
   The panel draws the outer frame, so the nav keeps only its dividing rule
   and drops the edges that would double the box's own. */
.nl-sol-rail .nl-sol-cat-nav { margin-bottom: 0; border-left: 0; }
.nl-sol-rail .nl-sol-cat-link { border-bottom: 0; }
.nl-sol-rail .nl-sol-cat-link:last-child { border-right: 0; }
.nl-sol-cat-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 70px;
  padding: 12px 14px;
  border-right: 1px solid var(--nl-rule);
  border-bottom: 1px solid var(--nl-rule);
  font-size: .75rem;
  font-weight: 500;
  line-height: 1.35;
  color: var(--rv-navy);
  text-decoration: none;
  background: #fff;
  transition: background 160ms var(--nl-ease);
}
.nl-sol-cat-link b { flex: none; font-size: .6875rem; color: var(--nl-ink-2); }
@media (hover: hover) and (pointer: fine) { .nl-sol-cat-link:hover { background: var(--rv-primary-light); } }
/* Folded into the filter panel: same box, the jump list as its lower band.
   The panel draws the outer frame, so the nav keeps only its dividing rule
   and drops the edges that would double the box's own. */
.nl-sol-rail .nl-sol-cat-nav { margin-bottom: 0; border-left: 0; }
.nl-sol-rail .nl-sol-cat-link { border-bottom: 0; }
.nl-sol-rail .nl-sol-cat-link:last-child { border-right: 0; }

/* The static twin of the sticky rail: jump targets scroll to this, because
   scrollIntoView on a pinned element overshoots by its own sticky offset. */
#nl-sol-anchor {
  display: block; height: 0;
  scroll-margin-top: calc(var(--nl-hdr-h, 76px) + 14px);
}
/* ── The filter rail. A boxed panel sitting inside the capability sheet,
      directly above the groups it filters — it used to be a full-bleed bar
      above the catalogue heading, where it read as a page-level control
      rather than as this list's filter. Not sticky: with the jump list
      folded in, the panel is ~255px tall, and pinning that under the
      header would hold two fifths of a laptop viewport for a control the
      reader has already used. It stays where it is read. `overflow: hidden`
      is what clips the nav's cells to the rounded corners. ─────────────────── */
.nl-sol-rail {
  position: relative;
  margin-bottom: clamp(34px, 4vw, 52px);
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--nl-rule-strong);
  border-radius: var(--nl-r);
  box-shadow: 0 10px 26px rgba(12,27,80,.07);
}
/* An explicit grid, not a flex wrap. Region and care setting are two
   different axes, so each keeps its own row rather than sharing one
   line behind a divider — two clear layers at every desktop width,
   not just below the tablet breakpoint. */
.nl-sol-rail-inner {
  display: grid;
  align-items: center;
  gap: 9px 14px;
  padding: clamp(14px, 1.5vw, 18px) clamp(15px, 1.6vw, 22px);
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "title end" "rlabel rlabel" "region region" "slabel slabel" "settings settings";
}
/* Names the panel's job outright: these chips filter the capability groups
   below them, not the page. */
.nl-sol-rail-title {
  grid-area: title;
  justify-self: start;
  font-size: .9375rem; font-weight: 600;
  letter-spacing: -.012em; color: var(--rv-navy);
}
/* The region row is a question, not a bare set of chips: the label
   names the axis so "Singapore" is read as where you are, not as
   another capability filter. */
.nl-sol-flabel {
  grid-area: rlabel;
  justify-self: start; align-self: end;
  font-size: .6875rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--nl-ink-2);
}
.nl-sol-flabel--settings { grid-area: slabel; margin-top: 4px; }
.nl-sol-chips { display: flex; flex-wrap: wrap; gap: 8px; }
/* Named, not :nth-of-type — the label above the region row is a div
   too, and counting types silently handed it the region cell. */
.nl-sol-chips--region { grid-area: region; }
.nl-sol-chips--settings { grid-area: settings; }
.nl-sol-rail-rule { display: none; }
.nl-sol-selects { grid-area: settings; }

.nl-sol-tally {
  grid-area: end; justify-self: end;
  font-size: .8125rem; font-weight: 300; color: var(--nl-ink-2);
  white-space: nowrap;
}
.nl-sol-tally .nl-fig { font-weight: 600; color: var(--rv-navy); }

/* Narrow viewports swap the chip rows for the two native selects
   already in the markup — a real picker beats a 9-chip wrap on a
   thumb. Square, hairline, the page's own caret. */
.nl-sol-selects { display: none; gap: 10px; grid-template-columns: 1fr 1fr; }
.nl-sol-select { position: relative; display: block; }
.nl-sol-select select {
  width: 100%;
  padding: 10px 32px 10px 12px;
  font-family: inherit; font-size: .8125rem; font-weight: 500;
  color: var(--rv-navy); background: #F1F4FB;
  border: 0; border-left: 1px solid var(--nl-rule-strong); border-radius: 0 var(--nl-r-sm) var(--nl-r-sm) 0;
  -webkit-appearance: none; appearance: none;
  cursor: pointer;
}
.nl-sol-select i {
  position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  font-size: 12px; color: var(--nl-ink-2); pointer-events: none;
}

/* ── The sheet. A light field one step above paper so the white
      cells read as cells; the hairlines still carry structure. ── */
.nl-sol {
  background: var(--nl-stage);
  padding: clamp(32px, 3.8vw, 54px) 0 clamp(56px, 6.6vw, 96px);
}
.nl-sol-group + .nl-sol-group { margin-top: clamp(40px, 4.6vw, 68px); }
.nl-sol-group.is-out { display: none; }
.nl-sol-group-hd {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px 20px; flex-wrap: wrap;
  padding-bottom: 11px;
}
.nl-sol-group-hd h2 {
  font-size: clamp(1.1875rem, 1.85vw, 1.5rem); font-weight: 600;
  line-height: 1.15; letter-spacing: -.028em; color: var(--rv-navy);
}
/* Right-aligned on the sheet's edge, where a column head belongs.
   At 1240px that is a long way from the heading, so it carries its
   own unit and reads on its own rather than as a loose number. */
.nl-sol-count { font-size: .8125rem; font-weight: 300; color: var(--nl-ink-2); white-space: nowrap; }
.nl-sol-count .nl-fig { font-weight: 600; color: var(--rv-navy); }

/* The sheet opens on a 3px rule, the same mark the tab register
   uses. With a care setting selected it takes that setting's hue,
   so the active filter stays legible three thousand pixels down
   the page; with none selected it is a neutral strong rule —
   absence drawn as deliberately as presence. */
.nl-sol-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-left: 1px solid var(--nl-rule);
  border-top: 3px solid var(--nl-c, var(--nl-rule-strong));
  /* The setting's 3px rule is the register's opening edge, so it stays a
     full-width straight line — only the closing corners are eased. */
  border-radius: 0 0 var(--nl-r) var(--nl-r);
  overflow: hidden;
  transition: border-top-color 240ms var(--nl-ease);
}
.nl-sol-card {
  display: flex; flex-direction: column;
  padding: clamp(16px, 1.5vw, 20px) clamp(15px, 1.4vw, 19px) clamp(14px, 1.3vw, 17px);
  background: #fff;
  border-right: 1px solid var(--nl-rule);
  border-bottom: 1px solid var(--nl-rule);
}
.nl-sol-card.is-out,
.nl-sol-card.is-folded { display: none; }
/* One Phosphor glyph per card, chosen for what the capability
   actually does. Brand blue, the same ink as the care-setting
   icons above, so the sheet reads as one set of marks. */
.nl-sol-ico {
  display: block; margin-bottom: 11px;
  font-size: 21px; line-height: 1;
  color: var(--rv-primary);
}
.nl-sol-name {
  font-size: 1rem; font-weight: 600;
  line-height: 1.28; letter-spacing: -.022em;
  color: var(--rv-navy);
  text-wrap: balance;
}
.nl-sol-desc {
  margin-top: 8px;
  font-size: .875rem; font-weight: 300; line-height: 1.58;
  color: var(--nl-ink-2);
}
/* A named partner or a roadmap position is a fact about the card,
   so it gets a rule of its own rather than a tinted pill. */
.nl-sol-note {
  margin-top: 11px; padding-top: 9px;
  border-top: 1px solid var(--nl-rule);
  font-size: .75rem; font-weight: 300; line-height: 1.45;
  color: var(--nl-ink-2);
}
.nl-sol-note strong { font-weight: 600; color: var(--rv-navy); }
.nl-sol-note--soon { display: flex; align-items: baseline; gap: 7px; color: var(--nl-ink-soon); }
.nl-sol-note--soon strong { color: var(--nl-ink-soon); }
.nl-sol-note--soon i { flex: none; font-size: 13px; color: var(--rv-accent); }
/* Anchored to the card's foot, so a row of unequal cards still
   closes on one line. It answers the question the filter asks
   before you ask it: where does this actually run? */
.nl-sol-cover {
  margin-top: auto; padding-top: 16px;
  font-size: .6875rem; font-weight: 600; line-height: 1.45;
  color: var(--nl-ink-2);
}
/* The "Singapore only" lead is carried by the words, not a second ink. */
.nl-sol-cover b { font-weight: 600; color: inherit; }

/* Colour as wayfinding: each setting keeps one hue across the sheet, so a
   column of cards is scannable as colour before it is read as words. The
   dot carries the hue and the label stays at the cover line's own ink —
   six saturated texts at 11px uppercase would fight the card titles. */
.nl-cov {
  display: inline-flex; align-items: center; gap: 5px;
  white-space: nowrap;
}
.nl-cov::before {
  content: ""; flex: none;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--nl-cov-hue, var(--nl-ink-2));
}
.nl-cov[data-v="aac"]  { --nl-cov-hue: var(--nl-aac); }
.nl-cov[data-v="day"]  { --nl-cov-hue: var(--nl-day); }
.nl-cov[data-v="home"] { --nl-cov-hue: var(--nl-home); }
.nl-cov[data-v="ccms"] { --nl-cov-hue: var(--nl-ccms); }
.nl-cov[data-v="res"]  { --nl-cov-hue: var(--nl-res); }
.nl-cov[data-v="ishc"] { --nl-cov-hue: var(--nl-ishc); }
/* "All Settings" is every hue at once, so it gets the ring, not a colour. */
.nl-cov[data-v="all"]::before {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--nl-ink-2);
}

@media (prefers-reduced-motion: no-preference) {
  .nl-sol-card:hover .nl-cov::before { transform: scale(1.15); }
  .nl-cov::before { transition: transform 180ms var(--nl-ease, ease); }
}

.nl-sol-group { scroll-margin-top: calc(var(--nl-hdr-h, 76px) + 122px); }
.nl-sol-more {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 12px;
  padding: 12px 16px;
  font: inherit;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--rv-primary-dark);
  background: transparent;
  border: 0;
  cursor: pointer;
}
.nl-sol-more i { font-size: 12px; }
.nl-sol-more:hover { color: var(--rv-navy); background: rgba(255,255,255,.55); }
.nl-sol-more[hidden] { display: none; }

.nl-solutions-page .rv-sticky { display: none; }

.nl-sol-empty {
  background: #fff;
  border-top: 3px solid var(--nl-rule-strong);
  border-radius: 0 0 var(--nl-r) var(--nl-r);
  padding: clamp(38px, 4.6vw, 60px) clamp(18px, 2.2vw, 30px);
}
.nl-sol-empty h2 {
  font-size: clamp(1.125rem, 1.8vw, 1.4rem); font-weight: 600;
  line-height: 1.2; letter-spacing: -.026em; color: var(--rv-navy);
}
.nl-sol-empty .nl-body { margin-top: 11px; max-width: 52ch; font-size: .9375rem; }
.nl-sol-empty .nl-btn { margin-top: 22px; }

/* 1080 — the sheet drops to two columns. The filter rail is already
   two rows at every width (see .nl-sol-rail-inner above). */
@media (max-width: 1080px) {
  .nl-sol-grid,
  .nl-cap-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nl-sol-cat-nav { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* 720 — one column, and the chip rows become the two native selects.
   Nine wrapped chips is not a filter on a thumb. */
/* 780 — one column, and the chip rows become the two native selects.
   The setting row needs about 700px of content width; below that it
   wraps to two lines and the sticky bar reaches 143px, which is a
   sixth of a phone screen. Nine wrapped chips is not a filter on a
   thumb anyway. */
@media (max-width: 780px) {
  .nl-sol-grid,
  .nl-cap-grid { grid-template-columns: minmax(0, 1fr); }
  .nl-sol-chips, .nl-sol-flabel { display: none; }
  .nl-sol-selects { display: grid; }
  .nl-sol-rail-inner {
    padding: 11px 0;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "title" "settings" "end";
  }
  .nl-sol-tally { justify-self: start; }
}

@media (max-width: 860px) {
  .nl-sol-paths { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
  .nl-sol-path { padding: 18px 15px; }
  .nl-sol-path > i:first-child { font-size: 24px; }
  .nl-sol-path small { font-size: .8125rem; }
  .nl-sol-cat-nav { display: flex; overflow-x: auto; scroll-snap-type: x proximity; }
  .nl-sol-cat-link { min-width: 150px; scroll-snap-align: start; }
}

/* news-info.html — category pills, featured story, article grid. */
body.nl .rv-news-hero { background: var(--rv-navy); }
body.nl .rv-news-hero::before { display: none; }
body.nl .rv-search-box { border-radius: var(--nl-r-sm); box-shadow: none; border: 1px solid var(--nl-navy-rule); background: rgba(255,255,255,.06); }
body.nl .rv-search-box input { color: #fff; }
body.nl .rv-search-box input::placeholder { color: var(--nl-on-navy-2); }
body.nl .rv-cat { border-radius: 0 var(--nl-r-sm) var(--nl-r-sm) 0; border-left: 1px solid var(--nl-rule-strong); border-top: 0; border-right: 0; border-bottom: 0; background: #F1F4FB; color: var(--nl-ink-2); }
body.nl .rv-cat[aria-pressed="true"] { background: color-mix(in srgb, var(--rv-primary) 10%, #F1F4FB); border-left-color: var(--rv-primary); color: var(--rv-navy); }
body.nl .rv-featured,
body.nl .rv-empty { border-radius: var(--nl-r); box-shadow: none; border: 1px solid var(--nl-rule-strong); }
body.nl .rv-cat-tag { border-radius: var(--nl-r-sm); }
body.nl .rv-articles { display: block; border-top: 1px solid var(--nl-rule-strong); }
body.nl .rv-article { border: 0; border-radius: 0; border-bottom: 1px solid var(--nl-rule); }
body.nl .rv-article:hover { transform: none; box-shadow: none; }
body.nl .rv-article .rv-thumb { display: none; }
body.nl .rv-newsletter { border-radius: var(--nl-r); box-shadow: none; background: var(--rv-navy); }
body.nl .rv-newsletter input,
body.nl .rv-newsletter button { border-radius: var(--nl-r-sm); }

/* contact.html — hero gradient + form card. */
body.nl .rv-contact-hero { background: var(--rv-navy); }
body.nl .rv-contact-form-card { border-radius: var(--nl-r); box-shadow: none; border: 1px solid var(--nl-rule-strong); background: #fff; }

/* Legacy button skins, wherever they still appear. */
body.nl .btn,
body.nl .footer-lnk,
body.nl .error-nav-tile { border-radius: var(--nl-r-sm); }
body.nl .btn:hover { transform: none; box-shadow: none; }
body.nl .btn:active { transform: scale(.975); }
body.nl .btn-outline:hover { background: var(--rv-primary-light); color: var(--rv-primary-dark); }

/* Legacy footer, on the three pages still carrying it — same
   navy field and hairline rule as `.rv-footer`, so the two
   footer implementations read as one even before they are
   consolidated onto the shared partial. */
body.nl .footer { background: var(--rv-navy); }
body.nl .footer-top { border-bottom: 1px solid var(--nl-navy-rule); }
body.nl .footer-col-label { color: var(--nl-on-navy-2); letter-spacing: .13em; text-transform: uppercase; font-size: .6875rem; }
body.nl .footer-lnk { color: var(--nl-on-navy-2); }
body.nl .footer-lnk:hover { color: #fff; }

/* ═══════════════════════════════════════════════════════════
   ARTICLE PAGE — news/<slug>.html, generated by
   tools/build-news.py from content/news/.

   The register grammar has no long-form reading surface yet:
   news-info.html lists articles, it never opens one. This is
   that surface. It keeps the page-hero opening every interior
   page already uses, then holds one 720px measure for the body
   so the line length stays readable — the same "hold the
   measure on the children, not the container" rule the page
   hero states, for the same reason.

   No card. The body sits directly on paper, separated from the
   hero by the field boundary and from the CTA by one rule.
   ═══════════════════════════════════════════════════════════ */
.nl-post { padding: clamp(38px, 4.6vw, 62px) 0 clamp(46px, 5.4vw, 76px); }
.nl-post-inner > * { max-width: 720px; }

/* The meta line is the listing's, but on the navy field rather
   than on paper, so it takes the field's secondary ink. The
   category tag keeps its own light chip and stays legible. */
.nl-post-meta { margin-top: 18px; }
.nl-page-hero--field .nl-article-meta { color: var(--nl-on-navy-2); }

/* The lead image keeps the listing's frame so an article looks
   like the card it was opened from. */
.nl-post-figure {
  /* No max-width of its own: it inherits the 720px measure from
     `.nl-post-inner > *` so the image shares the body's left and
     right edges. Setting 100% here would win on source order and
     let the figure run the full container width. */
  margin: 0 0 clamp(28px, 3.4vw, 44px);
  border-radius: var(--nl-r);
  overflow: hidden;
  background: var(--nl-stage);
  aspect-ratio: 16 / 9;
}
.nl-post-figure img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── Prose. Rendered Markdown, so every element it can emit
      needs a rule — an unstyled <blockquote> or <table> would
      fall back to the browser's, which is not in this system.

      Type comes off the documented ramp: the reading measure is
      the `lead` step, sub-heads are `title`, tabular text is
      `body`. The one exception is the h2, which needs a step
      between `title` (1.125rem) and `headline` (up to 2.6rem) —
      at the headline size an article's section head would rival
      its own h1. That gap is the only new value here. ── */
/* Shown on an article that has no version in the reader's chosen language.
   Quiet by design: it explains the fallback without competing with the
   article. Hidden by default and revealed by revamp.js. */
.nl-post-langnote {
  margin: 0 0 clamp(20px, 2.4vw, 30px);
  padding: 12px 16px;
  border: 1px solid var(--nl-rule);
  border-radius: var(--nl-r);
  background: var(--nl-stage);
  color: var(--nl-ink-2);
  font-size: .8125rem; font-weight: 400; line-height: 1.55;
}
.nl-post-langnote[hidden] { display: none; }
/* The offer variant is an action rather than an explanation, so it carries
   the link colour and the arrow sits with the text. */
.nl-post-langnote--offer { display: flex; align-items: center; gap: 8px; }
.nl-post-langnote--offer a {
  color: var(--nl-ink-link); font-weight: 500; text-decoration: none;
}
.nl-post-langnote--offer a:hover { text-decoration: underline; }
.nl-post-langnote--offer i { color: var(--nl-ink-link); font-size: .875em; }

.nl-prose {
  font-size: clamp(1.0125rem, 1.28vw, 1.1875rem);
  font-weight: 300; line-height: 1.7; color: var(--nl-ink-2);
}
.nl-prose > * + * { margin-top: 1.1em; }
.nl-prose h2, .nl-prose h3, .nl-prose h4 {
  color: var(--rv-navy); font-weight: 600; letter-spacing: -.02em; line-height: 1.28;
}
.nl-prose h2 { margin-top: 2em; font-size: 1.5rem; padding-top: .9em; border-top: 1px solid var(--nl-rule); }
.nl-prose h3 { margin-top: 1.7em; font-size: 1.125rem; }
.nl-prose h4 { margin-top: 1.5em; font-size: 1.125rem; font-weight: 500; }
.nl-prose h2 + *, .nl-prose h3 + *, .nl-prose h4 + * { margin-top: .6em; }
.nl-prose strong { font-weight: 600; color: var(--rv-navy); }
.nl-prose a { color: var(--nl-ink-link); text-underline-offset: .18em; text-decoration-thickness: 1px; }
.nl-prose ul, .nl-prose ol { padding-left: 1.25em; }
.nl-prose li + li { margin-top: .45em; }
.nl-prose li::marker { color: var(--nl-rule-strong); }
.nl-prose img { max-width: 100%; height: auto; border-radius: var(--nl-r); }
.nl-prose hr { border: 0; border-top: 1px solid var(--nl-rule); margin: 2em 0; }
.nl-prose blockquote {
  margin-left: 0; padding: 2px 0 2px 20px;
  border-left: 2px solid var(--rv-primary);
  color: var(--rv-navy);
}
/* build-news.py wraps every rendered table in this, so a table
   wider than the measure scrolls inside its own box instead of
   widening the page. */
.nl-prose .nl-scroll-x { overflow-x: auto; }
.nl-prose table { width: 100%; border-collapse: collapse; font-size: 1rem; }
.nl-prose th, .nl-prose td { text-align: left; padding: 10px 12px 10px 0; border-bottom: 1px solid var(--nl-rule); }
.nl-prose th { color: var(--rv-navy); font-weight: 600; }

/* ── Back to the listing. One rule above it, matching the way
      every other register closes against its container. ── */
.nl-post-back { margin-top: clamp(34px, 4vw, 52px); padding-top: 22px; border-top: 1px solid var(--nl-rule); }

/* ═══════════════════════════════════════════════════════════
   NO CLOSING RULE — every ruled register in the system opens
   with a rule and separates its rows with one. The last row's
   rule closed nothing: the section it sits in draws its own
   full-bleed boundary 50-80px below, so the two ran as a pair
   of parallel hairlines at the foot of every list on every
   page. A register is closed by the boundary of the thing that
   contains it, never by a second line of its own.

   Last in the cascade so it wins over each list's own rule
   without every component restating it.
   ═══════════════════════════════════════════════════════════ */
.nl-note:last-child,
.nl-item:last-child,
.nl-integ-row:last-child,
.nl-office:last-child,
.nl-scope:last-child,
.nl-avail-row:last-child,
.nl-region-list > .nl-region:last-child { border-bottom: 0; }
body.nl .footer-contact-item { color: var(--nl-on-navy-2); }
body.nl .footer-contact-item a { color: var(--nl-on-navy-2); }
body.nl .footer-bottom { color: var(--nl-on-navy-2); }

/* ═══════════════════════════════════════════════════════════
   HOME HERO — the light stage. Copy on the ground at left; at
   right a cutout of the talent standing on a disc, with the
   product's own panels composed over her in real DOM. Nothing
   here is baked into the photograph, so every label stays
   crisp at any DPR and can be restyled or translated.

   Geometry: the panels are placed as a percentage of an
   aspect-locked stage, so position AND size scale together.
   The arrangement is rigid — only its scale changes between
   901px and the 1240px cap. The cutout's subject spans its
   full width, so the talent is inset to 74% and the ~13%
   gutter each side is where the panels live. Face zones held
   clear: elder x22-52 / y14-42, carer x58-86 / y8-34.
   ═══════════════════════════════════════════════════════════ */
.nl-hero--stage { padding: 0; background: var(--rv-bg); }

.nl-hero--stage .nl-hero-inner {
  max-width: var(--nl-max);
  min-height: 0;
  margin: 0 auto;
  padding: clamp(24px, 3vw, 48px) var(--nl-gut) clamp(28px, 2.6vw, 38px);
  overflow: visible;
}

/* The copy was an absolutely-positioned white panel over a photograph.
   On the light ground it returns to plain flow: no panel, no scrim. */
.nl-hero--stage .nl-hero-copy {
  position: relative;
  z-index: 1;
  inset: auto;
  width: auto;
  padding: 0;
  background: none;
  opacity: 1;
  border-top: 0;
  box-shadow: none;
  transform: none;
}
.nl-hero--stage .nl-h1 {
  color: var(--rv-navy);
  font-size: clamp(2.9rem, 4.9vw, 4.5rem);
}
.nl-hero--stage .nl-hero-copy .nl-lead { max-width: 46ch; color: var(--nl-ink-2); }

/* ── The stage ──────────────────────────────────────────── */
.nl-hero-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 100 / 104;
}
/* The backdrop shape. A flat vector blob — no photographic detail to
   resample, so transform and scaling are safe. It replaces the old disc
   and hairline ring; its own silhouette is the whole form.

   Geometry: the box carries the artwork's own 883x934 viewBox ratio, so
   the shape is never distorted. It is an <img>, not a background, so the
   ratio is enforced by the file itself rather than by a number here that
   could drift out of sync with it — and `contain` cannot silently mask a
   mismatch as letterboxed dead space. */
.nl-hero-blob {
  position: absolute;
  left: 50%;
  top: -5%;
  z-index: 0;
  width: 92%;
  height: auto;
  transform: translateX(-50%);
  pointer-events: none;
  /* Solid to 50% of its own height, then fades out by the bottom edge.
     Masking the shape rather than fading the whole layer keeps the
     talent and the product panels above it at full strength. */
  -webkit-mask-image: linear-gradient(to bottom, #000 50%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 50%, transparent 100%);
}

.nl-hero-talent {
  position: absolute;
  left: 50%;
  bottom: 0;
  z-index: 1;
  width: 74%;
  height: auto;
  transform: translateX(-50%);
  /* Fades the photograph into the stage floor instead of ending on a hard
     rectangular edge, so it reads as sitting in the scene rather than
     pasted over the blob. */
  -webkit-mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 88%, transparent 100%);
}

/* ── The floating icon marks ─────────────────────────────── */
/* Six round icons ring the figure. Each is artwork on its own pale disc, so
   the disc is the whole mark: no surface, no border, no copy. */
.nl-hero-cards { position: absolute; inset: 0; z-index: 2; }

.nl-hc {
  position: absolute;
  /* Widths are a share of the stage, so the marks hold their proportion to
     the figure at every viewport instead of stepping at breakpoints. The
     intrinsic width/height attributes on each <img> supply the ratio, so
     `height: auto` never needs a per-icon number here. */
  height: auto;
  /* Lifted off the photograph, since the flat disc carries no depth of its
     own the way the panel artwork's baked shadow did. */
  filter: drop-shadow(0 clamp(6px, .8vw, 12px) clamp(14px, 1.6vw, 22px) rgba(32, 79, 166, .26));
  pointer-events: none;
  user-select: none;
}

/* Size and placement, as percentages of the stage. One width for all six —
   they are the same mark at the same weight, so any variation would read as
   an accident rather than a hierarchy. They ring the figure: three down the
   left, three down the right, alternating so no two sit at the same height
   and none lands on a face or on the tablet the carer is holding. */
.nl-hc { width: 15%; }
.nl-hc--coord   { left: 3%;   top: 3%; }
.nl-hc--ai      { right: -3%; top: 52%; }
.nl-hc--family  { right: 2%; top: 1%; }
.nl-hc--trend   { left: -7%;  top: 41%; }
.nl-hc--profile { left: -1%;  bottom: 9%; }
/* Sits on the stage floor, clear of the tablet: any higher and it crosses
   her hands. */
.nl-hc--plans   { right: 1%;  bottom: 6%; }

/* ── Two marks, drawn rather than photographed ──────────── */
.nl-hero-spark {
  position: absolute;
  right: 1%;
  top: 0;
  width: clamp(17px, 1.8vw, 24px);
  height: auto;
  fill: var(--rv-primary);
}
.nl-hero-swirl {
  position: absolute;
  left: 2%;
  bottom: 9%;
  width: clamp(30px, 3.2vw, 44px);
  height: auto;
  stroke: color-mix(in srgb, var(--rv-primary) 52%, #fff);
  stroke-width: 2.5;
}

/* ── Desktop: copy beside the stage ─────────────────────── */
@media (min-width: 901px) {
  .nl-hero--stage .nl-hero-inner {
    display: grid;
    grid-template-columns: minmax(0, 43fr) minmax(0, 57fr);
    align-items: center;
    gap: clamp(20px, 2.6vw, 44px);
  }
  .nl-hero--stage .nl-hero-copy { align-self: center; max-width: 560px; }
  .nl-hero--stage .nl-hero-media { position: relative; inset: auto; min-width: 0; }
}
@media (min-width: 1240px) {
  .nl-hero--stage .nl-hero-inner { grid-template-columns: minmax(0, 41fr) minmax(0, 59fr); }
}

/* ── Stacked: the stage above the copy ──────────────────── */
@media (max-width: 900px) {
  .nl-hero--stage .nl-hero-media {
    position: relative;
    inset: auto;
    width: min(100%, 440px);
    margin: 0 auto clamp(14px, 3vw, 24px);
  }
  /* All six marks stay, just pulled in tighter and sized up a step so they
     hold up on a 440px stage without spilling off it. */
  .nl-hero-cards .nl-hc { width: 16%; }
  .nl-hc--coord   { left: 4%;   top: 4%; }
  .nl-hc--family  { right: 3%;  top: 2%; }
  .nl-hc--ai      { right: -1%; top: 50%; }
  .nl-hc--trend   { left: -3%;  top: 42%; }
  .nl-hc--profile { left: 2%;   bottom: 8%; }
  .nl-hc--plans   { right: 3%;  bottom: 6%; }
  .nl-hero-spark,
  .nl-hero-swirl { display: none; }
}

/* Under ~430px the stage shrinks further still, so the marks are pulled in
   again and sized up once more to stay legible — all six still ring the
   figure, just closer to the frame. */
@media (max-width: 430px) {
  .nl-hero-cards .nl-hc { width: 20%; }
  .nl-hc--coord   { left: 2%;   top: 2%; }
  .nl-hc--family  { right: 1%;  top: 0%; }
  .nl-hc--ai      { right: -2%; top: 48%; }
  .nl-hc--trend   { left: -4%;  top: 44%; }
  .nl-hc--profile { left: 0%;   bottom: 6%; }
  .nl-hc--plans   { right: 0%;  bottom: 2%; }
}
@media (max-width: 720px) {
  .nl-hero--stage .nl-h1 { font-size: clamp(2.5rem, 11vw, 3.1rem); }
  /* The three-line break is art-directed for the wide desktop column; at
     phone widths that column is much narrower, so the same breaks leave
     ragged, uneven lines. Let the browser wrap naturally instead. */
  .nl-hero--stage .nl-h1 br { display: none; }
}
