/* ============================================================================
   norman.css — Norman Event Productions
   ============================================================================

   THE WHOLE SITE'S CSS IS IN THIS ONE FILE, and it is linked rather than
   inlined: fourteen pages people navigate between get one cached stylesheet for
   the visit instead of the same bytes re-sent with every page. Everything is
   prefixed `nm-` on a `body class="nm"`, plus one page modifier (`nm--home`,
   `nm--dest`, ...) for per-page deltas.

   SIZING. Norman's comps arrived as browser screenshots at assorted widths
   (1068px, 1486px, 1489px),
   so there is no one comp width to compute a `vw` against, and a fourteen-page
   site with real content needs to reflow rather than scale. So: a max-width
   container, `rem`/`px` type on a `clamp()` scale, and fluid gaps. Ratios were
   read off the comps and converted; the comp measurement is in a trailing
   comment where the number is not obvious.

   SECTIONS
      1. Tokens
      2. Base
      3. Layout primitives — container, section rhythm, eyebrow, headings
      4. Buttons
      5. Header
      6. Footer
      7. Assurance bar — the white band of guarantees
      8. Client logo strip
      9. Hero — home (video)
     10. Stats band
     11. Capability carousel — "Everything your event needs under one roof"
     12. Why-choose-us — checklist beside a photograph
     13. Closing CTA band
     14. Hero — inner pages (framed art beside the headline)
     15. Statement block — centred mark, headline, pull quote
     16. Numbered service grid
     17. Location list
     18. Advantage cards — icon row
     19. Split statement — heading left, copy and pull quote right
     20. Four-up media strip
     21. Process steps
     22. Filter pills
     23. Project cards
     24. Spec blocks — kit specifications beside a photo mosaic
     25. Format profile — a labelled breakdown beside a photograph
     26. Kit categories — the inventory page's four-column spec columns
     27. Photo band — a full-bleed image between sections
     28. Enquiry form
     29. Direct-contact rows
     30. Case study
     31. Values grid — the numbered grid, four across with rules
     32. Capability lists — three columns of ticked items
     33. Team cards
     99. Responsive
     34. Landing pages — the three "-2" quote pages, with their own breakpoints
         (after 99 because it is self-contained)
   ========================================================================= */


/* ──────────────────────────────────────────────
   1. Tokens
   ──────────────────────────────────────────────
   The gradient is the brand. Every accent on every page is some crop of the
   same blue-to-purple ramp — the stat numbers, the checkmarks, the section
   numerals, the icon strokes, the map pins — so it lives here once as both a
   ready-made gradient and its two stops, because some of those uses need the
   stops separately (an SVG stroke cannot take a CSS gradient).

   THE SECOND COPY IS IN grad_defs() IN templates/_icons.html, and nothing keeps
   the two in step automatically: a paint server cannot reference a custom
   property. Both hold #14b3e3 to #8a2be2 top-to-bottom. Change one, change the
   other. */
.nm {
  --nm-bg: #000;
  --nm-bg-raise: #0b0b0d;          /* cards that need to lift off pure black */
  --nm-pill: #191919;              /* header nav pill, sampled */

  --nm-ink: #fff;
  --nm-ink-dim: #c6c9d1;           /* body copy on black */
  --nm-ink-dimmer: #8d919b;        /* captions, meta, footer legal */

  /* ACCENTS/Gradient, read off the Figma file rather than sampled off a flat
     comp — and it is a published token there, carried identically by the
     desktop stat numeral (node 296:132) and the mobile one (321:2822), which
     is what makes it safe to keep as one site-wide value.

     BOTH ENDS AND THE DIRECTION CHANGED. The stops were sampled off a JPEG of
     the "500+" numeral and came out #6555e2/#299ae2, about 12 degrees of hue
     off the real ramp at both ends; the angle was read as a diagonal and is
     actually a straight vertical, blue at the cap and purple at the baseline.
     On a 96px numeral that reads immediately. On the checkmarks and icon
     strokes that share this token it barely does — which is why it was wrong
     for so long, and why correcting it here is safe for them. */
  --nm-purple: #8a2be2;
  --nm-blue: #14b3e3;
  --nm-grad: linear-gradient(180deg, var(--nm-blue) 0%, var(--nm-purple) 100%);

  --nm-line: rgba(255, 255, 255, .14);
  --nm-line-soft: rgba(255, 255, 255, .08);
  --nm-veil: rgba(255, 255, 255, .07);   /* secondary button, translucent wells */

  --nm-font-display: "Anton", "Arial Narrow", Haettenschweiler, sans-serif;
  --nm-font-cond: "Oswald", "Arial Narrow", sans-serif;
  --nm-font-body: "Outfit", "Segoe UI", system-ui, -apple-system, sans-serif;

  /* THE SHELL SCALES WITH THE VIEWPORT; THE CAP IS A BACKSTOP, NOT THE DESIGN.
     Measured off the comps rather than chosen: the side inset was taken from
     fourteen full-width content bands across comps from 1068px to 1486px wide,
     and the inset RATIO is flat — correlation between comp width and inset
     ratio is -0.01. A fixed max-width container would have shown that ratio
     rising with comp width. It does not, so the design is a percentage gutter
     of about 12% (text bands) to 8.5% (the gallery's card edges, which are the
     more reliable read because a card reaches the container bound and a
     sentence stops where it stops). Content therefore sits at roughly 85% of
     the viewport at every width the comps were drawn at.

     An earlier 1400px cap with a 72px gutter reproduced that at 1440 (88%) and
     broke everything above it: 1256px of content at 1920 is 65% of the screen,
     and 49% at 2560, which is the wide empty margin this replaced.

     THE FIX WAS THE TWO LIMITS, NOT THE PROPORTION. The gutter's 4.5vw term
     already reproduced the comps at every width where the cap did not bite —
     1440 sat at 88% of viewport, right on the measurement. What broke wide
     screens was the 1400px shell cap and the 72px ceiling on the gutter:
     together they froze content at 1256px, so 1920 showed 65% content and 2560
     showed 49%, which is the empty margin this replaced.

     Two passes got it here. Lifting those limits first put content at 91% of
     the viewport, which fixed the empty margins but left the content sitting
     close to the screen edges; the gutter then went from 4.5vw to 6.4vw, which
     lands content at a flat 87% and back inside the 85-88% the comps measure.
     So the ratio is now the comps' own, and only the two limits differ from the
     first build.

     Net against the original 1400px-capped build: 1920 gains 418px of content
     and 2560 gains 604, while 1440 is within 15px of where it started.

     The 2160px cap stops content near 1880px, which is where a full-bleed Anton
     headline and a four-column kit grid stop being comfortable; past it the
     margins grow again on purpose. No comp is wider than 1486px, so everything
     above that is a judgement rather than a measurement. */
  --nm-shell: 2160px;              /* outer box, padding included */
  --nm-gutter: clamp(22px, 6.4vw, 150px);

  /* Vertical rhythm. This is the padding on ONE side, so adjacent sections put
     twice it between them — at 118px that was 236px of black at 1440, about
     double the gap the comps show, which is what brought it down to 92. The
     clamp compresses it hard on phones, where fourteen full-height sections is
     otherwise a lot of scrolling. */
  --nm-section-y: clamp(48px, 6.4vw, 92px);

  /* THE HEADER'S HEIGHT IS ONE NUMBER, NOT THREE COPIES OF A CLAMP.
     The badge is the tallest thing in the bar and both heroes are pulled up
     under the sticky header by exactly the bar's height, so the logo's size and
     the two heroes' negative margin have to agree to the pixel or the video
     starts part-way down the screen. That agreement used to be three separate
     `clamp(58px, 4.6vw, 78px) + 28px` expressions in three sections. It is now
     one token the other three read, which is what lets section 99 shrink the
     phone header by editing a single line. The 28px is the bar's own padding,
     14px top and bottom, set on .nm-header__inner. */
  --nm-logo: clamp(58px, 4.6vw, 78px);
  --nm-header-h: calc(var(--nm-logo) + 28px);
}


/* ──────────────────────────────────────────────
   2. Base
   ────────────────────────────────────────────── */
*,
*::before,
*::after { box-sizing: border-box; }

.nm {
  margin: 0;
  background: var(--nm-bg);
  color: var(--nm-ink);
  font-family: var(--nm-font-body);
  font-size: 16px;
  line-height: 1.66;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* The footer wordmark and several section backgrounds are wider than the
     viewport by design; without this a horizontal scrollbar appears on every
     page. Anything that needs to overflow does it inside its own clipped box. */
  overflow-x: hidden;
}

/* EVERY RESET BELOW IS WRAPPED IN :where(), and that is not cosmetic.
   Written the obvious way — `.nm a { color: inherit }` — the reset is a class
   plus a type selector, specificity (0,1,1), which BEATS every single-class
   component rule at (0,1,0). The result is a stylesheet where
   `.nm-btn--solid { color: #000 }` silently loses to the link reset and the
   button renders white-on-white, where `.nm-stats__grid { margin-top: 54px }`
   silently loses to the list reset, and where nothing in devtools looks wrong
   because the winning rule is the one nobody thought to check.
   That happened here, to three components, before this was changed.

   :where() contributes zero specificity, so each of these lands at (0,0,1) —
   the type selector alone — and any component class overrides it without
   needing !important or a doubled-up ancestor. Do not "simplify" these back
   into plain descendant selectors. */
:where(.nm) img,
:where(.nm) svg,
:where(.nm) video { display: block; max-width: 100%; }

:where(.nm) a { color: inherit; text-decoration: none; }

:where(.nm) p { margin: 0 0 1em; }
:where(.nm) p:last-child { margin-bottom: 0; }

:where(.nm) :is(h1, h2, h3, h4) { margin: 0; font-weight: 400; }

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

/* [hidden] HAS TO WIN, and in this stylesheet it does not by default.
   The UA rule is `[hidden] { display: none }` at specificity (0,1,0), which any
   component that sets `display` beats or ties — and several here do:
   `.nm-proj { display: flex }`, `.nm-steps { display: grid }`, the pending
   tiles. The gallery filter and the mobile nav both hide elements by setting
   the `hidden` property, so without this a filtered-out project card stays
   visible and the filter looks broken while the DOM says it worked.
   !important because the whole point is to outrank every display declaration
   in the file, including ones added later. */
[hidden] { display: none !important; }

/* Focus is visible and gradient-free: a solid blue ring reads on black at any
   size, where the brand gradient would wash out at 2px. */
.nm :focus-visible {
  outline: 2px solid var(--nm-blue);
  outline-offset: 3px;
}

/* Screen-reader-only, for the labels the comps draw as placeholder text inside
   the field rather than above it. */
.nm-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.nm-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 14px 20px;
  background: #fff;
  color: #000;
  font-family: var(--nm-font-cond);
  text-transform: uppercase;
  letter-spacing: .08em;
}
.nm-skip:focus { left: 12px; top: 12px; }


/* ──────────────────────────────────────────────
   3. Layout primitives
   ──────────────────────────────────────────────
   Four things repeat on all fourteen pages: the content shell, the section's
   vertical padding, the little rule-and-label eyebrow above a heading, and the
   heading itself. They are separate classes rather than one `.nm-section`
   because plenty of sections need the padding without the shell (anything with
   a full-bleed background) or the shell without the padding (a strip inside an
   already-padded section). */
.nm-shell {
  width: 100%;
  max-width: var(--nm-shell);
  margin-inline: auto;
  padding-inline: var(--nm-gutter);
}

.nm-section { padding-block: var(--nm-section-y); }

/* Eyebrow — "| LIVE PRODUCTION SPECIALISTS". The bar is a border rather than a
   pseudo-element so the label can wrap without the bar detaching from it. */
.nm-eyebrow {
  display: inline-block;
  padding-left: 11px;
  border-left: 2px solid #fff;
  font-family: var(--nm-font-cond);
  /* Label/C-md: 18px at the 1920 frame, on 10% tracking. Both were read off a
     flat comp as 12px/.19em; the size is the token's and is wrong on every
     section of every page, the tracking is right only here — the CTA band
     (node 296:227) is the one eyebrow drawn at 3.42px, which is the .19em this
     had. Hence the override on .nm-cta below rather than a second value. */
  font-size: clamp(12px, .94vw, 18px);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--nm-ink);
}
.nm-cta .nm-eyebrow { letter-spacing: .19em; }   /* node 296:227: 3.42px at 18 */

/* The display face. Anton is a single-weight ultra-condensed grotesque and the
   comps use it for every large all-caps headline. Two properties matter as much
   as the family: line-height below 1 (the comps set the two lines of the hero
   headline almost touching) and a slight negative letter-spacing, because Anton
   ships loose for its width. */
.nm-h1,
.nm-h2,
.nm-h3 {
  font-family: var(--nm-font-display);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: -.005em;
  line-height: .94;
  /* A LAST RESORT THAT ONLY FIRES WHEN ONE WORD IS WIDER THAN THE COLUMN.
     Anton at headline size puts a 13-letter word past 290px, and the narrowest
     phone column on this site is 276 — so "[ORGANISATION NAME] Annual Awards
     Gala" on the our-works page pushed the whole document 5px wide at 320,
     which is a horizontal scrollbar on every section of the page rather than a
     ragged line in one heading. This changes nothing at any width where the
     words fit, which is every heading on every page above 320. */
  overflow-wrap: break-word;
}

/* THE SIZES ARE MEASURED, NOT PICKED. Each comp headline was measured for ink
   width and cap height, and Anton was checked against those ratios rather than
   eyeballed:

     headline                      comp w:cap    Anton w:cap
     "DELIVER MORE THAN A TICKET"      11.36         12.42
     "DESTINATION EVENTS THAT"          9.81         10.98
     "FLAWLESSLY EXECUTED."            10.00          9.93

   ANTON RUNS ABOUT 7% WIDE against the comps' face, which is an ultra-compressed
   display type (the proportions are narrower than anything on Google Fonts at
   this stroke weight — League Gothic matches the width but is nowhere near heavy
   enough). That 7% is why the two-column heroes give their text column a little
   more room than the comps do: at the comp's exact column width, Anton's second
   line wraps and a two-line headline becomes three. If the real face is ever
   licensed, change --nm-font-display and the columns can come back in.

   The homepage h1 is measurably LARGER than the inner pages': cap height is
   4.49% of viewport width in homepage s (1) and 3.91% in destination s (1). So
   .nm-h1 carries the homepage size and .nm-phero__title (section 14) steps it
   down — the difference is in the comps, not a rounding artefact. */
.nm-h1 { font-size: clamp(40px, 5.1vw, 94px); }   /* comp: cap 4.49% of vw */
/* Header/H3: Anton 64 / lineHeight 1 / letterSpacing -2%. The leading and the
   tracking are the token's as much as the size is — .nm-h1/.nm-h2/.nm-h3 share
   .94 and -.005em above, read off the flat comps, and every H3-token headline
   in the file measures 1 and -2%: the stats band, the carousel, why-choose-us,
   this statement block and the services header. */
.nm-h2 {
  font-size: clamp(29px, 3.33vw, 64px);
  line-height: 1;
  letter-spacing: -.02em;
}
.nm-h3 { font-size: clamp(22px, 1.9vw, 34px); }

/* Sub-headline under a hero h1 — "FULL-SERVICE EVENT PRODUCTION & MANAGEMENT".
   Condensed and light, so it reads as a different voice from the Anton line
   above it rather than a smaller copy of it. */
.nm-kicker {
  font-family: var(--nm-font-cond);
  font-size: clamp(16px, 1.5vw, 27px);
  font-weight: 300;
  line-height: 1.25;
  letter-spacing: .005em;
  text-transform: uppercase;
  color: #d9dbe1;
}

/* Card and list-item headings: Oswald, not Anton. The comps step down to a
   medium-weight condensed face for anything under about 34px — see the stat
   labels and the checklist headings — because Anton's counters close up at
   small sizes and stop being readable. */
.nm-label {
  font-family: var(--nm-font-cond);
  font-size: clamp(15px, 1.15vw, 20px);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: .035em;
  text-transform: uppercase;
  color: var(--nm-ink);
}

.nm-body { color: var(--nm-ink-dim); }
.nm-body--sm { font-size: 15px; }

/* A centred section header (eyebrow + h2), used by roughly half the sections. */
.nm-head { text-align: center; }
.nm-head .nm-eyebrow { margin-bottom: 14px; }
.nm-head--left { text-align: left; }

/* The optional lead paragraph under a section heading. Narrow measure and
   centred with the heading, so a centred header block stays one shape. */
.nm-head__lede {
  max-width: 76ch;
  margin: clamp(12px, 1.4vw, 20px) auto 0;
  color: var(--nm-ink-dim);
}
.nm-head--left .nm-head__lede { margin-inline: 0; }

/* Ambient glow. Several sections sit on a supplied photograph of coloured haze;
   several others in the comps have the same haze with no asset shipped for it.
   This reproduces it in CSS so those sections do not fall back to flat black —
   two wide, very soft radial gradients, one purple and one teal, at the
   opacities the supplied art measures. */
.nm-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(58% 82% at 12% 52%, rgba(101, 85, 226, .30) 0%, rgba(101, 85, 226, 0) 68%),
    radial-gradient(52% 78% at 88% 46%, rgba(41, 154, 226, .22) 0%, rgba(41, 154, 226, 0) 70%);
}

/* The circular wordmark, blown up and half off the left edge at low opacity.
   The comps use it as a watermark behind two sections. It is decorative, so it
   is a background rather than an <img>. */
.nm-watermark {
  position: absolute;
  top: 50%;
  left: 0;
  z-index: 0;
  /* 300 square, half of it off the left edge — nodes 296:501 (this section) and
     296:154 (the homepage stats band) draw it identically at (-150, centred),
     which is exactly a -50% translate on a 300px box. It was 340 on a -46%
     translate, sampled off a flat comp. */
  width: clamp(180px, 15.625vw, 300px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  opacity: .07;
  background: no-repeat center / contain;
  pointer-events: none;
}


/* ──────────────────────────────────────────────
   4. Buttons
   ──────────────────────────────────────────────
   Two variants and one shape. Square corners, condensed uppercase label, and a
   solid triangle after the label on the primary. The triangle is drawn with
   borders rather than set as a glyph because the glyph's size and baseline vary
   between the fallback fonts, and it is decorative — a border triangle is
   invisible to a screen reader without needing aria-hidden. */
.nm-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 15px 26px;
  border: 1px solid transparent;
  font-family: var(--nm-font-cond);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .075em;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}

.nm-btn::after {
  content: "";
  width: 0; height: 0;
  border-style: solid;
  border-width: 4px 0 4px 6px;
  border-color: transparent transparent transparent currentColor;
}
.nm-btn--bare::after { display: none; }

.nm-btn--solid {
  background: #fff;
  color: #000;
}
.nm-btn--solid:hover { background: #dcdee3; }

.nm-btn--ghost {
  background: var(--nm-veil);
  color: #fff;
  border-color: rgba(255, 255, 255, .16);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.nm-btn--ghost:hover { background: rgba(255, 255, 255, .14); }

/* The gradient button. Not in the first two comps, but the brand ramp is the
   obvious primary action for the form pages, and defining it here keeps it from
   being reinvented per page. */
.nm-btn--grad {
  background: var(--nm-grad);
  color: #fff;
}
.nm-btn--grad:hover { filter: brightness(1.12); }

.nm-btn--wide { width: 100%; }

/* A pair of buttons under a block of copy, wrapping on narrow screens. */
.nm-btns {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}


/* ──────────────────────────────────────────────
   5. Header
   ──────────────────────────────────────────────
   The comps draw the header transparent over the hero: badge logo at the left,
   a dark rounded nav pill centred, a white CONTACT US button at the right. It
   is `sticky` rather than `absolute` because these are fourteen long content
   pages rather than one funnel — losing the navigation for four screenfuls of
   inventory tables would be worse than the comp's clean hero.

   `.nm-header--solid` is added by norman.js past 40px of scroll, so the pill
   never has to be readable against arbitrary page content. Without the script
   the header simply stays transparent and everything still works: the pill has
   its own background, which is the whole reason the comp puts the nav inside
   one. */
.nm-header {
  position: sticky;
  top: 0;
  z-index: 60;
  transition: background-color .22s ease, border-color .22s ease;
  border-bottom: 1px solid transparent;
}

.nm-header--solid {
  background: rgba(0, 0, 0, .88);
  border-bottom-color: var(--nm-line-soft);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

/* A three-column grid, not a flex row.
   ────────────────────────────────────
   The comp centres the nav pill on the PAGE's centre line — measured off comp
   s (1), the pill spans 332-745 of 1068, centre 538.5 against a page centre of
   534. A flex row with `margin-inline: auto` on the nav cannot do that: auto
   margins centre a child in the space LEFT OVER by its siblings, and the badge
   (78px) and the CONTACT US button (~141px) are different widths, so the pill
   lands off-centre by half their difference. Equal `1fr` side columns centre
   the middle one against the container regardless of what the sides contain. */
.nm-header__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 20px;
  padding-block: 14px;
}

/* The nav's wrapper is the grid's middle column. The auto-margin trick lives
   here rather than on the <ul> because the <ul> is not the grid item — that
   was the other half of the same bug. */
.nm-header__nav { display: flex; justify-content: center; }

/* Third column, pushed to its right edge. */
.nm-header__cta { justify-self: end; }

/* The badge is the only element in the header allowed to grow past the bar's
   own height — in the comp it overhangs the hero slightly. */
.nm-header__brand { justify-self: start; }
.nm-header__logo {
  width: var(--nm-logo);
  height: auto;
}

/* The pill itself. Its centring is the grid's job (see .nm-header__inner
   above), not this rule's — an auto margin here would be centring the <ul>
   inside its own wrapper, which does nothing. */
.nm-nav {
  display: flex;
  align-items: center;
  background: var(--nm-pill);
  border-radius: 7px;
}

.nm-nav__link {
  display: block;
  padding: 15px clamp(14px, 1.5vw, 26px);
  font-family: var(--nm-font-cond);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: .055em;
  text-transform: uppercase;
  color: #b6b8be;
  transition: color .16s ease;
}
.nm-nav__link:hover { color: #fff; }

/* Hairline dividers between items, drawn on the <li> so the first one has none
   and the padding above stays symmetrical. */
.nm-nav__item + .nm-nav__item { border-left: 1px solid rgba(255, 255, 255, .13); }

/* The page you are on. `aria-current="page"` in the markup is what makes this
   announce, so the styling hangs off the attribute rather than a class — one
   less thing per page template to keep in sync. */
.nm-nav__link[aria-current="page"] { color: #fff; }

/* The SERVICES dropdown.
   ──────────────────────
   The comp draws five flat links, but nine of the fourteen pages are services,
   and a header that cannot reach them is not navigation. So SERVICES keeps its
   comped appearance and gains a panel.

   IT OPENS WITH NO JAVASCRIPT. `:hover` covers the mouse and `:focus-within`
   covers the keyboard, which between them are every non-touch visitor — so the
   panel is not one of the three things norman.js does, and it cannot break by
   the script failing to load. Touch has no hover: there, tapping SERVICES
   simply follows its href to the Event Types hub, which lists the same nine
   pages as real content. That is why SERVICES is an <a> to a real page and not
   a <button> — the fallback has to go somewhere.

   Below 1180px the panel is not a panel at all; see section 99, where it
   flattens into an indented sub-list inside the open burger menu and the hover
   rules stop applying. */
.nm-nav__item--has-menu { position: relative; }

/* The caret. Rotates when the panel is open, which is the only affordance a
   mouse user gets that the item has more behind it than a link. */
.nm-nav__caret {
  display: inline-block;
  width: 0; height: 0;
  margin-left: 7px;
  vertical-align: 2px;
  border-style: solid;
  border-width: 4px 3.5px 0;
  border-color: currentColor transparent transparent;
  transition: transform .16s ease;
}
.nm-nav__item--has-menu:hover .nm-nav__caret,
.nm-nav__item--has-menu:focus-within .nm-nav__caret { transform: rotate(180deg); }

.nm-menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 5;
  min-width: 232px;
  padding: 7px 0;
  border: 1px solid var(--nm-line);
  border-radius: 7px;
  background: #101012;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .55);
  /* Closed with visibility + opacity rather than `display: none` so it can
     fade, and so `visibility` can be transitioned alongside it — a display
     change is not animatable and the panel would snap.

     HOW THE KEYBOARD GETS IN, since it is not obvious and it is easy to
     "fix" this into something broken: a `visibility: hidden` subtree is NOT
     focusable, exactly like a `display: none` one, so nothing can Tab
     straight into the closed panel. It does not need to. Focus lands on the
     SERVICES link first, which is a sibling of the panel inside the same
     <li>, so :focus-within on that <li> becomes true, the panel becomes
     visible, and only then are its links focusable — the next Tab lands on
     the first one and focus stays within the <li>, holding it open. Verified
     with JavaScript disabled: Tab reaches Services, then walks Event Types,
     Destination Management, Corporate Events, and on through all nine.

     This is why the panel must stay a SIBLING of the link inside one <li>.
     Move it out of that element and :focus-within has nothing to match, the
     panel never opens for a keyboard, and the nine service links become
     unreachable without a pointer. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(-5px);
  transition: opacity .15s ease, transform .15s ease, visibility .15s;
}
.nm-nav__item--has-menu:hover .nm-menu,
.nm-nav__item--has-menu:focus-within .nm-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.nm-menu__link {
  display: block;
  padding: 9px 18px;
  font-family: var(--nm-font-cond);
  font-size: 13px;
  font-weight: 300;
  letter-spacing: .055em;
  text-transform: uppercase;
  color: #c2c4ca;
  white-space: nowrap;
  transition: background-color .14s ease, color .14s ease;
}
.nm-menu__link:hover,
.nm-menu__link[aria-current="page"] {
  background: rgba(255, 255, 255, .07);
  color: #fff;
}

/* One rule between the event-family pages and the technical-service pages. It
   is a border on the <li> rather than an <hr>, so the groups stay one list for
   a screen reader instead of becoming two. */
.nm-menu__item--divide {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--nm-line-soft);
}

/* Mobile: the pill cannot hold five items under about 900px, so it becomes a
   panel toggled by a button. The toggle is hidden on desktop and the panel is
   the same <ul>, restyled — one nav in the markup, not two. */
.nm-burger {
  display: none;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  padding: 0;
  background: var(--nm-pill);
  border: 0;
  border-radius: 7px;
  cursor: pointer;
}
.nm-burger__bar,
.nm-burger__bar::before,
.nm-burger__bar::after {
  display: block;
  width: 19px;
  height: 2px;
  background: #fff;
  content: "";
}
.nm-burger__bar {
  position: relative;
  margin-inline: auto;
}
.nm-burger__bar::before { position: absolute; top: -6px; }
.nm-burger__bar::after { position: absolute; top: 6px; }


/* ──────────────────────────────────────────────
   6. Footer
   ──────────────────────────────────────────────
   Three columns (badge / quick links / contacts), a legal row under a hairline,
   then the giant NORMAN wordmark bleeding off the bottom of the page. The
   wordmark is supplied as a 7116px PNG; it is placed as an <img> with a srcset
   rather than a background so the browser can pick a width, and it is cropped
   by the footer's own overflow so the letters run off the bottom edge exactly as
   the comp shows. */
.nm-foot {
  position: relative;
  overflow: hidden;
  padding-top: clamp(40px, 4.4vw, 66px);
}

 /* The third column is `auto`, not a fraction. In comp s (7) the contacts
    column is only about 100px wide and hugs the right edge — the heading's left
    edge sits almost exactly under the values' left edge. Given it a fraction of
    the row instead, the heading stays pinned to the left of a wide column while
    the right-aligned values drift away from it, leaving a gap the comp does not
    have. */
.nm-foot__cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto;
  gap: clamp(28px, 3vw, 56px);
  align-items: start;
}
.nm-foot__contacts-col { justify-self: end; }

.nm-foot__logo { width: clamp(64px, 5.6vw, 92px); height: auto; }

/* Column heading — Anton at small size, with a short rule beneath it. The rule
   is a pseudo-element so it stays the width of the label's box rather than the
   text, matching the comp. */
.nm-foot__title {
  font-family: var(--nm-font-display);
  font-size: 17px;
  letter-spacing: .01em;
  text-transform: uppercase;
}
.nm-foot__title::after {
  display: block;
  width: 26px;
  height: 2px;
  margin-top: 7px;
  background: #fff;
  content: "";
}

.nm-foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin-top: 18px;
}
.nm-foot__link {
  font-family: var(--nm-font-cond);
  font-size: 14px;
  font-weight: 300;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #cdcfd5;
}
.nm-foot__link:hover,
.nm-foot__link[aria-current="page"] { color: #fff; }

.nm-foot__contacts {
  margin-top: 18px;
  text-align: right;
}
.nm-foot__contact {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  color: #cdcfd5;
}
.nm-foot__contact:hover { color: #fff; }

.nm-foot__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  justify-content: space-between;
  margin-top: clamp(26px, 2.6vw, 40px);
  font-family: var(--nm-font-cond);
  font-size: 13px;
  font-weight: 300;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #b2b5bc;
}
.nm-foot__legal-links { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.nm-foot__legal a:hover { color: #fff; }

/* The wordmark. The negative bottom margin is what crops it, against .nm-foot's
   overflow: hidden.

   MIND THE UNIT. A percentage margin resolves against the containing block's
   WIDTH, never its height — so -6.2% is 6.2% of the footer's width, which at
   1440 is 89px, or 29% of the image's own 304px height. That is the intent (the
   comp cuts roughly the bottom quarter of the letterforms off the page) but the
   number is not a percentage of the image, and changing the image's aspect ratio
   changes how much it crops.

   It sits INSIDE the shell rather than bleeding to the viewport edges: comp
   s (7) starts the N about 50px in and ends the final N about 50px from the
   right, which is the page gutter, not a full bleed. */
.nm-foot__wordmark {
  display: block;
  width: 100%;
  height: auto;
  margin-top: clamp(18px, 2.4vw, 40px);
  margin-bottom: -6.2%;                 /* comp: bottom ~26% of the glyphs cropped */
  pointer-events: none;
}


/* ──────────────────────────────────────────────
   7. Assurance bar
   ──────────────────────────────────────────────
   The band of five guarantees. Five items and four hairline dividers spread
   across the full width, on white.

   IT USED TO SCROLL, AND THAT WAS A MISREADING. The flat comp showed the row
   bleeding to both edges with the first and last item clipped, which reads as
   the giveaway of a marquee — so this was built as one, with the list
   duplicated in the markup, a track translated by half its width, a hover
   pause, an aria-hidden twin and a prefers-reduced-motion fallback. Node
   296:242 has it static: one row, justify-between, nothing clipped and nothing
   repeated. All of that apparatus is gone rather than turned off, and the
   section is no longer called a marquee.

   WHITE BACKGROUND, ON REQUEST (2026-09-08), where the comps draw it black —
   and the node agrees. It is the only light band on the site, which is why the
   inversion has to be complete rather than just a background swap: the item
   text inherited white from .nm, the five icons are fill="currentColor" and so
   follow it, and the dividers were white at low alpha. Setting `color` on the
   container is what carries the icons across, which is the reason they were
   built with currentColor rather than a hardcoded white. */
.nm-assure {
  padding-block: clamp(14px, 1.25vw, 24px);
  /* The node insets this 70px at 1920 — narrower than --nm-gutter, so the row
     reaches further out than the page's other content. That is deliberate in
     the design: it is a full-width band, not a column of copy. */
  padding-inline: clamp(20px, 3.65vw, 70px);
  background: #fff;
  /* Carries the five icons too — they are fill="currentColor". */
  color: #000;
}

.nm-assure__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nm-assure__item {
  display: flex;
  align-items: center;
  gap: clamp(6px, .52vw, 10px);
  font-family: var(--nm-font-display);
  font-size: clamp(13px, 1.25vw, 24px);   /* Header/H7: Anton 24 / 1 / 0 */
  line-height: 1;
  letter-spacing: 0;
  text-transform: uppercase;
  white-space: nowrap;
}

/* A 1px x 20px rule, not a border on the item: the node centres these in the
   gaps, and with justify-between an element does that on its own where a
   border-left would sit hard against the next item's edge. */
.nm-assure__rule {
  flex: 0 0 auto;
  width: 1px;
  height: 20px;
  background: rgba(12, 18, 28, .2);
}

.nm-assure__icon {
  flex: 0 0 auto;
  width: clamp(14px, 1.25vw, 24px);
  height: auto;
}

/* Five claims in Anton will not sit on one line much under a tablet, so the
   row wraps and the dividers — which only make sense between items on a single
   line — come out. Wrapping is the reading that keeps all five legible rather
   than shrinking them to fit. Below 640 the phone frames take over; see the
   assurance rules in section 99. */
@media (max-width: 1023px) {
  .nm-assure__row {
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px clamp(14px, 2.6vw, 28px);
  }
  .nm-assure__rule { display: none; }
}


/* ──────────────────────────────────────────────
   8. Client logo strip
   ──────────────────────────────────────────────
   "AS TRUSTED BY INDUSTRY LEADERS ACROSS THE COUNTRY" over six white logos.
   The logos are placeholders (logoipsum) in the supplied art.

   MEASURED OFF THE FIGMA NODE, NOT THE FLAT COMP (file PnfMBS9M4DH6zyg9TtkVoC,
   section 296:116 / client 213:50, drawn on a 1920 frame):

       label            16px, 60% white, uppercase, 1.5 leading
       label -> row     12px
       row height       40px, logos vertically centred in it
       logo sizes       their own — 140x40, 140x31, 128x28, 149x39,
                        120x32, 116x36, which are the six files' intrinsic
                        sizes rendered 1:1
       gap              60.09px, even, including across the loop's seam
       window           1187px wide, centred (19.1% to 80.9% of the frame)
       fade             240.4px inside each end (opaque from 31.6% to 68.4%)
       strip opacity    75%

   THE SIX ARE NOT ONE HEIGHT. This section used to set a single
   `height: clamp(17px, 1.5vw, 26px)` on all of them with `width: auto`, on the
   reasoning that height-sizing lets a replacement of any aspect ratio drop in
   without relayout. It does — but it also throws away the design: it made the
   tallest mark 35% smaller than drawn, and it stretched the short wide ones
   (Logo03 is 128x28; forced to a common height it comes out half again too
   wide) while squashing the tall ones. The marks now render at their own size,
   all six multiplied by one factor, so their designed relationship to each
   other survives.

   --nm-clients-k IS THAT FACTOR, and it is a length equal to 1px on the 1920
   frame — the same trick the phone hero uses. `calc(40 * var(--nm-clients-k))`
   reads "the 40px the design draws". Below about 1056px it holds at 0.55 so
   the marks stop shrinking (the tallest bottoms out at 22px, which is near
   where the old cap had them at every width); above 1920 it holds at 1 so they
   never exceed the drawn size.

   THE ROW SCROLLS, and it is section 7's marquee again rather than a second
   mechanism: the group is repeated in the markup, the track translates by
   exactly one group, and the seam is invisible because what arrives is
   identical to what left. Everything written there about why the animation
   sits on the track and not the items applies here unchanged.

   SIX COPIES, NOT TWO, AND THAT IS THE WHOLE DESIGN OF THIS SECTION.
   Duplicating once and translating -50% — which is what the assurance band
   does and what this did first — is only correct while ONE group is wider than
   the viewport. A marquee shifts by one repeating unit, so what has to span
   the screen is not the track but the track MINUS that unit. Six logos span
   about 520px on a phone and cap at about 980px, so:

       viewport   group   covered after the shift   hole
          390      517             518               ok
          768      563             564              204px
         1440      816             816              624px
         1920      980             980              940px
         3440      980             980             2460px

   Phones were fine, every desktop ran a hole through the strip once a cycle,
   and the seam measured perfectly the whole time — the join was never the
   problem, the coverage was. Six copies put 5 x 980 = 4899px behind the shift,
   which clears a 4K viewport with room; the count and the translate below are
   one decision and have to move together.

   The gap AT THE SEAM also has to equal the gap between logos, or the loop
   shows a wider space once per cycle. Hence padding-right on the group equal
   to its own gap — that makes the repeating unit "six logos plus one gap", and
   copies of it tile with even spacing across every join. The assurance band
   gets the same effect from padding-inline on each item, which is why it needs
   no equivalent rule. */
.nm-clients {
  --nm-clients-k: clamp(.55px, .0521vw, 1px);
  padding-block: clamp(24px, 2.6vw, 32px);
  text-align: center;
}

/* The clip the marquee travels behind, and the soft ends. The mask is what
   makes the logos arrive and leave rather than appear and vanish at a hard
   edge. It sits on this wrapper rather than on .nm-clients so it takes the row
   only: masking the section would fade the ends of the label too.

   THE FADE IS LONG, NOT AN EDGE TREATMENT. The comp ramps each side almost to
   the middle — only the centre few logos are at full strength and everything
   outboard of them is on its way in or out. A 9% edge fade reads as a mask
   over a strip that is otherwise flat; 40% reads as the strip itself being lit
   in the middle, which is what the comp draws.

   It steps back on a phone. 40% a side leaves about 78px of full-strength
   centre at 390px, which is narrower than one logo — the whole row would sit
   in permanent half-fade. The two are one idea at two widths, so the ramp is a
   custom property and the breakpoint only re-points it. */
.nm-clients__viewport {
  /* THE NODE'S WINDOW AND FADE, AS A MASK. The design clips the row to a
     1187px box centred on the 1920 frame and lays a 240.4px black gradient
     inside each end of it. Rather than reproduce that as a fixed-width box —
     which would need its own centring and would stop the loop reaching the
     edges — the row stays full-bleed and the mask does both jobs at once:
     nothing at all outside the box's edges, ramping to full where the
     gradients end. The four stops ARE the node's geometry:

         19.1%  the box's left edge  (366.5 of 1920)
         31.6%  where its gradient ends (606.9 of 1920)
         68.4%  where the right gradient begins
         80.9%  the box's right edge

     A phone has no room for it. 19.1% of 390px is gone before the first mark
     clears the edge, and the full-strength middle would be one logo wide, so
     the two stops pull in and the row keeps a soft edge instead of a window. */
  --nm-clients-fade-out: 19.1%;
  --nm-clients-fade-in: 31.6%;
  overflow: hidden;
  margin-top: clamp(10px, calc(12 * var(--nm-clients-k)), 12px);
  -webkit-mask-image: linear-gradient(90deg,
    transparent 0 var(--nm-clients-fade-out),
    #000 var(--nm-clients-fade-in),
    #000 calc(100% - var(--nm-clients-fade-in)),
    transparent calc(100% - var(--nm-clients-fade-out)) 100%);
  mask-image: linear-gradient(90deg,
    transparent 0 var(--nm-clients-fade-out),
    #000 var(--nm-clients-fade-in),
    #000 calc(100% - var(--nm-clients-fade-in)),
    transparent calc(100% - var(--nm-clients-fade-out)) 100%);
}
@media (max-width: 640px) {
  .nm-clients__viewport { --nm-clients-fade-out: 4%; --nm-clients-fade-in: 20%; }
}

/* The node sets this in DM Sans Bold, which the site does not load and does
   not want to for one line — the three families in section 1 are the whole
   type system. It keeps Oswald and takes the node's size, colour and case,
   which is what actually reads different: 12px near-white was a caption, 16px
   at 60% is the label the design draws. */
.nm-clients__label {
  font-family: var(--nm-font-cond);
  font-size: clamp(12px, calc(16 * var(--nm-clients-k)), 16px);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: .105em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .6);
}

.nm-clients__track {
  display: flex;
  width: max-content;
  animation: nm-clients 42s linear infinite;
}
/* Hover pauses it, as the assurance band does — a logo the visitor is trying
   to read should stop moving. */
.nm-clients:hover .nm-clients__track { animation-play-state: paused; }

.nm-clients__group {
  display: flex;
  /* The node centres each mark in a 40px band rather than sitting them on a
     baseline, which is what keeps a 28px-tall logo optically level with a
     40px one. */
  align-items: center;
  flex: 0 0 auto;
  gap: calc(60 * var(--nm-clients-k));
  /* Equal to the gap above: this is the seam spacer described in the section
     note. If the two ever diverge the join shows as a stutter once a cycle. */
  padding-right: calc(60 * var(--nm-clients-k));
}

/* --nm-clients-h is the file's own intrinsic height, set per logo by the macro
   in _shared.html. Width stays auto so each mark keeps its aspect ratio: the
   height is the only thing driven, and one factor drives all six. */
.nm-clients__logo {
  flex: 0 0 auto;
  width: auto;
  height: calc(var(--nm-clients-h) * var(--nm-clients-k));
  opacity: .75;                         /* node: the strip carries opacity 75% */
  transition: opacity .18s ease;
}
.nm-clients__logo:hover { opacity: .7; }

/* ONE GROUP, expressed as a fraction of the track so it stays exact: six equal
   groups, so one of them is 100%/6. Written as the division rather than
   -16.667% because the 6 here is the same 6 as the copy count in the macro —
   change one and this has to change with it.

   The duration is per-shift, not per-track, so adding copies did not speed the
   strip up: 42s still buys exactly one group of travel, at any width. */
@keyframes nm-clients {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(-100% / 6), 0, 0); }
}

/* Same fallback as section 7: the strip is decorative reassurance, so standing
   still costs it nothing. The duplicate goes rather than wrapping off-screen,
   and the remaining six centre and wrap. */
@media (prefers-reduced-motion: reduce) {
  .nm-clients__track {
    width: 100%;
    animation: none;
    justify-content: center;
  }
  /* All five duplicates go, not just one. */
  .nm-clients__group[aria-hidden="true"] { display: none; }
  .nm-clients__group {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 10px;
    padding-right: 0;
  }
}


/* ──────────────────────────────────────────────
   9. Hero — home (video)
   ──────────────────────────────────────────────
   A looping muted video behind the headline, a dot grid at the right, and a
   two-column body: the pitch and buttons at the left, two paragraphs of
   supporting copy at the right.

   THE VIDEO HAS NO POSTER. One was not supplied and there is no encoder in this
   repo to pull a frame out of the mp4, so the element gets the hero's own black
   background until the first frame decodes — which reads as the intended dark
   hero rather than as a hole. If a poster JPEG turns up, add it as
   `poster="..."` and nothing else has to change.

   The video is decorative: the headline carries the message, so the element is
   aria-hidden and there are no captions to miss. */
.nm-hero {
  position: relative;
  isolation: isolate;
  /* Pulled up under the sticky header so the video starts at the very top of
     the page, as the comp shows. The header's own height is what the padding
     below restores. */
  margin-top: calc(-1 * var(--nm-header-h));
  padding-top: calc(var(--nm-header-h) + clamp(40px, 5.4vw, 92px));
  padding-bottom: clamp(44px, 5vw, 84px);
  background: #000;
  overflow: hidden;
}

.nm-hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.nm-hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* NO GRADE. The clip plays exactly as supplied — see the note below, which
     covers the whole site's heroes. It used to carry brightness(1.25)
     saturate(1.05) to lift the lamps and beams out of a very dark frame; that
     went with the overlays, because "natural video only" means the grade too.
     The frame is dark, and that is now the intended look. */
}

/* NO OVERLAY ON ANY HERO, ANYWHERE ON THIS SITE — requested 2026-09-08: the
   supplied image or video is to show at its own natural exposure. Both hero
   shells used to carry one (a two-gradient .nm-hero__veil here, a heavier
   .nm-phero__veil in section 14, plus a brightness grade on the video), and all
   of it is gone rather than turned down.

   ON THE TWELVE PHOTOGRAPH HEROES IT COSTS NOTHING, AND THAT IS MEASURED.
   Every supplied hero still is already graded almost to black — mean relative
   luminance 0.0011 (contact, destination) to 0.0039 (our-works) — and white
   copy over them clears WCAG at every size and every width tested
   (2560/1920/1440/1024/640/390). The veil was darkening near-black frames and
   erasing the only thing in them, which is the truss, the beams and the LED.

   THE TWO VIDEO HEROES ARE A DIFFERENT STORY, AND IT IS AN OPEN ACCESSIBILITY
   ITEM RATHER THAN A SETTLED ONE. The homepage and the about page play the same
   clip. Its MEAN luminance is 0.013, but it is a moving lighting rig: pausing
   it at ten timestamps and measuring the 95th-percentile luminance of the
   frame actually behind each text block gives 63 of 600 samples under WCAG.
   The small copy is what fails, persistently rather than on the odd frame:

     index  @640   lede     16px   2.35-3.24:1   (needs 4.5)  10/10 frames
     index  @390   lede     16px   2.99-4.30:1   (needs 4.5)  10/10
     about  @1024  aside    14px   3.03-3.55:1   (needs 4.5)  10/10
     index  @1440  eyebrow  12px   3.15-4.30:1   (needs 4.5)  10/10
     index  @1024  eyebrow  12px   3.16-4.13:1   (needs 4.5)  10/10
     about  @1440  eyebrow  12px   3.14-4.71:1   (needs 4.5)   9/10

   Every headline passes (worst 4.07:1 against the 3:1 large text needs), so
   the page still reads; it is the 12-16px copy that goes under when a lamp
   sweeps behind it.

   DO NOT FIX THIS BY PUTTING THE VEIL BACK — it was removed on purpose and the
   client wants the clip's own exposure. The fix that does not touch the image
   is a scrim behind the GLYPHS only (a text-shadow, or a translucent pill
   behind the eyebrow), which leaves the video exactly as supplied. Raised with
   the client; see the spec doc.

   AND IF A BRIGHTER STILL IS EVER SUPPLIED FOR ONE OF THE TWELVE, RE-MEASURE.
   Their headroom comes from the art being dark, not from the type sizes: a
   hero still around 0.25 mean luminance would put the 14px aside under 4.5:1.

   The mobile breakpoint used to re-shape the horizontal ramp into a vertical
   one, because a left-to-right ramp is wrong once the hero stacks and the copy
   spans full width. With no ramp at all that correction has nothing to correct
   and it went too. */

/* The dot grid. Ten columns of eight dots at the hero's upper right in the
   comp — a background-image rather than 80 elements, faded out at the left and
   bottom with a mask so it dissolves into the video instead of ending on a hard
   edge. Hidden below the tablet breakpoint, where there is no room beside the
   headline for it. */
.nm-hero__dots {
  position: absolute;
  /* Anchored to the TOP right, and deliberately short. The comp's hero is
     shorter than this one (its aside copy is bottom-aligned in a taller box),
     so a grid sized to the comp's proportions grew down into the paragraphs at
     the right and sat behind the text. It now stops above them. */
  top: clamp(76px, 7.4vw, 112px);
  right: clamp(20px, 4.5vw, 72px);
  z-index: -1;
  width: clamp(180px, 21vw, 320px);
  height: clamp(104px, 10.4vw, 158px);
  background-image: radial-gradient(circle, rgba(255, 255, 255, .62) 1.5px, transparent 1.6px);
  background-size: 10% 12.5%;           /* comp: 10 columns x 8 rows */
  /* A gentle fade at the left and bottom only, so the block dissolves into the
     video on the two sides that face the copy and stays an even grid everywhere
     else. An earlier, steeper pair of gradients ate most of the dots and the
     10x8 block stopped reading as a grid at all. */
  -webkit-mask-image: linear-gradient(100deg, transparent 0%, #000 34%),
                      linear-gradient(0deg, transparent 0%, #000 26%);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(100deg, transparent 0%, #000 34%),
              linear-gradient(0deg, transparent 0%, #000 26%);
  mask-composite: intersect;
  pointer-events: none;
}

.nm-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, .74fr);
  gap: clamp(28px, 4vw, 72px);
  align-items: end;
}

/* BREAKPOINT-AWARE LINE BREAKS. A headline's break is content — it is the
   rhetorical shape of the line, not an accident of container width — so it is
   an element rather than a font-size tuned until the text happens to wrap. The
   frames break the same headline in different places at different widths,
   which needs two of them: --wide is off on a phone, --phone is off above it.

   Both default to `none` and are switched on by the breakpoint, so a break with
   no rule for the current width simply does not fire. */
.nm-br--phone { display: none; }

/* PHONE ART. Every full-bleed section background now has a second picture cut
   for the phone: the desktop files are landscape and these sections are all
   portrait below 640, so the narrow rung of a 1920x1080 file is the middle
   slice of it with the composition gone. The swap is a <source media> inside a
   <picture> (see bleed_img in _shared.html), which keeps one <img> and one
   class per section — the CSS below did not have to learn about any of it.

   display:contents IS LOAD-BEARING. Every one of these images is
   position:absolute, so the <picture> around it would otherwise generate an
   empty inline box in the section's flow: a stray line box in .nm-cta, and an
   extra grid item anywhere the background sits in a grid. */
.nm-bleed { display: contents; }

/* The two sections that have no desktop background at all and are given one by
   the phone art — the led-walls why-list and the gallery project grid. A
   <picture> cannot express "nothing above the breakpoint", so these are plain
   images switched off here and on in section 99. */
.nm-bg--phone { display: none; }

/* The gallery grid's own positioning context, for the same background. Inert
   above the breakpoint, where the section has nothing behind it. */
.nm-projects-sec__bg { display: none; }

.nm-hero__eyebrow { margin-bottom: clamp(14px, 1.6vw, 26px); }
.nm-hero__title { margin-bottom: clamp(14px, 1.9vw, 30px); }
.nm-hero__kicker { margin-bottom: clamp(14px, 1.5vw, 24px); }

.nm-hero__lede {
  max-width: 52ch;
  margin-bottom: clamp(22px, 2.6vw, 40px);
  color: #dfe1e6;
}

/* The right column's copy is smaller and dimmer than the lede — it is detail
   for the reader who is already interested, not part of the pitch.

   ITS WIDTH IS THE GRID COLUMN'S JOB, NOT A ch CAP. This carried
   `max-width: 46ch`, which at a fixed 14px font-size is a fixed 422px at every
   viewport — so the block stayed 422px while the page grew around it and looked
   narrower the wider the screen got. Measured against comp s (1), where this
   paragraph block is 349px of a 1068px viewport (32.7%):

       viewport    comp ratio wants    46ch gave    its grid track
       1440              471px           422px          477px
       1920              627px           422px          637px

   The 0.74fr track was already reproducing the comp's ratio to within 2% at
   both widths. The cap was the only thing narrowing it, so it is gone and the
   track governs.

   The 82ch backstop does not bind at any width the shell reaches (the track
   tops out near 720px, about 93ch) — it is there so that if the shell cap is
   ever raised, this does not quietly become a 120-character line. */
.nm-hero__aside {
  max-width: 82ch;
  font-size: 14px;
  line-height: 1.72;
  color: #b9bcc3;
}
.nm-hero__aside p + p { margin-top: 1em; }


/* ──────────────────────────────────────────────
   10. Stats band
   ──────────────────────────────────────────────
   Four figures with the wordmark watermarked behind them at the left. The
   numerals are the gradient's loudest use on the site: Anton at ~64px with the
   ramp clipped to the glyphs.

   `background-clip: text` needs the fallback colour set FIRST and the
   transparent fill last, so a browser that drops the clip renders solid purple
   text rather than nothing. That ordering is load-bearing — swapping the two
   lines makes the numbers invisible wherever the property is unsupported. */
.nm-stats {
  position: relative;
  overflow: hidden;
}

.nm-stats__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: clamp(28px, 3.4vw, 54px);
}

/* Hairline separators between the columns, on the item so the first has none.
   The comp runs them the full height of the tallest card. */
.nm-stat {
  padding-inline: clamp(14px, 1.7vw, 30px);
}
.nm-stat:first-child { padding-left: 0; }
.nm-stat + .nm-stat { border-left: 1px solid var(--nm-line); }

.nm-stat__figure {
  display: block;
  margin-bottom: 10px;
  font-family: var(--nm-font-display);
  font-size: clamp(46px, 4.6vw, 82px);
  line-height: .9;
  letter-spacing: -.01em;
  color: var(--nm-purple);              /* fallback — keep before the clip */
  background: var(--nm-grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.nm-stat__label { margin-bottom: 10px; }

.nm-stat__body {
  font-size: 15px;
  line-height: 1.62;
  color: var(--nm-ink-dim);
}

.nm-stats__cta {
  display: flex;
  justify-content: center;
  margin-top: clamp(30px, 3.4vw, 52px);
}


/* ──────────────────────────────────────────────
   11. Capability carousel
   ──────────────────────────────────────────────
   "Everything your event needs under one roof": a fixed left column of copy,
   then a slide made of a portrait photograph and the service's own heading and
   link, then prev/next buttons and dots.

   The track is a scroll-snap carousel, so it swipes and keyboard-scrolls with
   the script absent — norman.js only adds the arrow/dot wiring on top. CSS does
   the scrolling, JS does the indicators. */
.nm-cap {
  position: relative;
  overflow: hidden;
  padding-block: clamp(40px, 3.33vw, 64px);   /* node: 64 at 1920 */
}

 /* `fill`, not `cover`, and this is the one place in the stylesheet that wants
    it. The supplied art is a 3.3:1 panorama of coloured haze and the section it
    backs is about 1.4:1, so `cover` scales it 1.5x to reach the height and
    crops 350px off each side — which throws away the purple lobe on the left
    and the teal one on the right, the only two things in the image, and leaves
    the section looking flat black. `fill` stretches instead of cropping, and a
    diffuse gradient has no features to distort: the lobes land where the comp
    puts them. Anything with an edge in it must use `cover`. */
.nm-cap__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

/* Both frames drop the background to 70% and lay a black gradient over its top
   and bottom edges, so the haze fades into the sections either side instead of
   ending on a seam. A pseudo-element rather than markup: .nm-cap__bg sits at
   z-index 0 and .nm-cap__inner at 1, so an ::after painted after the image and
   before the content lands exactly between them. */
.nm-cap__bg { opacity: .7; }
.nm-cap::after {
  position: absolute;
  inset: 0;
  z-index: 0;
  content: "";
  background: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0) 50%, #000 100%);
  pointer-events: none;
}

/* THE COLUMNS ARE PERCENTAGES, NOT fr, AND THAT IS THE POINT. Node 296:155
   lays the section out on fixed pixels — a 430px copy column, 72px gutters, a
   350px photograph and the rest for the slide's text, inside 1480px of content
   on a 1920 frame. Those add to exactly 100%, so writing them as percentages
   reproduces the node's proportions at ANY container width, which matters here
   because this site's shell is deliberately wider than the node's (see the
   gutter note in section 1: content sits at ~87% of viewport against the
   frame's 77%). fr units would have divided the leftover space after a fixed
   gap instead, which is a different ratio at every width.

       430 : 72 : 978        ->  29.05% / 4.86% / 66.08%   (section)
       350 : 72 : 556        ->  35.79% / 7.36% / 56.85%   (slide)

   The old .82fr/1.35fr gave the copy column 37.8% — a third too wide, which is
   what pushed the photograph and the slide text right and left the headline
   setting on four lines where the node sets three. */
.nm-cap__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 29.05% 66.08%;
  gap: 4.86%;
  align-items: center;
}

.nm-cap__lead-title {
  margin-block: clamp(12px, .833vw, 16px) clamp(20px, 1.667vw, 32px);
}

/* Oswald REGULAR at 32 and 70% white — the node's, against the 300-weight 27px
   this carried. Scoped to the section: the hero's kicker is its own size and
   weight in its own frame, and .nm-kicker is shared between them. */
.nm-cap .nm-kicker {
  font-size: clamp(18px, 1.667vw, 32px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}

/* The positioned ancestor the control row measures against — see
   .nm-cap__controls. Its height is the track's, which is the photograph's. */
.nm-cap__col { position: relative; }

/* The slide viewport. `scroll-snap-type` on the track and `scroll-snap-align`
   on the slides is the whole carousel; `scrollbar-width: none` hides the bar
   without disabling the scrolling it represents. */
.nm-cap__track {
  display: flex;
  gap: clamp(20px, 2.6vw, 44px);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.nm-cap__track::-webkit-scrollbar { display: none; }

.nm-cap__slide {
  flex: 0 0 100%;
  display: grid;
  grid-template-columns: 35.79% 56.85%;
  gap: 7.36%;
  /* `stretch`, not `center`: the node runs the text column the full height of
     the photograph and pushes its own content to the top, which is what leaves
     the room at the foot for the controls. Centring it put the text block in
     the middle and closed that gap. */
  align-items: stretch;
  scroll-snap-align: start;
}

.nm-cap__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 350 / 450;              /* node 296:166 */
  object-fit: cover;
  border-radius: 8px;
}

/* The node insets the slide's text by 64px from the top of the photograph
   rather than aligning the two, so the headline starts below the picture's
   edge. */
.nm-cap__copy { padding-top: clamp(24px, 3.33vw, 64px); }

/* A slide with no photograph yet.
   ──────────────────────────────
   The comp shows seven services in this carousel and shipped art for one of
   them (the moving-head fixture). The other six get this instead of a
   photograph: the brand glow, the badge watermarked, and the service's name.

   It is NOT the supplied photograph repeated six times, which would read as
   finished and quietly commit the client to illustrating lighting with a photo
   of lighting on the audio slide as well. It is not blank either — a hole in
   the carousel looks like a bug rather than a gap in the assets. It looks
   deliberate and obviously unphotographed, which is exactly the status. Delete
   this rule when the six images arrive; nothing else changes, because the slide
   markup switches to an <img> on the presence of a photo. */
.nm-cap__photo--pending,
.nm-media4__media--pending {
  display: grid;
  place-items: center;
  padding: 18px;
  border: 1px solid var(--nm-line);
  border-radius: 4px;
  background:
    radial-gradient(72% 60% at 30% 24%, rgba(101, 85, 226, .42) 0%, rgba(101, 85, 226, 0) 70%),
    radial-gradient(66% 58% at 76% 78%, rgba(41, 154, 226, .34) 0%, rgba(41, 154, 226, 0) 72%),
    #08080a;
}
.nm-cap__photo--pending span,
.nm-media4__media--pending span {
  font-family: var(--nm-font-display);
  font-size: clamp(18px, 1.6vw, 26px);
  line-height: 1.05;
  text-align: center;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
}

/* Header/H3 again — the slide's name is set at the same 64px as the section
   headline beside it, not stepped down to .nm-h3's 34. */
.nm-cap__slide-title {
  font-size: clamp(28px, 3.33vw, 64px);
  line-height: 1;
  letter-spacing: -.02em;
  margin-bottom: clamp(20px, 1.667vw, 32px);
}

/* II/LG: 18 / 1.5 / -2%, at 70% white. The 44ch cap came off because the node
   runs this the full width of its column — 556px at the frame, about 62
   characters — and capping it at 44 left a column of text half the width of
   the picture next to it. */
.nm-cap__slide-body {
  max-width: none;
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  margin-bottom: clamp(20px, 1.667vw, 32px);
  color: rgba(255, 255, 255, .7);
}

/* The glass button, on both frames: 10% white behind a 12px blur with a 20%
   border, on a 4px radius. .nm-btn--ghost's own 7% veil and 2px blur read as
   flat grey at this size. */
.nm-cap .nm-btn {
  min-width: 180px;
  padding: clamp(14px, 1.25vw, 24px) clamp(24px, 2.5vw, 48px);
  border-radius: 4px;
  font-size: clamp(14px, .94vw, 18px);
  letter-spacing: -.02em;
}
.nm-cap .nm-btn--ghost {
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .2);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* THE CONTROLS ARE PINNED, NOT STACKED. The node puts them at the bottom right
   of the slide's own height — level with the foot of the photograph, inside the
   450px row, which is why the whole section is 578 tall (64 + 450 + 64) rather
   than 450 plus a control row. Below the track in normal flow they added their
   own height under the picture and the section grew.

   Absolute against .nm-cap__col, whose height is the track's and therefore the
   photograph's, so the two bottoms line up whatever the column width resolves
   to. The clearance above them is the node's 64px text inset plus the text
   being shorter than the picture; nothing here needs to know the copy's height.
   Put back into flow below the tablet breakpoint, where the slide stacks. */
.nm-cap__controls {
  position: absolute;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
}

.nm-cap__arrow {
  display: grid;
  place-items: center;
  width: clamp(38px, 2.5vw, 48px);
  height: clamp(38px, 2.5vw, 48px);
  padding: 0;
  background: rgba(255, 255, 255, .1);
  border: 1px solid var(--nm-line);
  border-radius: 50%;
  color: #fff;
  cursor: pointer;
  transition: background-color .16s ease;
}
.nm-cap__arrow:hover { background: rgba(255, 255, 255, .22); }
.nm-cap__arrow[disabled] { opacity: .35; cursor: default; }
.nm-cap__arrow svg { width: clamp(13px, .89vw, 17px); height: clamp(13px, .89vw, 17px); }

/* The dot row is 136x16 on BOTH frames, so unlike everything else in this
   section it does not scale — same seven dots, same pitch, phone and desktop. */
.nm-cap__dots {
  display: flex;
  align-items: center;
  gap: 13px;
}

/* The current dot is a ring, the rest are filled — that is the comp, and it
   keeps the indicator legible without relying on colour alone. */
.nm-cap__dot {
  width: 7px;
  height: 7px;
  padding: 0;
  background: rgba(255, 255, 255, .5);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}
.nm-cap__dot[aria-current="true"] {
  width: 15px;
  height: 15px;
  background: transparent;
  border: 1.5px solid #fff;
}


/* ──────────────────────────────────────────────
   12. Why-choose-us
   ──────────────────────────────────────────────
   Copy and a photograph at the left; at the right a list of three claims, each
   with a gradient checkmark and a hairline between them. The checkmark is an
   inline SVG using the gradient's two stops via a <linearGradient>, defined once
   per page in a hidden <svg> and referenced by id — see the `nm-grad-stroke`
   macro in the templates. */
/* A supplied background for the why-choose-us section, on the pages where the
   client shipped the coloured haze as a file instead of leaving it to the CSS
   glow. Same absolute-cover treatment as the other full-bleed section art.

   `fill`, not `cover`, for the reason given in section 11: this is diffuse haze
   with no features to distort, and `cover` on a 2.5:1 image behind a much
   squarer section would crop both coloured lobes straight out of frame. */
/* The node pads this section 96 top and bottom, a little more than the site's
   own --nm-section-y ceiling of 92. */
.nm-why { padding-block: clamp(48px, 5vw, 96px); }

.nm-why__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

/* Node 296:188: two flex-1 columns of 704 either side of a 72px gutter, inside
   1480 of content. Percentages for the same reason as section 11 — they hold
   the node's proportion at any container width, where the 1fr/1.08fr this
   carried made the right column wider than the left at every width and fr
   would have split the leftover space after a fixed gap rather than the whole.

       704 : 72 : 704   ->   47.57% / 4.86% / 47.57% */
.nm-why__grid {
  display: grid;
  grid-template-columns: 47.57% 47.57%;
  gap: 4.86%;
  /* The node stretches the right column and centres its content inside — the
     three claims sit against the middle of the left column's full height,
     headline through photograph, rather than starting level with the eyebrow.
     `start` is what had the list top-aligned and the space all falling below
     the third claim. */
  align-items: stretch;
}

.nm-why__list {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.nm-why__title { margin-block: clamp(12px, .833vw, 16px) clamp(20px, 1.667vw, 32px); }

/* STYLE/Paragraph: 18 / 1.5 / -2% at 70% white. The 48ch cap is gone because
   the node runs this the full 704 of its column, which is where its two-line
   set comes from; capped it broke to four. */
.nm-why__body {
  max-width: none;
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}

.nm-why__photo {
  width: 100%;
  height: auto;
  margin-top: clamp(24px, 2.5vw, 48px);
  aspect-ratio: 550 / 274;              /* node 296:197: 704x350, same ratio */
  object-fit: cover;
  border-radius: 8px;
}

.nm-why__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(10px, .625vw, 12px) clamp(14px, 1.25vw, 24px);
}

/* The rule belongs to the item above it, 12px under its copy, and the next
   claim starts 32px below that — the node's own asymmetry, which a single gap
   between items cannot express. At 10% white, not the 14% --nm-line. */
.nm-why__item + .nm-why__item {
  border-top: 1px solid rgba(255, 255, 255, .1);
  margin-top: clamp(10px, .625vw, 12px);
  padding-top: clamp(20px, 1.667vw, 32px);
}

.nm-why__check {
  grid-row: 1;
  /* The supplied check.svg is a 48x48 box whose ink fills the width but only
     ~81% of the height, so `width` sets the visible mark directly and the box
     carries a little slack underneath. `height: auto` keeps that slack rather
     than squashing the artwork, and the row's own alignment absorbs it. */
  width: clamp(32px, 2.5vw, 48px);   /* node 296:202 */
  height: auto;
}

/* Header/H10: Oswald 24 / 1.25 / 0 — no tracking, where .nm-label carries
   .035em and a 500 weight everywhere else on the site. */
.nm-why__item-title {
  align-self: center;
  font-size: clamp(17px, 1.25vw, 24px);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: 0;
}

/* THE COPY SPANS BOTH COLUMNS. It used to start in column 2, lining up under
   the heading rather than under the checkmark — the node runs it the full
   width of the item, flush left with the mark above it. Indented, it also lost
   about 60px of measure per line and broke to three where the node sets two. */
.nm-why__item-body {
  grid-column: 1 / -1;
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}


/* ──────────────────────────────────────────────
   13. Closing CTA band
   ──────────────────────────────────────────────
   Full-bleed art, the ask at the left, and a stack of two buttons plus a phone
   number at the right. Two background modes, because the comps differ: the
   destination page ships a photograph (the Las Vegas strip), the homepage does
   not and gets the CSS glow from section 3 instead. `.nm-cta--photo` is the
   difference — with a photograph the veil has to be much heavier for the white
   Anton headline to hold up. */
.nm-cta {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: clamp(46px, 5.83vw, 112px);   /* node 296:222 */
}

.nm-cta__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.nm-cta__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgba(0, 0, 0, .55) 0%, rgba(0, 0, 0, .3) 45%, rgba(0, 0, 0, .7) 100%);
}
/* OVER A PHOTOGRAPH THE NODE USES ONE GRADIENT, BOTTOM-UP: black at the foot
   of the band fading to nothing at the top, over an ungraded image. That is
   much lighter than what this carried — a centre radial plus a three-stop
   vertical, tuned when the homepage had no photograph and the destination
   page's skyline was the only case. With the arena shot in place the frame is
   already dark where the copy sits and the heavier veil just flattened it.

   The centred variant keeps the old treatment: it puts copy across the middle
   of the frame, where a bottom-up gradient gives it nothing. */
.nm-cta--photo .nm-cta__veil {
  background: linear-gradient(0deg, #000 0%, rgba(0, 0, 0, 0) 100%);
}

/* The centred variant used to keep a heavier veil — a centre radial plus a
   three-stop vertical — on the reasoning that copy across the middle of the
   frame gets nothing from a bottom-up gradient. Node 296:619 does not: it uses
   the same single bottom-up gradient as the split band, over an ungraded
   photograph. The override is gone and both variants share one veil.

   WORTH WATCHING: this is the brightest hero art on the site, a lit Las Vegas
   strip, and the headline sits over the upper half where the node's gradient
   has faded out. It reads, but it is the thinnest contrast margin in the
   build — if that photograph is ever swapped for a lighter one, this is the
   first thing to re-measure. */

/* Node 296:223: a flex-1 copy column, a 48px gutter and a FIXED 348px aside,
   inside 1480 of content — so 1084 / 48 / 348, which as percentages of the
   whole is what holds the node's proportion at any container width. Same
   reasoning as sections 11 and 12.

   `start`, not `center`: the node top-aligns the two columns, so the aside's
   first line sits level with the eyebrow rather than the block floating
   against the middle of a taller headline. */
.nm-cta__grid {
  display: grid;
  grid-template-columns: 73.24% 23.51%;
  gap: 3.24%;
  align-items: start;
}

.nm-cta__title { margin-block: clamp(12px, .833vw, 16px); }

/* II/LG at 70% white. The node gives this a 702px measure inside a 1084px
   column — 64.76% — rather than the 46ch it carried, which at 18px is 414px
   and broke the two-line set into four. */
.nm-cta__body {
  max-width: 64.76%;
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}

/* 24px between the lede, the button stack and the phone line; 25px between the
   two buttons. Close enough to look uniform, different enough that one gap
   cannot express both — hence the nested grid. */
.nm-cta__aside { display: grid; gap: clamp(14px, 1.25vw, 24px); }
.nm-cta__aside-lede,
.nm-cta__phone {
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}

/* The number is the same size and colour as the line it sits in, set bold —
   the node does not switch face for it. It used to go condensed, uppercase and
   pure white, which read as a third typeface in a two-line block. */
.nm-cta__phone a,
.nm-cta__phone span {
  font-weight: 700;
  color: inherit;
}
.nm-cta__phone a:hover { color: #fff; }

.nm-cta .nm-btns { gap: clamp(14px, 1.3vw, 25px); }

/* Button/Button: 4px radius, 18px label, the node's 48/24 padding.

   SCOPED TO THE SECTION, NOT TO .nm-cta__aside. The split variant stacks its
   buttons inside the aside; the centred variant puts them in a .nm-btns row.
   An aside-scoped rule therefore reached one layout and not the other, so the
   destination page's pair kept the site's square 13px default while the
   homepage's got the node's. Same for the phone overrides in section 99. */
.nm-cta .nm-btn {
  padding: clamp(14px, 1.25vw, 24px) clamp(24px, 2.5vw, 48px);
  border-radius: 4px;
  font-size: clamp(14px, .94vw, 18px);
  letter-spacing: -.02em;
}
.nm-cta .nm-btn--ghost {
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .2);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* A centred variant — the destination comp puts the whole block on the page's
   centre line over the skyline instead of splitting it left/right. */
.nm-cta--center .nm-cta__grid {
  grid-template-columns: minmax(0, 1fr);
  /* `stretch`, not `center`. Centring the grid item shrink-wraps it to its
     widest child, and the body's max-width is a PERCENTAGE — so it resolved
     against that shrunken box instead of the content column and came out at
     33% of the section where the node wants 47%. The column stays full width
     and text-align does the centring. */
  justify-items: stretch;
  text-align: center;
}
/* 702 of 1480 here against 702 of 1084 in the split layout — the node gives the
   body the same absolute measure in both, so the percentage differs. */
.nm-cta--center .nm-cta__body {
  max-width: 47.43%;
  margin-inline: auto;
}
.nm-cta--center .nm-btns {
  justify-content: center;
  margin-top: clamp(24px, 2.5vw, 48px);
}

/* THE TURNING BADGE. Node 296:620: 389 square at (1616, 63) on a 1920x507
   band, so about a fifth of it sits past the right edge and it starts just
   under the top. Written in vw so the overhang stays proportional instead of
   becoming a crop at one width and a gap at another.

   z-index 0 puts it over the veil (-1) and the photograph (-2) but under the
   copy, which is the node's own stacking: background, then badge, then
   container. Hidden below the tablet breakpoint — no frame draws it there, and
   at that width it would sit behind centred text rather than beside it. */
.nm-cta__spin {
  position: absolute;
  top: 3.28vw;
  right: -4.43vw;
  z-index: 0;
  width: 20.26vw;
  aspect-ratio: 1;
  opacity: .08;
  background: no-repeat center / contain;
  animation: nm-spin 40s linear infinite;
  pointer-events: none;
}
@keyframes nm-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
/* Decoration with no information in it, so standing still costs nothing. */
@media (prefers-reduced-motion: reduce) {
  .nm-cta__spin { animation: none; }
}
@media (max-width: 1023px) {
  .nm-cta__spin { display: none; }
}


/* ──────────────────────────────────────────────
   14. Hero — inner pages
   ──────────────────────────────────────────────
   Every page but the homepage opens on this: headline and buttons at the left,
   a framed photograph and two paragraphs at the right, over a very dark
   photograph. Same shell as the home hero, different inner grid, so the
   overlay and the negative top margin are shared rather than repeated.

   The frame is four corner brackets, drawn as two pseudo-elements each carrying
   two borders. Four separate elements would be cleaner to read but this needs no
   extra markup, and the brackets are decoration — they must not land in the
   accessibility tree. */
.nm-phero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin-top: calc(-1 * var(--nm-header-h));
  /* The node stands the content 140px clear of a 140px header (296:450's
     pt-140). Our bar is shorter than the frame's, so this is the frame's 140
     added to whatever --nm-header-h resolves to rather than a total. */
  padding-top: calc(var(--nm-header-h) + clamp(40px, 7.29vw, 140px));
  padding-bottom: clamp(36px, 3.65vw, 70px);
  background: #000;
}

.nm-phero__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* NO VEIL HERE EITHER. The inner-page hero used to carry a much heavier
   overlay than the homepage's, and it was removed site-wide on 2026-09-08 for
   the reasons measured in section 10. All thirteen inner pages now ship a
   supplied hero background, so the `.nm-glow` fallback in phero() is only
   reached by a page added without art — and that page would want checking
   against the note in section 10 before it ships. */

/* Node 296:451: a 900px text column, a 72px gutter and 668 for the art, inside
   1640 of content — 54.88% / 4.39% / 40.73%, which as percentages hold the
   node's proportion at any container width.

   THIS REPLACES A WORKAROUND THAT IS NO LONGER NEEDED. The columns were
   1.32fr/0.84fr — deliberately wider than the comp's 1.07/0.82 — because Anton
   runs about 7% wider than the design's Anton SC and the comp's own split
   wrapped "DELIVER MORE THAN A TICKET" onto a third line. Measured against the
   real node rather than the flat comp, that does not happen: at the node's 88px
   the long line sets 849px and the node's column resolves to 919px here, since
   this site's shell is wider than the frame's 1640 (see the gutter note in
   section 1). 70px of slack, so the design's own ratio is used. */
.nm-phero__grid {
  display: grid;
  grid-template-columns: 54.88% 40.73%;
  gap: 4.39%;
  align-items: start;
}

/* Header/H2: Anton 88 / 1.1 / -4%. It was set from the flat comp at 82 with the
   homepage's .94 leading; the node gives the inner-page headline its own token,
   larger than .nm-h1's 82 cap rather than stepped down from it. */
.nm-phero__title {
  font-size: clamp(34px, 4.583vw, 88px);
  line-height: 1.1;
  letter-spacing: -.04em;
}

.nm-phero__eyebrow { margin-bottom: clamp(20px, 1.667vw, 32px); }
.nm-phero__title { margin-bottom: clamp(12px, .833vw, 16px); }
.nm-phero__kicker { margin-bottom: clamp(24px, 3.333vw, 64px); }

/* Oswald Regular 32 at 70%, as on the carousel and the homepage CTA — the
   kicker is one voice across the site and .nm-kicker's 300-weight 27px is not
   it.

   SCOPED TO THE GRID, NOT THE SECTION. about.html reuses the .nm-phero shell
   but fills it with the HOMEPAGE hero's internals (.nm-hero__grid, its own
   kicker and its square button pair), so a .nm-phero descendant selector
   reaches a hero this was never measured against — it would have given that
   page phero buttons while the homepage's stayed square. .nm-phero__grid is
   the layout these values actually belong to. */
.nm-phero__grid .nm-kicker {
  font-size: clamp(18px, 1.667vw, 32px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}

.nm-phero__grid .nm-btns { gap: clamp(14px, 1.3vw, 25px); }

/* Button/Button, the same glass-and-white pair the homepage CTA carries. */
.nm-phero__grid .nm-btn {
  min-width: 180px;
  padding: clamp(14px, 1.25vw, 24px) clamp(24px, 2.5vw, 48px);
  border-radius: 4px;
  font-size: clamp(14px, .94vw, 18px);
  letter-spacing: -.02em;
}
.nm-phero__grid .nm-btn--ghost {
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .2);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* THE HAIRLINE DOWN THE ART COLUMN IS THE NODE'S, and it is the reason this
   column is now an element rather than a bare wrapper: 1px of 20% white with
   the contents inset 32 from it. The gap between the photograph and the copy
   is the column's own, which is why .nm-phero__frame no longer carries a
   bottom margin. */
.nm-phero__col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(16px, 1.667vw, 32px);
  padding-left: clamp(16px, 1.667vw, 32px);
  border-left: 1px solid rgba(255, 255, 255, .2);
}

/* The bracketed frame. Insets are negative so the brackets sit outside the
   photograph, as the comp shows. */
.nm-phero__frame { position: relative; }
.nm-phero__frame::before,
.nm-phero__frame::after {
  position: absolute;
  width: 22px;
  height: 22px;
  content: "";
  pointer-events: none;
}
.nm-phero__frame::before {
  top: -7px; left: -7px;
  border-top: 2px solid #fff;
  border-left: 2px solid #fff;
}
.nm-phero__frame::after {
  right: -7px; bottom: -7px;
  border-right: 2px solid #fff;
  border-bottom: 2px solid #fff;
}
/* The other two corners hang off an inner span, for the same no-extra-markup
   reason — the span is empty and aria-hidden. */
.nm-phero__corners::before,
.nm-phero__corners::after {
  position: absolute;
  width: 22px;
  height: 22px;
  content: "";
  pointer-events: none;
}
.nm-phero__corners::before {
  top: -7px; right: -7px;
  border-top: 2px solid #fff;
  border-right: 2px solid #fff;
}
.nm-phero__corners::after {
  bottom: -7px; left: -7px;
  border-bottom: 2px solid #fff;
  border-left: 2px solid #fff;
}

.nm-phero__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 668 / 350;               /* node 296:471: 350 tall, column-wide */
  object-fit: cover;
  border-radius: 4px;
}

/* Body/SM: 16 / 1.5 / -2%, and WHITE. This was 14px of #b9bcc3 on 1.72 leading,
   capped at 54ch — the node runs it at full column width in plain white, which
   makes it read as the hero's second paragraph rather than as a caption under
   the picture. The cap is gone for the same reason it went in the other
   sections: it is a fraction of the column the design already sized. */
.nm-phero__aside {
  max-width: none;
  font-size: clamp(14px, .83vw, 16px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: #fff;
}
.nm-phero__aside p + p { margin-top: clamp(12px, .833vw, 16px); }


/* ──────────────────────────────────────────────
   15. Statement block
   ──────────────────────────────────────────────
   Centred: the badge mark small, an Anton headline, two paragraphs at a narrow
   measure, then a pull quote with the eyebrow's rule at its left. Used to state
   the page's one big claim before the detail sections. */
.nm-statement {
  position: relative;
  overflow: hidden;
  padding-block: clamp(48px, 3.646vw, 70px);   /* node: 70 at 1920 */
  border-top: 1px solid rgba(255, 255, 255, .1);
  text-align: center;
}

.nm-statement__inner { position: relative; z-index: 1; }

/* The node puts a small N MONOGRAM above the headline (296:493, 21x24) — not
   the full badge with its ring of "NORMAN EVENT PRODUCTIONS" lettering.

   THIS USED TO BE A CROP, AND THE CROP NEVER WORKED. No monogram-only asset had
   been supplied, so the badge was rendered oversized inside a small window with
   overflow:hidden and pulled into place by a negative margin. That window is a
   <span> — display:inline, where width, height, overflow and `margin: 0 auto`
   are all ignored — so on all six pages carrying this block it rendered the
   FULL badge, lettering ring and all, flush left instead of a centred monogram.
   Forcing display:block fixed the box but not the pull: a negative margin on an
   inline replaced image resolves against the line box's baseline, so it then
   cropped the top-right of the ring rather than the centre.

   The monogram is now its own file, exported from the node
   (normal-logo-mark.svg), and the wrapper is just a centring block. */
.nm-statement__mark {
  display: block;
  width: 21px;
  margin: 0 auto clamp(16px, 1.25vw, 24px);
}
.nm-statement__mark img {
  display: block;
  width: 100%;
  height: auto;
}

.nm-statement__title { margin-bottom: clamp(20px, 1.667vw, 32px); }

/* STYLE/Paragraph at 70%, on the node's 880-of-1480 measure. The 70ch cap is a
   character count, so it drifts with the font size; 59.46% is the design's own
   proportion and holds at any width.

   The 1.5em between paragraphs is the node's blank line: it sets this as four
   paragraphs, two of them empty, which at 1.5 leading is one clear line of
   space rather than the 1.1em this had. */
.nm-statement__body {
  max-width: 59.46%;
  margin-inline: auto;
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}
.nm-statement__body p + p { margin-top: 1.5em; }

/* The pull quote: bold italic body copy with the brand rule at its left, the
   node's way of marking the sentence the page wants remembered.

   `width: fit-content` is what centres it properly. It was a block capped at
   76ch with an auto margin, which centres the BOX — so once the sentence was
   shorter than the cap the rule sat at the box's left edge with a gap before
   the text, and once it was longer the line broke where the node keeps it on
   one. Shrink-to-fit puts the rule against the first word and lets the centred
   parent do the rest. */
.nm-statement__quote {
  width: fit-content;
  max-width: 100%;
  margin: clamp(20px, 1.667vw, 32px) auto 0;
  padding-left: 8px;
  border-left: 2px solid #fff;
  font-size: clamp(15px, .94vw, 18px);
  font-style: italic;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: -.02em;
  text-align: left;
  color: #fff;
}


/* ──────────────────────────────────────────────
   16. Numbered service grid
   ──────────────────────────────────────────────
   Three columns by two rows of numbered services. The numeral uses the same
   clipped gradient as the stat figures, at a smaller size, with the same
   fallback-first ordering. */
.nm-numgrid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 1.667vw, 32px);
  margin-top: clamp(32px, 3.333vw, 64px);
}

/* THE CARDS ARE PADDED BOXES, not bare stacks — 32 all round on an 8px radius
   (node 296:509). The fill and border are transparent there, so nothing draws;
   what the padding buys is the space BETWEEN two cards' copy, which is 32 of
   padding either side of a 32 gutter rather than the gutter alone.

   Scoped away from the four-up variant, which is a different object: that one
   is a row of columns divided by hairlines, and it sets its own padding-inline
   to place those rules. Padding it here would push its first column off the
   page gutter and double-space its dividers. */
.nm-numgrid:not(.nm-numgrid--4) .nm-numcard {
  padding: clamp(20px, 1.667vw, 32px);
  border-radius: 8px;
}

/* Header/H4: Anton 48 / 1 / -2%, with the same clipped gradient and the same
   fallback-first ordering as the stat figures. */
.nm-numcard__num {
  display: block;
  margin-bottom: clamp(16px, 1.25vw, 24px);
  font-family: var(--nm-font-display);
  font-size: clamp(30px, 2.5vw, 48px);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--nm-purple);              /* fallback — keep before the clip */
  background: var(--nm-grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* Header/H11: Oswald 20 / 1 / 0 — the same token the why-choose-us claims use,
   and the same reason it overrides .nm-label, which carries a 500 weight and
   .035em tracking everywhere else. */
.nm-numcard__title {
  margin-bottom: clamp(16px, 1.25vw, 24px);
  font-size: clamp(15px, 1.042vw, 20px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0;
}

/* Body/SM at 80% white. */
.nm-numcard__body {
  font-size: clamp(14px, .83vw, 16px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .8);
}


/* ──────────────────────────────────────────────
   17. Location list
   ──────────────────────────────────────────────
   Two columns of pinned rows over the glow art. Each row is pin / name /
   description on one line, with a hairline under it; the name column is fixed
   so the descriptions align down the page, which is what makes the block read
   as a table rather than ten separate items. */
.nm-locs {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* The node screens the art back rather than laying it under at full strength,
   then fades the band into black top and bottom — the same treatment the
   homepage carousel gets, and for the same reason: the section has no hard edge
   of its own. */
.nm-locs__bg {
  position: absolute;
  z-index: -1;
  /* THE NODE DOES NOT COVER THE SECTION WITH THIS — it hangs a square 232% as
     tall as the band, centred on it and turned upside down (296:533 places the
     image at left 100.03% / top 165.94% on a -100% x -231.88% box, which is
     Figma's way of writing a both-axes flip anchored bottom-right). Read back
     as CSS that is: full width, 231.88% height, pulled up 65.94% so the
     overflow is even top and bottom, rotated half a turn.

     `fill`, not `cover`, for the reason given in sections 11 and 12: this is
     diffuse haze with no features to distort, and the node stretches it too —
     a 1024 square becomes 1920x2089 at the frame. */
  top: -65.94%;
  left: 0;
  width: 100%;
  height: 231.88%;
  object-fit: fill;
  opacity: .7;
  mix-blend-mode: screen;
  transform: rotate(180deg);
}
.nm-locs::after {
  position: absolute;
  inset: 0;
  z-index: -1;
  content: "";
  background: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0) 50%, #000 100%);
  pointer-events: none;
}

.nm-locs__title { margin-block: clamp(12px, .833vw, 16px) clamp(20px, 1.667vw, 32px); }

/* STYLE/Paragraph at 70%, full width. The 78ch cap is a character count and
   drifts with the size; the node runs this the width of the section. */
.nm-locs__lede {
  max-width: none;
  font-size: clamp(15px, .94vw, 18px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}

/* COLUMN-MAJOR, and that is the point of the explicit rows.
   The comp reads Las Vegas, Chicago, Los Angeles, Dallas, Washington DC down
   the left and Orlando, New York City, Atlanta, Nashville, Miami down the
   right. A plain two-column grid flows row-major, which would put Orlando
   beside Las Vegas and re-pair all ten cities — a change nobody would notice in
   review and which no test would catch, because every city is still on the
   page. `grid-auto-flow: column` with five fixed rows fills the left column
   first, so the source order and the comp's reading order are the same.

   The row count is tied to the city count: ten cities in five rows. If the list
   grows, this number grows with it — a sixth city with five rows starts a THIRD
   column. */
.nm-locs__cols {
  display: grid;
  grid-template-rows: repeat(5, auto);
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: clamp(12px, .833vw, 16px);
  margin-top: clamp(32px, 3.333vw, 64px);
}

.nm-loc {
  display: grid;
  grid-template-columns: auto minmax(130px, 180px) minmax(0, 1fr);
  gap: clamp(14px, 1.25vw, 24px);
  align-items: center;
  padding: clamp(14px, 1.042vw, 20px);
  border-bottom: 1px solid rgba(255, 255, 255, .15);
}

.nm-loc__pin {
  width: clamp(22px, 1.667vw, 32px);
  height: auto;
}

/* THE CITY IS ANTON, NOT THE LABEL TOKEN. It was .nm-label — Oswald 500 on
   .035em tracking, the site's card-heading style — where the node sets it in
   the display face at 28 (296:545). At the size these rows run, the condensed
   medium read as a table header; Anton reads as a place name, which is what
   makes the block feel like a list of destinations rather than a spec sheet. */
.nm-loc__name {
  font-family: var(--nm-font-display);
  font-size: clamp(18px, 1.458vw, 28px);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: 0;
  text-transform: uppercase;
  color: #fff;
}

.nm-loc__body {
  font-size: clamp(14px, .83vw, 16px);
  line-height: 1.4;
  color: rgba(255, 255, 255, .7);
}


/* ──────────────────────────────────────────────
   18. Advantage cards
   ──────────────────────────────────────────────
   Four columns of icon / heading / paragraph, no borders — the comp separates
   them on whitespace alone. The icons are inline SVG stroked with the brand
   gradient. */
.nm-adv {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(20px, 1.667vw, 32px);
  margin-top: clamp(32px, 3.333vw, 64px);
}

/* 48 at the frame — the same size as the why-choose-us checkmarks, and nearly
   double the 28 this carried. The node's layer names for two of these say
   "handshake" and "compass"; the assets themselves draw a document and a
   circle, which is what ico.icon_doc and ico.icon_target already render, so
   the glyphs stay as they are. */
.nm-adv__icon {
  width: clamp(32px, 2.5vw, 48px);
  height: auto;
  margin-bottom: clamp(12px, .833vw, 16px);
}

/* Header/H10: Oswald 24 / 1.25 / 0 — the third place this token appears (the
   why-choose-us claims and the service-card titles are the others), and the
   third time it has to override .nm-label's 500 weight and .035em. */
.nm-adv__title {
  margin-bottom: clamp(12px, .833vw, 16px);
  font-size: clamp(17px, 1.25vw, 24px);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: 0;
}

/* Body/SM at 70%. */
.nm-adv__body {
  font-size: clamp(14px, .83vw, 16px);
  line-height: 1.5;
  letter-spacing: -.02em;
  color: rgba(255, 255, 255, .7);
}


/* ──────────────────────────────────────────────
   19. Split statement — heading left, copy and pull quote right
   ──────────────────────────────────────────────
   The corporate and wedding comps open their second section this way instead of
   the destination page's centred statement (section 15): a short Anton headline
   held at the left, and the argument plus the page's pull quote at the right,
   with the badge watermarked behind the heading. Same content shape, opposite
   composition, so it is its own component rather than a modifier of the centred
   one — every rule would have had to be overridden. */
.nm-stmt2 {
  position: relative;
  overflow: hidden;
}

.nm-stmt2__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
  gap: clamp(26px, 3.6vw, 64px);
  align-items: start;
}

/* The comps hold this heading a little below the copy's first line, which is
   what the padding does — it is optical alignment, not a mistake. */
.nm-stmt2__title { padding-top: clamp(0px, 1.2vw, 20px); }

/* The widest prose on the site: this column is ~59% of the content width, so
   with the shell scaling to the viewport it reaches 105 characters at 1920
   uncapped. The comps put it at roughly 80. */
.nm-stmt2__body { max-width: 76ch; color: var(--nm-ink-dim); }
.nm-stmt2__body p + p { margin-top: 1.1em; }

.nm-stmt2__quote {
  margin-top: clamp(18px, 2vw, 30px);
  padding-left: 14px;
  border-left: 2px solid #fff;
  font-size: clamp(14px, 1.05vw, 16px);
  font-style: italic;
  font-weight: 500;
  line-height: 1.6;
  color: #fff;
}


/* ──────────────────────────────────────────────
   20. Four-up media strip
   ──────────────────────────────────────────────
   Four portrait tiles edge to edge under the split statement, two of which the
   comps mark as video with a white play badge. The strip is a scroll-snap row
   rather than a grid: at four tiles it fits on a desktop, and below the tablet
   breakpoint it becomes a swipeable carousel instead of four tiles squeezed to
   80px each.

   NO PHOTOGRAPHY WAS SUPPLIED for any of these tiles on any page. They render
   on the same pending treatment as the homepage carousel's unphotographed
   slides (section 11) — brand glow and a label — so the section reads as an
   intentional gap rather than as broken images. Give a tile a `photo` and it
   becomes an <img> with no other change. */
.nm-media4 {
  display: flex;
  gap: clamp(10px, 1vw, 16px);
  margin-top: clamp(28px, 3.4vw, 54px);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.nm-media4::-webkit-scrollbar { display: none; }

.nm-media4__item {
  position: relative;
  flex: 0 0 calc((100% - 3 * clamp(10px, 1vw, 16px)) / 4);
  scroll-snap-align: start;
}

.nm-media4__media {
  width: 100%;
  height: auto;
  aspect-ratio: 316 / 350;             /* comp: 316x350 at 1341 wide */
  object-fit: cover;
  border-radius: 3px;
}

/* The play badge: a white disc with a black triangle, bottom-right, on the two
   tiles the comps mark as video.

   IT IS A REAL <button> NOW that the clips exist — it was a decorative <span>
   while there was nothing to play. That is why it carries an accessible name in
   the markup (naming the clip, not "play video"), and why the appearance is
   reset: a <button> inherits the platform's own chrome and font otherwise.

   The clips are `preload="none"`, so nothing downloads until this is pressed —
   see the comment on .nm-media4__media below for why that matters here. */
.nm-media4__play {
  position: absolute;
  right: clamp(10px, 1vw, 16px);
  bottom: clamp(10px, 1vw, 16px);
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  padding: 0;
  background: #fff;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  transition: transform .16s ease, background-color .16s ease, opacity .2s ease;
}
.nm-media4__play:hover {
  transform: scale(1.08);
  background: #e8e9ed;
}
.nm-media4__play::after {
  content: "";
  margin-left: 3px;                    /* optical centring of a triangle */
  width: 0; height: 0;
  border-style: solid;
  border-width: 7px 0 7px 11px;
  border-color: transparent transparent transparent #000;
}

/* A <video> in a tile sizes exactly like the <img> does. Kept as its own
   selector rather than adding video to .nm-media4__media's element list,
   because `background` is only wanted here: the poster is letterboxed by
   `object-fit: cover` the same as a photo, but before the poster paints the
   element is transparent and would show the page through it. */
video.nm-media4__media { background: #08080a; }

/* While a clip is playing the badge gets out of the way. Driven by a class on
   the item rather than `hidden`, so it can fade rather than vanish. */
.nm-media4__item--playing .nm-media4__play {
  opacity: 0;
  pointer-events: none;
}

/* The dot row under the strip. IT EXISTS ONLY AT PHONE WIDTH, because that is
   the only width at which the strip is a carousel: four tiles fit on a desktop
   and the tablet breakpoint shows two and a bit, which reads as a row that
   continues. One tile per screen does not — nothing on screen says there are
   three more — so the phone frames (corporate s (3), wedding s (3)) draw a
   pagination row and the wider ones do not.

   Built by norman.js and absent without it, hence `display: none` here rather
   than a `hidden` attribute: with no script the strip is still a swipeable
   scroller, just an unlabelled one.

   The dots themselves are the capability carousel's (section 11) — same
   filled-dot / current-ring pair, same sizes, because they are the same
   control doing the same job on the same page. */
.nm-media4__dots { display: none; }

@media (max-width: 640px) {
  .nm-media4__dots {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 13px;
    margin-top: calc(22 * var(--nm-u));
  }
}

.nm-media4__dot {
  width: 7px;
  height: 7px;
  padding: 0;
  background: rgba(255, 255, 255, .5);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}
.nm-media4__dot[aria-current="true"] {
  width: 15px;
  height: 15px;
  background: transparent;
  border: 1.5px solid #fff;
}


/* ──────────────────────────────────────────────
   21. Process steps
   ──────────────────────────────────────────────
   Five numbered stages in a row, each with a gradient disc, a heading and a
   paragraph, separated by hairlines. Unlike the numbered service grid (section
   16) the numeral here is a filled disc rather than clipped text, because the
   comp draws it that way — and because a two-digit number inside a 30px circle
   needs a real background to sit on.

   The rules run the full height of the tallest step, which is why they are
   borders on the item rather than a separator element. */
/* Five columns by default because five of the six process sections have five
   steps. The contact page's "what happens next" has four, and rather than a
   modifier class for one section it sets --nm-steps-cols on a wrapper: with
   `repeat(5, …)` hardcoded, four steps would leave an empty fifth column and
   the row would read as though a step were missing. */
.nm-steps {
  display: grid;
  grid-template-columns: repeat(var(--nm-steps-cols, 5), minmax(0, 1fr));
  margin-top: clamp(28px, 3.4vw, 52px);
}

.nm-step { padding-inline: clamp(12px, 1.5vw, 26px); }
.nm-step:first-child { padding-left: 0; }
.nm-step + .nm-step { border-left: 1px solid var(--nm-line); }

.nm-step__num {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  margin-bottom: clamp(12px, 1.4vw, 22px);
  border-radius: 50%;
  background: var(--nm-grad);
  font-family: var(--nm-font-cond);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .02em;
  color: #fff;
}

.nm-step__title { margin-bottom: 9px; }

.nm-step__body {
  font-size: 14px;
  line-height: 1.6;
  color: var(--nm-ink-dim);
}


/* ──────────────────────────────────────────────
   22. Filter pills
   ──────────────────────────────────────────────
   The gallery's category row: "ALL EVENTS" set apart by a vertical rule, then
   seven category pills. The active pill is white on black; the rest are dark
   grey.

   They are <button>s, not links. Filtering happens in the page (norman.js) and
   there is no per-category URL to link to, so an <a href="#"> would be lying
   about being navigation — and with JavaScript unavailable a dead link is worse
   than a button that does nothing, because the whole unfiltered grid is still
   right there. `aria-pressed` carries the state, which is also the styling
   hook, so nothing has to keep a class in sync. */
.nm-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: clamp(20px, 2.4vw, 36px);
  justify-content: center;
}

.nm-filters__pill {
  padding: 9px 18px;
  background: #26262a;
  border: 0;
  border-radius: 999px;
  font-family: var(--nm-font-cond);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #d6d8dd;
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease;
}
.nm-filters__pill:hover { background: #34343a; color: #fff; }

.nm-filters__pill[aria-pressed="true"] {
  background: #fff;
  color: #000;
}

/* The rule after "ALL EVENTS". A border on the item rather than a separate
   element, so it cannot end up orphaned on a wrapped row. */
.nm-filters__sep {
  align-self: stretch;
  width: 1px;
  background: var(--nm-line);
}


/* ──────────────────────────────────────────────
   23. Project cards
   ──────────────────────────────────────────────
   Three across: a photograph, a category pill, the project title, a venue line
   and a description, on a card that lifts very slightly off the page black.

   THE CARD COPY IS BRACKETED ON PURPOSE. The comps print "[CLIENT NAME]" and
   "[Venue Name], [City]" because no real project data had been supplied, and
   the templates print the same brackets. Inventing client names on a portfolio
   page would be fabricating credentials, so the brackets stay until the client
   fills them in. */
.nm-projects {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(18px, 2.2vw, 34px);
  margin-top: clamp(26px, 3vw, 44px);
}

.nm-proj {
  display: flex;
  flex-direction: column;
  padding: clamp(12px, 1.1vw, 17px);
  background: var(--nm-bg-raise);
  border: 1px solid var(--nm-line-soft);
  border-radius: 6px;
}

.nm-proj__media {
  width: 100%;
  height: auto;
  aspect-ratio: 337 / 253;             /* comp: 337x253 at 1341 wide */
  object-fit: cover;
  border-radius: 3px;
}

.nm-proj__cat {
  align-self: flex-start;
  margin-top: clamp(12px, 1.2vw, 18px);
  padding: 5px 13px;
  background: #2c2c31;
  border-radius: 999px;
  font-family: var(--nm-font-cond);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: #e2e4e8;
}

.nm-proj__title {
  margin-top: 12px;
  font-family: var(--nm-font-cond);
  font-size: clamp(15px, 1.15vw, 19px);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: .01em;
  color: #fff;
}

.nm-proj__meta {
  margin-top: 4px;
  font-size: 13px;
  color: var(--nm-ink-dimmer);
}

.nm-proj__body {
  margin-top: 12px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--nm-ink-dim);
}

/* Shown by norman.js when a filter matches nothing. It exists in the markup
   with `hidden` rather than being created by the script, so its copy is
   translatable and reviewable in the template like everything else. */
.nm-projects__empty {
  margin-top: clamp(26px, 3vw, 44px);
  text-align: center;
  color: var(--nm-ink-dim);
}


/* ──────────────────────────────────────────────
   24. Spec blocks
   ──────────────────────────────────────────────
   The LED walls page's kit section: three blocks that alternate sides — specs
   left / mosaic right, then mosaic left / specs right, then back again — each
   block being a heading over a rule and two columns of name-and-value pairs.

   The pairs are a <dl>. They are literally names and values ("Brightness" /
   "Adjustable 1200-2000 nits output"), a definition list says exactly that, and
   it means a screen reader reads each value as belonging to its label instead of
   as six unrelated lines of text. Two visual columns come from the grid; the
   markup stays one flat list, because splitting the <dl> in two to make columns
   would break the pairing on a phone.

   The mosaic BLEEDS PAST THE SHELL in the comp — the right-hand strip runs off
   the edge of the page — which is why it is allowed to overflow rather than
   being fitted inside the gutter. */
.nm-specs {
  position: relative;
  z-index: 1;
  display: grid;
  gap: clamp(34px, 4.6vw, 78px);
  margin-top: clamp(30px, 3.6vw, 56px);
}

/* The spec section's own positioning context, so its background has something
   to be absolute against. Without it the image resolves against the initial
   containing block and paints a viewport-sized panel at the top of the page —
   see the comment in the specs() macro. Only added when there IS a background,
   so the plain case gains no stacking context it does not need. */
.nm-specs-wrap--bg {
  position: relative;
  overflow: hidden;
}

/* Optional full-bleed haze behind the whole spec section, on the pages whose
   comp draws one. `fill` for the same reason as the other haze backgrounds in
   sections 11 and 12 — it is diffuse, so stretching it costs nothing and
   cropping it would throw the coloured lobes out of frame. */
.nm-specs__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

.nm-spec {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(24px, 3.4vw, 60px);
  align-items: center;
}

/* Blocks 2 of 3 put the mosaic on the left. `order` rather than a reversed
   grid, so the source order stays heading-then-pictures-then-next-heading and
   the reading order is right whatever the layout does. */
.nm-spec--flip .nm-spec__art { order: -1; }

.nm-spec__title {
  font-family: var(--nm-font-display);
  font-size: clamp(19px, 1.6vw, 28px);
  letter-spacing: .005em;
  text-transform: uppercase;
  color: #fff;
}
.nm-spec__title::after {
  display: block;
  height: 1px;
  margin-top: clamp(12px, 1.3vw, 20px);
  background: var(--nm-line);
  content: "";
}

.nm-spec__pairs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 1.8vw, 28px) clamp(18px, 2vw, 34px);
  margin-top: clamp(16px, 1.8vw, 26px);
}

.nm-spec__pairs dt {
  font-family: var(--nm-font-cond);
  font-size: clamp(13px, 1vw, 16px);
  font-weight: 500;
  letter-spacing: .035em;
  text-transform: uppercase;
  color: #fff;
}

.nm-spec__pairs dd {
  margin: 5px 0 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--nm-ink-dim);
}

/* The mosaic: one tall tile and two stacked, matching the comp's arrangement. */
.nm-spec__art {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  gap: clamp(6px, .6vw, 10px);
  aspect-ratio: 5 / 3;
}
.nm-spec__art > :first-child { grid-row: span 2; }

.nm-spec__tile {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 3px;
}


/* ──────────────────────────────────────────────
   25. Format profile
   ──────────────────────────────────────────────
   The event-type page's two profiles: a full-width heading, then a photograph
   on one side and five labelled rows on the other — What it is / Why it works /
   Best for / What it requires / Production point. The in-person profile puts
   the photograph left, the virtual profile puts it right.

   The rows are a <dl> for the same reason as the spec pairs in section 24:
   they are label-and-value, and the labels repeat identically across both
   profiles, so a screen reader needs to know which "Best for" belongs to
   which format. Two <dl>s, each scoped to its own profile, answers that. */
.nm-format__grid {
  display: grid;
  grid-template-columns: minmax(0, .84fr) minmax(0, 1.16fr);
  gap: clamp(24px, 3.4vw, 60px);
  align-items: start;
  margin-top: clamp(24px, 2.8vw, 44px);
}

/* The virtual profile: rows left, photograph right. `order` on the art rather
   than a reversed grid, so the source order stays the same in both. */
.nm-format--flip .nm-format__art { order: 1; }

.nm-format__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 447 / 493;             /* comp: 447x493 at 1338 wide */
  object-fit: cover;
  border-radius: 5px;
}

.nm-format__rows dt {
  font-family: var(--nm-font-cond);
  font-size: clamp(14px, 1.05vw, 17px);
  font-weight: 400;
  letter-spacing: .045em;
  text-transform: uppercase;
  color: #fff;
}

.nm-format__rows dd {
  margin: 7px 0 0;
  font-size: 14px;
  line-height: 1.62;
  color: var(--nm-ink-dim);
}

/* The hairline goes on the wrapper div, so it sits between pairs rather than
   between a label and its own value. */
.nm-format__row { padding-block: clamp(13px, 1.5vw, 20px); }
.nm-format__row:first-child { padding-top: 0; }
.nm-format__row + .nm-format__row { border-top: 1px solid var(--nm-line); }


/* ──────────────────────────────────────────────
   26. Kit categories
   ──────────────────────────────────────────────
   The inventory page's five kit sections, each four columns wide: an icon and a
   category heading, then a list of item-and-detail pairs. Twenty categories and
   about sixty items across one page, which is why this is a component driven by
   a list rather than sixty hand-written blocks.

   The pairs are a <dl> for the same reason as the spec pairs in section 24 —
   they are name and detail, and the detail must be announced as belonging to
   its name. No rules and no card: the comp separates the columns on whitespace
   alone, like the advantage cards in section 18. */
.nm-kits {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(26px, 3vw, 48px);
  margin-top: clamp(28px, 3.4vw, 52px);
}

.nm-kit__head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: clamp(14px, 1.6vw, 22px);
}

/* Sized in em so the icon tracks the heading beside it rather than needing a
   second clamp kept in step with the type. */
.nm-kit__icon {
  flex: 0 0 auto;
  width: 1.15em;
  height: 1.15em;
}

.nm-kit__title {
  font-family: var(--nm-font-cond);
  font-size: clamp(14px, 1.05vw, 17px);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #fff;
}

.nm-kit__items dt {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.35;
  color: #fff;
}

.nm-kit__items dd {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--nm-ink-dim);
}

.nm-kit__item + .nm-kit__item { margin-top: clamp(14px, 1.6vw, 22px); }


/* ──────────────────────────────────────────────
   27. Photo band
   ──────────────────────────────────────────────
   A full-bleed photograph between two sections, used five times on the
   inventory page to break the kit lists up. Fixed aspect rather than fixed
   height, so the crop is the same shape at every width, and `cover` rather than
   the `fill` of section 11 — these are photographs with real content in them
   and must not be stretched.

   None of the five was supplied, so they render on the pending treatment at the
   same aspect ratio. */
.nm-band {
  width: 100%;
  aspect-ratio: 1341 / 300;            /* comp: full-bleed at 1341 wide */
  object-fit: cover;
}

/* The pending treatment gives its tiles a radius and a border, which is right
   for a card and wrong for something bleeding to both edges of the page. */
.nm-band--pending {
  border-radius: 0;
  border-inline: 0;
}


/* ──────────────────────────────────────────────
   28. Enquiry form
   ──────────────────────────────────────────────
   The contact page's brief form: a dark card floating over the ambient glow,
   with a two-column field grid and labels ABOVE the inputs rather than as
   placeholders inside them.

   That last part is the comp's own choice and it is the right one, so it is
   worth recording why it must not be "tidied" into placeholder-only fields
   later. A placeholder disappears the moment someone types, so a visitor who
   looks away mid-form cannot tell which field they are in; it is low-contrast
   by definition; and it is not reliably announced as a field's name. The comp
   keeps BOTH — a real <label> above and an example inside — which is why every
   field here has a visible label and a placeholder showing the expected format
   ("DD/MM/YYYY or season", "e.g. Madison Square Garden, NY").

   Required fields are marked by an asterisk in the label text and by the
   `required` attribute. Never by colour alone. */
.nm-form-sec {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

/* The supplied haze behind the brief form, on the contact page only. `fill`
   rather than `cover` for the reason given in section 11: it is diffuse colour
   with no features to distort, and cropping a 1.32 image to this section's
   shape would throw its coloured lobes out of frame. Behind the shell, which
   carries its own z-index. */
.nm-form-sec__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

.nm-form-sec__head { margin-bottom: clamp(24px, 3vw, 42px); }

/* The card is narrower than the shell on purpose — comp s (3) centres it at
   about 52% of the viewport, not the full content column, because a two-column
   form stretched to 1700px puts a label a very long way from its field.

   So the cap is proportional rather than the flat 760px it was: 760 was 52% of
   a 1440 viewport and correct while the shell capped at 1400, but once content
   scales past that a fixed 760 leaves the card looking marooned. This keeps it
   on the comp's ratio and stops it growing past ~1050, where the two columns
   get too far apart to scan. Below 1440 the 760px floor never applies, because
   the shell is narrower than the card's own cap by then. */
.nm-form-card {
  max-width: clamp(760px, 52.5vw, 1050px);
  margin-inline: auto;
  padding: clamp(22px, 2.8vw, 44px);
  background: rgba(10, 10, 12, .82);
  border: 1px solid var(--nm-line-soft);
  border-radius: 8px;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.nm-form__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 1.6vw, 22px);
}

/* The message field spans both columns, as the comp has it. */
.nm-form__field--wide { grid-column: 1 / -1; }

.nm-form__label {
  display: block;
  margin-bottom: 7px;
  font-size: 13px;
  font-weight: 400;
  color: #dcdee3;
}

.nm-form__input,
.nm-form__select,
.nm-form__area {
  width: 100%;
  padding: 12px 14px;
  background: #141418;
  border: 1px solid rgba(255, 255, 255, .11);
  border-radius: 5px;
  /* Inherited font is NOT the default for form controls — every browser
     substitutes its own UI font here, so the fields would be the one place on
     the site not set in Outfit. */
  font-family: var(--nm-font-body);
  font-size: 14px;
  line-height: 1.4;
  color: #fff;
  transition: border-color .16s ease, background-color .16s ease;
}

.nm-form__input:hover,
.nm-form__select:hover,
.nm-form__area:hover { border-color: rgba(255, 255, 255, .2); }

.nm-form__input:focus,
.nm-form__select:focus,
.nm-form__area:focus {
  border-color: var(--nm-blue);
  background: #17171c;
}

/* Placeholder contrast. The comp's placeholders read as mid-grey; anything
   dimmer stops being legible on the field's own dark fill, and these
   placeholders carry format hints worth reading. `opacity: 1` because Firefox
   applies its own. */
.nm-form__input::placeholder,
.nm-form__area::placeholder { color: #82858d; opacity: 1; }

.nm-form__area {
  min-height: 132px;
  resize: vertical;
}

/* The selects keep the platform's own dropdown — a custom listbox would be a
   worse control on a phone and easy to get wrong for the keyboard — but lose
   the platform's chrome, so the caret is drawn here. A data: URI rather than a
   file, so the stylesheet stays the single request it is. */
.nm-form__select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%2382858d' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
}

/* An unchosen select is showing its prompt option, which is placeholder text
   and should read like the placeholders above rather than like an answer. The
   prompt option carries `value=""` and the field is `required`, which is what
   makes :invalid true only while nothing is chosen. */
.nm-form__select:invalid { color: #82858d; }

.nm-form__actions {
  margin-top: clamp(20px, 2.4vw, 32px);
  text-align: center;
}

.nm-form__consent {
  margin-top: clamp(14px, 1.6vw, 22px);
  font-size: 12px;
  line-height: 1.6;
  color: var(--nm-ink-dimmer);
}
.nm-form__consent a {
  color: #c9ccd2;
  text-decoration: underline;
}
.nm-form__consent a:hover { color: #fff; }

/* reCAPTCHA sits above the button. Centred, and given its own row, because its
   iframe is a fixed 302px wide and cannot be reflowed. */
.nm-form__captcha {
  display: flex;
  justify-content: center;
  margin-top: clamp(16px, 1.8vw, 24px);
}

/* Shown by norman.js when Send is pressed with the box unticked or expired. */
.nm-form__captcha-msg {
  margin-top: 12px;
  text-align: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--nm-ink);
}

/* Set by norman.js once a valid form is on its way, so a second press cannot
   post the same reCAPTCHA token again. */
.nm-form button[disabled] { opacity: .6; cursor: progress; }


/* ──────────────────────────────────────────────
   29. Direct-contact rows
   ──────────────────────────────────────────────
   "Prefer to speak directly?" — the headline held at the left, three rows at
   the right: a gradient outline icon, a heading, the detail, and a note.
   Hairlines between rows, the same rhythm as the why-choose-us checklist.

   THE DETAILS ARE BRACKETED. The comp prints "[PHONE NUMBER]", "[EMAIL
   ADDRESS]" and "[FULL COMPANY ADDRESS]" because none had been supplied, and
   the template prints the same brackets until the constants in app.py are
   filled in. See the footer partial for why a visible bracket beats an omitted
   line. */
.nm-direct__grid {
  display: grid;
  grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: clamp(26px, 3.6vw, 64px);
  align-items: start;
}

.nm-direct__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 4px clamp(14px, 1.6vw, 24px);
  padding-block: clamp(18px, 2vw, 28px);
}
.nm-direct__row:first-child { padding-top: 0; }
.nm-direct__row + .nm-direct__row { border-top: 1px solid var(--nm-line); }

/* The icon spans all three text rows so it stays beside the heading rather
   than being pushed down by a two-line note. */
.nm-direct__icon {
  grid-row: span 3;
  align-self: start;
  width: clamp(22px, 1.8vw, 28px);
  height: auto;
  margin-top: 4px;
}

.nm-direct__value {
  grid-column: 2;
  margin-top: 6px;
  font-size: 15px;
  color: #e4e6ea;
}

.nm-direct__note {
  grid-column: 2;
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--nm-ink-dimmer);
}


/* ──────────────────────────────────────────────
   30. Case study
   ──────────────────────────────────────────────
   The our-works page's three project write-ups. Each is an eyebrow, an Anton
   headline, three icon-and-value meta rows (location / delegates / production
   scope), then three labelled prose blocks (the brief / what we delivered /
   the outcome) and a CTA — beside a portrait photograph that alternates side.

   THE COMPS' OWN COPY IS EDITORIAL PLACEHOLDER, and unusually explicit about
   it: "[Description of production approach, key technical solutions, and
   outcomes. Insert when client approves case study for publication.]" So the
   brackets are transcribed exactly. This is the one page where the placeholder
   text is itself the specification — it names who has to approve the content
   before it can be written.

   The comps also draw the photograph as an empty grey box rather than a
   picture, so even the comp has no image here. It renders on the pending
   treatment like everywhere else. */
.nm-case__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, .78fr);
  gap: clamp(26px, 3.6vw, 64px);
  align-items: start;
}

/* Cases 2 of 3 put the photograph on the left. `order` on the art, so the
   source order is always heading-then-detail-then-picture. */
.nm-case--flip .nm-case__art { order: -1; }

.nm-case__title { margin-block: 14px clamp(18px, 2vw, 28px); }

/* The meta rows. A <dl> again: three labels with three values, and the labels
   repeat across all three case studies, so the pairing has to be explicit. */
.nm-case__meta { margin-bottom: clamp(18px, 2vw, 28px); }

.nm-case__row {
  display: grid;
  grid-template-columns: auto minmax(96px, 128px) minmax(0, 1fr);
  gap: clamp(10px, 1.2vw, 18px);
  align-items: center;
  padding-block: clamp(11px, 1.2vw, 16px);
  border-bottom: 1px solid var(--nm-line);
}
.nm-case__row:first-child { padding-top: 0; }

.nm-case__icon {
  width: clamp(15px, 1.2vw, 19px);
  height: auto;
}

.nm-case__row dt {
  font-family: var(--nm-font-cond);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #fff;
}

.nm-case__row dd {
  margin: 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--nm-ink-dim);
}

/* The prose blocks. The label is a heading, not bold body text, because "The
   brief" / "What we delivered" / "The outcome" is the structure of the case
   study and a screen reader should be able to jump between them. */
.nm-case__block + .nm-case__block { margin-top: clamp(14px, 1.6vw, 22px); }

.nm-case__block-title {
  margin-bottom: 6px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  color: #fff;
}

/* Same 59%-of-content column as .nm-stmt2__body, same reason for the cap. */
.nm-case__block-body {
  max-width: 78ch;
  font-size: 14px;
  line-height: 1.62;
  color: var(--nm-ink-dim);
}

.nm-case__cta { margin-top: clamp(20px, 2.4vw, 32px); }

.nm-case__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 445 / 475;             /* comp: 445x475 at 1333 wide */
  object-fit: cover;
  border-radius: 6px;
}


/* ──────────────────────────────────────────────
   31. Values grid
   ──────────────────────────────────────────────
   The about page's "How we operate": the same numbered cards as section 16, but
   four across with a hairline between each. A modifier rather than a second
   component, because the numeral, the heading and the body are identical — only
   the column count and the rules change.

   The rules are borders on the item, so the first has none and the row can wrap
   without a rule appearing at the start of the second line. */
.nm-numgrid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
}

.nm-numgrid--4 .nm-numcard { padding-inline: clamp(14px, 1.7vw, 30px); }
.nm-numgrid--4 .nm-numcard:first-child { padding-left: 0; }
.nm-numgrid--4 .nm-numcard + .nm-numcard { border-left: 1px solid var(--nm-line); }

/* The numeral runs larger here than in the six-up grid — comp s (3) sets it at
   roughly the size of the stat figures rather than the service numbers. */
.nm-numgrid--4 .nm-numcard__num { font-size: clamp(34px, 3.1vw, 54px); }


/* ──────────────────────────────────────────────
   32. Capability lists
   ──────────────────────────────────────────────
   Three columns of ticked items over the supplied haze — production
   the in-house team, destination capabilities. Plain lists with a chevron
   marker, which is drawn as a CSS triangle on the ::before rather than set as a
   character, for the same reason as the button arrows in section 4. */
/* The supplied haze behind the about page's capability lists, in place of the
   CSS glow the section used to carry. `fill` for the same reason as every other
   haze background here. */
.nm-caps__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

.nm-caps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(26px, 3.4vw, 58px);
  margin-top: clamp(28px, 3.4vw, 52px);
}

.nm-caps__title { margin-bottom: clamp(14px, 1.6vw, 22px); }

.nm-caps__item {
  position: relative;
  padding-left: 22px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--nm-ink-dim);
}
.nm-caps__item + .nm-caps__item { margin-top: clamp(9px, 1vw, 14px); }

.nm-caps__item::before {
  position: absolute;
  left: 2px;
  top: .55em;
  width: 0; height: 0;
  border-style: solid;
  border-width: 3.5px 0 3.5px 5px;
  border-color: transparent transparent transparent #9aa0ab;
  content: "";
}


/* ──────────────────────────────────────────────
   33. Team cards
   ──────────────────────────────────────────────
   Three cards: a portrait, a name, a job title, a short bio.

   THE COMP'S OWN PLACEHOLDER SAYS "AWAITING CONTENT" inside each portrait, and
   the name, title and bio are bracketed. So all three cards render on the
   pending treatment carrying that exact label, and the bracketed copy is
   transcribed. Nobody's name or photograph is invented — which on a "meet the
   team" page would mean inventing employees. */
.nm-team {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(18px, 2.2vw, 34px);
  margin-top: clamp(28px, 3.4vw, 52px);
}

.nm-team-card {
  padding: clamp(12px, 1.1vw, 17px);
  background: var(--nm-bg-raise);
  border: 1px solid var(--nm-line-soft);
  border-radius: 6px;
}

.nm-team-card__photo {
  width: 100%;
  height: auto;
  aspect-ratio: 253 / 176;             /* comp: 253x176 at 1150 wide */
  object-fit: cover;
  border-radius: 3px;
}

.nm-team-card__name {
  margin-top: clamp(12px, 1.2vw, 18px);
  font-family: var(--nm-font-display);
  font-size: clamp(17px, 1.4vw, 23px);
  letter-spacing: .005em;
  text-transform: uppercase;
  color: #fff;
}

.nm-team-card__role {
  margin-top: 3px;
  font-family: var(--nm-font-cond);
  font-size: 13px;
  font-weight: 300;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--nm-ink-dimmer);
}

.nm-team-card__bio {
  margin-top: 11px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--nm-ink-dim);
}


/* ──────────────────────────────────────────────
   99. Responsive
   ──────────────────────────────────────────────
   Three breakpoints, and each one exists for a reason rather than for a round
   number:

   1180px — the header pill stops fitting beside the badge and the CTA.
   1023px — the two-column section grids stop working: the comps' 40/60 splits
            leave both halves too narrow to read, so everything stacks.
    640px — phone. Four-across grids go to two or one, and the type scale's
            lower clamp bound takes over.
   ────────────────────────────────────────────── */

@media (max-width: 1180px) {
  /* The nav becomes a panel. It is absolutely positioned rather than pushing
     the page down, so opening it over a hero does not reflow what is behind it,
     and `hidden` on the wrapper is what closes it — toggled by norman.js, and
     honoured by [hidden] in the base reset, so with the script absent the panel
     never opens and the header degrades to badge + CTA. */
  .nm-burger { display: block; }

  .nm-nav {
    position: absolute;
    top: calc(100% + 8px);
    right: var(--nm-gutter);
    left: var(--nm-gutter);
    flex-direction: column;
    align-items: stretch;
    padding: 6px;
    border: 1px solid var(--nm-line);
    border-radius: 8px;
    background: #101012;
    margin-inline: 0;
  }
  .nm-nav[hidden] { display: none; }

  .nm-nav__item + .nm-nav__item {
    border-left: 0;
    border-top: 1px solid rgba(255, 255, 255, .1);
  }
  .nm-nav__link { padding: 14px 12px; font-size: 14px; }

  /* The nav leaves the grid flow entirely (it is absolutely positioned below),
     so the bar becomes badge / burger / CTA. `position: relative` is what the
     panel's absolute positioning resolves against. */
  .nm-header__inner {
    position: relative;
    grid-template-columns: 1fr auto auto;
  }
  .nm-header__nav { display: contents; }
  .nm-burger { justify-self: end; }

  /* The services panel flattens. Inside a stacked burger menu there is nowhere
     to float a panel to, and hover does not exist on the devices that see this
     breakpoint, so the sub-list is simply always open and indented. Every
     positioning and visibility rule from section 5 has to be undone or the
     panel stays clipped at opacity 0 and the nine links are unreachable —
     which is the failure this breakpoint exists to prevent. */
  .nm-menu {
    position: static;
    min-width: 0;
    margin: 2px 0 6px;
    padding: 0 0 0 12px;
    border: 0;
    border-left: 1px solid var(--nm-line-soft);
    border-radius: 0;
    background: none;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  .nm-menu__link { padding: 10px 8px; }
  .nm-menu__item--divide { margin-top: 0; padding-top: 0; border-top: 0; }

  /* The caret would now be pointing at something already open. */
  .nm-nav__caret { display: none; }
}

@media (max-width: 1023px) {
  .nm-hero__grid,
  .nm-phero__grid,
  .nm-why__grid,
  .nm-cap__inner,
  .nm-cta__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /* With the hero stacked there is no column beside the headline for the dot
     grid, and letting it sit behind the copy hurts legibility. */
  .nm-hero__dots { display: none; }

  /* Same reasoning for the inner hero's hairline: it divides two columns, and
     stacked there is nothing on its left to divide from — it would read as a
     rule down the page gutter. */
  .nm-phero__col {
    padding-left: 0;
    border-left: 0;
  }

  .nm-hero__grid { align-items: start; }

  /* The carousel slide's photo-beside-text split has the same problem, so the
     photo goes above the text and gets a landscape crop instead of the comp's
     portrait one — a 271x352 portrait at full width is taller than most phone
     viewports. */
  .nm-cap__slide { grid-template-columns: minmax(0, 1fr); }
  .nm-cap__photo { aspect-ratio: 16 / 10; }

  /* The slide has stacked, so there is no text column beside the photograph to
     hang the controls in — they go back into normal flow underneath it. */
  .nm-cap__controls {
    position: static;
    justify-content: center;
    margin-top: clamp(20px, 2.2vw, 34px);
  }
  .nm-cap__copy { padding-top: 0; }

  .nm-stats__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The vertical rules were separating columns that are now rows, so they
     become horizontal rules between the two bands and the left edge is reset on
     whichever card starts a row. */
  .nm-stat { padding-inline: 0; padding-block: clamp(16px, 3vw, 26px); }
  .nm-stat + .nm-stat { border-left: 0; }
  .nm-stat:nth-child(2n) { padding-left: clamp(16px, 3vw, 26px); border-left: 1px solid var(--nm-line); }
  .nm-stat:nth-child(n + 3) { border-top: 1px solid var(--nm-line); }

  .nm-numgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nm-adv { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nm-projects { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nm-kits { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nm-caps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nm-team { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Two across, and the vertical rules become horizontal ones between the two
     rows — the same treatment the stats band gets at this breakpoint, and for
     the same reason: a left border on what is now the start of a row draws a
     rule down the page's gutter. */
  .nm-numgrid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0;
  }
  .nm-numgrid--4 .nm-numcard {
    padding-inline: 0;
    padding-block: clamp(16px, 3vw, 26px);
  }
  .nm-numgrid--4 .nm-numcard + .nm-numcard { border-left: 0; }
  .nm-numgrid--4 .nm-numcard:nth-child(2n) {
    padding-left: clamp(16px, 3vw, 26px);
    border-left: 1px solid var(--nm-line);
  }
  .nm-numgrid--4 .nm-numcard:nth-child(n + 3) { border-top: 1px solid var(--nm-line); }

  /* A 4.5:1 band is a 90px sliver on a tablet and reads as a rendering fault
     rather than a photograph. */
  .nm-band { aspect-ratio: 2 / 1; }

  /* Photograph above its rows on both profiles, so the flip is undone for the
     same reason the spec blocks' is. */
  .nm-format__grid { grid-template-columns: minmax(0, 1fr); }
  .nm-format--flip .nm-format__art { order: 0; }
  .nm-format__photo { aspect-ratio: 16 / 10; }

  /* Specs above their mosaic, and the flip is undone: alternating sides is a
     two-column idea, and keeping `order: -1` here would put the pictures above
     the heading they belong to on every other block. */
  .nm-spec { grid-template-columns: minmax(0, 1fr); }
  .nm-spec--flip .nm-spec__art { order: 0; }

  .nm-stmt2__grid { grid-template-columns: minmax(0, 1fr); }
  .nm-stmt2__title { padding-top: 0; }

  /* The statement body's 59.46% is the node's 880-of-1480 desktop measure. It
     is a proportion of the column, so stacked it keeps shrinking: at 390px it
     leaves the copy in a 200px gutter of its own. Full width below the split. */
  .nm-statement__body { max-width: none; }

  /* Two-and-a-bit tiles visible, so the strip reads as swipeable rather than
     as a row that happens to be cut off. */
  .nm-media4__item { flex: 0 0 clamp(210px, 46%, 320px); }

  /* Five steps across a tablet gives each about 130px, which is two words per
     line. Two rows of three (the fifth alone) is what the comp's own mobile
     rhythm implies for everything else on these pages. */
  .nm-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .nm-step { padding-inline: 0; padding-block: clamp(14px, 2.4vw, 22px); }
  .nm-step + .nm-step { border-left: 0; }
  .nm-step:not(:nth-child(3n + 1)) {
    padding-left: clamp(14px, 2.4vw, 22px);
    border-left: 1px solid var(--nm-line);
  }
  .nm-step:nth-child(n + 4) { border-top: 1px solid var(--nm-line); }

  /* One column of ten. The auto-flow has to go back to `row` as well as the
     rows being released — leaving `column` with a single auto column would
     stack all ten into five rows two deep and overlap them. */
  .nm-locs__cols {
    grid-template-rows: none;
    grid-auto-flow: row;
  }

  /* The row's side padding spaces two columns apart. This is where the list
     becomes one column, so from here down it only indents the rows off the
     page gutter — same call as the service cards. */
  .nm-loc { padding-inline: 0; }

  .nm-direct__grid { grid-template-columns: minmax(0, 1fr); }

  /* Photograph under the write-up on every case, so the flip comes off for the
     same reason as the spec blocks' and the format profiles'. */
  .nm-case__grid { grid-template-columns: minmax(0, 1fr); }
  .nm-case--flip .nm-case__art { order: 0; }
  .nm-case__photo { aspect-ratio: 16 / 10; }

  .nm-foot__cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .nm-foot__logo-col { grid-column: 1 / -1; }
  /* Both halves of the desktop right-alignment have to come off, not just the
     text-align: `justify-self: end` on the column was still shoving the whole
     block — heading, rule and values — against the right edge of a stacked
     footer, which put the CONTACTS rule under nothing. */
  .nm-foot__contacts-col { justify-self: start; }
  .nm-foot__contacts { text-align: left; }
}

@media (max-width: 640px) {
  /* == THE PHONE FRAME IS A LAYOUT, NOT A SET OF PIXEL VALUES ==============
     Everything below is expressed as its size ON THE 390px FRAME multiplied
     by --nm-u, a length that equals exactly 1px when the viewport is 390 and
     scales with it either side. So `calc(44 * var(--nm-u))` reads "the 44px
     the frame draws" and stays 44px-of-a-390-frame on a 360, a 414 or a 430pt
     device.

     THIS IS THE BUG THE EARLIER PASSES HAD. Sizes read off the frame and typed
     in as fixed px only reproduce the frame at one viewport width. At a fixed
     44px the headline broke into three lines on a 390pt phone and two on a
     430pt one, the lede went from four lines to three, and the aside from four
     to five. Every one of those is the SAME css against a 10% wider column,
     which is a different layout — so "it matches the design" was only ever
     true at the one width the screenshots were taken at.

     The band is 332px to 507px of viewport (0.86 to 1.30). Outside it the
     scale holds still rather than running away: ~14.6px body copy is the floor
     on a 320px phone, and a 56px headline is as far as a phone frame's
     proportions are worth carrying once a viewport is tablet-shaped.

     .nm-shell's gutter is already 6.4vw, so the column all of this sits in was
     always proportional. It was only the contents that were not. */
  .nm { --nm-u: clamp(.86px, .2564vw, 1.3px); }

  /* -- Header ------------------------------------------------------------
     The frame draws the bar as badge + burger and nothing else. The CONTACT US
     button goes because the panel the burger opens already carries a Contact
     link, and because at 390 the button plus the badge leave the bar with no
     air between them. It is `display: none` rather than removed from the
     template: above this width the bar is still badge / pill / CTA. */
  .nm-header__cta { display: none; }

  /* The badge steps down with it. Everything that has to agree with the bar's
     height -- both heroes' pull-up -- reads --nm-header-h, which is derived
     from this, so this one line is the whole change. */
  .nm { --nm-logo: calc(46 * var(--nm-u)); }

  /* The chip keeps its dark fill and white bars: the same pill the desktop bar
     uses, only smaller and a little rounder. */
  .nm-burger {
    width: calc(36 * var(--nm-u));
    height: calc(36 * var(--nm-u));
    border-radius: calc(7 * var(--nm-u));
  }
  .nm-burger__bar,
  .nm-burger__bar::before,
  .nm-burger__bar::after { width: calc(16 * var(--nm-u)); }
  .nm-burger__bar::before { top: calc(-5 * var(--nm-u)); }
  .nm-burger__bar::after { top: calc(5 * var(--nm-u)); }

  /* -- Hero --------------------------------------------------------------
     THE FRAME REORDERS THE BLOCK. On the desktop comp the two supporting
     paragraphs are a second column beside the pitch, so the buttons are simply
     the last thing in the left column. Stacked, the frame runs lede ->
     paragraphs -> buttons: the argument finishes before it asks for the tap,
     and the buttons land at the bottom of the first screen where a thumb is.

     `display: contents` is what does it without a second copy of the markup in
     the template. The lead column stops generating a box, its five children
     become grid items in its place, and only then can the buttons take an
     order that puts them past the aside -- which is a sibling of the column,
     not of the buttons, so no amount of ordering reaches across it while the
     column is still a box.

     The row gap goes to 0 in the same breath. Those five children already
     carry their own margin-bottom, and a 28px grid gap between every one of
     them would add a second space to every gap in the block. */
  .nm-hero__lead { display: contents; }
  .nm-hero__grid { row-gap: 0; }
  .nm-hero__grid .nm-btns { order: 1; }

  /* See the note beside .nm-br--phone in section 9: the frames break these
     headlines in different places than the desktop comps do. */
  .nm-br--phone { display: inline; }
  .nm-br--wide { display: none; }

  /* The phone-only backgrounds come on. Same box as every other full-bleed
     background on the site — absolute, inset 0, stretched to the section,
     which is the shape the art was cut to. */
  .nm-bg--phone {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    object-fit: fill;
  }

  /* The gallery project grid has to become a positioning context to hold one.
     Its shell is already above it in the stacking order by virtue of coming
     later in the markup, so only the grid itself needs lifting. */
  .nm-projects-sec {
    position: relative;
    overflow: hidden;
  }
  .nm-projects-sec > .nm-shell { position: relative; z-index: 1; }

  /* THE TYPE SCALE IS THE FRAME'S, NOT THE DESKTOP COMP'S SHRUNK. Measured off
     the frame by the two things an image reports reliably -- where each line
     breaks, and how far a run of text reaches across the column:

       eyebrow   reaches 87% of the column     12px reached 51%    -> 17
       kicker    breaks after the ampersand    17px stayed 1 line  -> 22
       lede      four lines, breaking on "the"                     -> 17
       aside     ~43 characters a line         14px fit ~51        -> 17

     THE VERTICAL RHYTHM IS SOLVED, NOT EYEBALLED. The frame reports cap-top to
     cap-top; a CSS margin runs box-edge to box-edge, and the two differ by
     each face's half-leading plus its ascent-above-cap -- about 7px for Anton
     at this size, 6.7 for Oswald, 6.5 for Outfit, and NEGATIVE half-leading
     for the two condensed faces, whose content box is taller than these line
     heights. Reading gaps off the image and typing them in as margins
     overshoots every time, by an amount that flips sign depending on which
     pair of faces meets at the gap. Each margin below is the frame's measured
     cap-to-cap distance minus those two corrections.

     The three line-heights fall out of the same measurement and are exact: the
     frame steps the headline 48.8px, the kicker 25.4 and the body 24.4,
     against 48.8 / 25.3 / 24.5 rendered. They are ratios, so they need no
     frame unit. */
  .nm-hero__eyebrow {
    font-size: calc(17 * var(--nm-u));
    margin-bottom: calc(27 * var(--nm-u));
  }
  .nm-hero__title {
    font-size: calc(44 * var(--nm-u));
    line-height: 1.11;
    margin-bottom: calc(7 * var(--nm-u));
  }

  /* The kicker is white and medium here, against light-300 grey on the desktop
     comp. On a phone it carries the whole "what this company is" line directly
     under the headline, with no second column of copy beside it to share the
     job, and 300-weight Oswald over a moving video was the thinnest thing on
     the screen. */
  .nm-hero__kicker {
    font-size: calc(22 * var(--nm-u));
    font-weight: 500;
    line-height: 1.15;
    color: #fff;
    margin-bottom: calc(26 * var(--nm-u));
  }

  /* THE LEDE AND THE ASIDE ARE THE SAME TYPE IN THE FRAME, not a step apart.
     They step their lines by an identical 24.4px there, which is what settles
     it -- on the desktop comp they are 16px and 14px on two different
     leadings, and at phone width that reads as two kinds of text rather than
     as one argument continuing into its detail. Only the colour separates
     them, and that is unchanged from the comp. */
  .nm-hero__lede,
  .nm-hero__aside {
    font-size: calc(17 * var(--nm-u));
    line-height: 1.44;
  }
  .nm-hero__lede { margin-bottom: calc(16 * var(--nm-u)); }
  .nm-hero__aside { margin-bottom: calc(31 * var(--nm-u)); }

  /* The dot grid comes back. It is hidden at the 1023px breakpoint above,
     where a stacked hero leaves it sitting squarely behind full-width
     paragraphs; the frame draws it anyway, high and to the right, where it
     clears everything except the eyebrow. Anchored off the top of the page
     rather than off the header, because the frame runs it up level with the
     burger, and roughly square against the wide short block the comp uses.

     The pitch is a frame measurement like everything else, which is also why
     it stops using the desktop's percentages: those are 10 columns by 8 rows
     OF THE BLOCK, so re-proportioning the block would silently re-space the
     dots. */
  .nm-hero__dots {
    display: block;
    top: calc(32 * var(--nm-u));
    right: calc(6 * var(--nm-u));
    width: calc(184 * var(--nm-u));
    height: calc(180 * var(--nm-u));
    background-size: calc(23 * var(--nm-u)) calc(19 * var(--nm-u));
  }

  /* The frame rounds the hero's two buttons and makes the secondary one
     opaque. Both are scoped to the hero rather than to .nm-btn: the site's
     button shape is square by the comps, and the hero is the only frame of the
     phone design seen so far. If the rest of the mobile screens round theirs
     too, this belongs in section 4 instead of here.

     The opacity is not cosmetic. --nm-veil is 7% white, which over a lighting
     rig in motion is a window onto the video rather than a button, and the
     blur behind it made the beams swim under the label. A near-solid dark
     panel is what the frame draws and it is also what makes the label legible
     against every frame of the clip. */
  .nm-hero .nm-btn {
    border-radius: calc(8 * var(--nm-u));
    padding-block: calc(18 * var(--nm-u));
    /* Up from the 13px base. At phone width the two buttons sit alone under
       the lede with the full column to themselves, and the base label -- sized
       for a desktop row that shares its line with nothing -- reads small
       against the 17px lede directly above it. Frame-referenced like every
       other type size in this block so it tracks the same 390 grid. */
    font-size: calc(15 * var(--nm-u));
  }
  .nm-hero .nm-btns { gap: calc(15 * var(--nm-u)); }

  /* The marker points DOWN in the frame, where the desktop comp has it
     pointing right. It is the same border triangle rotated a quarter turn
     rather than a second one -- and it still needs no aria-hidden, because a
     rotated border is exactly as invisible to a screen reader as an unrotated
     one. */
  .nm-hero .nm-btn::after { transform: rotate(90deg); }
  .nm-hero .nm-btn--ghost {
    background: rgba(22, 22, 25, .9);
    border-color: rgba(255, 255, 255, .1);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .nm-hero .nm-btn--ghost:hover { background: rgba(34, 34, 38, .92); }

  /* ── Stats band, phone ──────────────────────────────────────────────────
     Figma node 321:2813 "stats-mobile". THAT FRAME IS 375 WIDE AND --nm-u IS
     REFERENCED TO 390, so every number below is the node's value times
     390/375. The two are the same proportion of their own frame, which is the
     thing that has to hold; the multiplier is only here because the hero's
     values were derived at 390 and one unit for the whole breakpoint is worth
     more than two units each exact to its own frame.

     THE PHONE STACK IS CENTRED AND THE DESKTOP ONE IS NOT. On the desktop node
     each stat is a left-aligned column in a four-across row with vertical
     rules between; stacked, the node centres number, title and body and puts a
     horizontal hairline between the items. So this is not the desktop band
     with its columns collapsed — the alignment flips too. */
  .nm-stats { padding-block: calc(21 * var(--nm-u)) calc(67 * var(--nm-u)); }

  /* The node hangs the watermark off the TOP of the section, not its middle:
     220x220 at (-110, 140) on the frame. The desktop rule centres it
     vertically, which on a section this tall put it a thousand pixels down,
     level with nothing. */
  .nm-stats .nm-watermark {
    top: calc(146 * var(--nm-u));
    left: calc(-114 * var(--nm-u));
    width: calc(229 * var(--nm-u));
    transform: none;
  }

  .nm-stats__grid {
    grid-template-columns: minmax(0, 1fr);
    /* Row spacing is the separator's job below, not the grid's — the node puts
       17px under the body copy, then the rule, then 33px before the next
       number, which a single symmetric gap cannot express. */
    row-gap: 0;
    margin-top: calc(50 * var(--nm-u));
  }

  .nm-stat {
    padding-inline: 0;
    padding-block: 0;
    text-align: center;
  }
  .nm-stat:nth-child(2n) { padding-left: 0; border-left: 0; }
  .nm-stat + .nm-stat {
    border-top: 1px solid rgba(255, 255, 255, .05);
    margin-top: calc(17 * var(--nm-u));
    padding-top: calc(33 * var(--nm-u));
  }

  /* H1 token: Anton 96 / 1.1 / -4%. It was rendering at 46px here, less than
     half the drawn size, because the desktop clamp bottoms out at phone
     widths. */
  .nm-stat__figure {
    font-size: calc(100 * var(--nm-u));
    line-height: 1.1;
    letter-spacing: -.04em;
    margin-bottom: calc(17 * var(--nm-u));
  }

  /* Header/H11 token: Oswald 20 / 1 / 0 — no tracking, where .nm-label carries
     .035em everywhere else. */
  .nm-stat__label {
    font-size: calc(21 * var(--nm-u));
    line-height: 1;
    letter-spacing: 0;
    margin-bottom: calc(17 * var(--nm-u));
  }

  .nm-stat__body {
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
    letter-spacing: -.02em;
    color: rgba(255, 255, 255, .7);
  }

  .nm-stats__cta { margin-top: calc(50 * var(--nm-u)); }

  /* Button/Button token: 18 / -2%, on a 4px radius — a different shape from
     the hero's 8px, and both are the node's. */
  .nm-stats__cta .nm-btn {
    border-radius: calc(4 * var(--nm-u));
    padding-block: calc(21 * var(--nm-u));
    font-size: calc(19 * var(--nm-u));
    letter-spacing: -.02em;
  }

  /* ── Capability carousel, phone ─────────────────────────────────────────
     Figma node 321:2944 "Section - Mobile", the same 375 frame and the same
     390/375 conversion as the stats band above.

     THIS SECTION'S HEADLINE IS THE ONE THAT IS NOT THE TOKEN. Header/H3 is
     Anton 64 and both the stats headline and the why-choose-us headline sit on
     it; this one is drawn at 44 (node 321:2951), left-aligned rather than
     centred, because it shares its screen with a 641px-tall slide and a
     64px headline would push the photograph off the first view. So the token
     stays on .nm-h2 and this is a local override rather than the other way
     round.

     The header block is left-aligned throughout, where the stats band centres
     everything — the slide below it is centred, so the section reads as a
     left-aligned introduction to a centred card. */
  .nm-cap .nm-h2 {
    font-size: calc(46 * var(--nm-u));
    letter-spacing: -.02em;
  }
  .nm-cap__lead-title { margin-block: calc(12 * var(--nm-u)) calc(17 * var(--nm-u)); }

  /* Oswald BOLD at 25 and 70% white, against the light-300 grey the desktop
     comp uses at 16. Scoped to the section because the hero's kicker is a
     different size and weight in its own frame. */
  .nm-cap .nm-kicker {
    font-size: calc(25 * var(--nm-u));
    font-weight: 700;
    line-height: 1;
    letter-spacing: -.02em;
    color: rgba(255, 255, 255, .7);
  }

  .nm-cap { padding-block: calc(33 * var(--nm-u)); }
  .nm-cap__inner { gap: calc(25 * var(--nm-u)); }

  /* THE SLIDE PHOTOGRAPH GOES BACK TO PORTRAIT. The 1023px breakpoint turns it
     landscape (16/10) on the reasoning that the comp's 271x352 portrait at full
     width is taller than most phone viewports — true at full width, but the
     phone frame does not run it at full width. It draws 256 of 343, which is a
     320px-tall portrait card centred in the column, and that fits. */
  .nm-cap__photo,
  .nm-cap__photo--pending {
    width: 74.6%;
    margin-inline: auto;
    aspect-ratio: 256 / 320;
    border-radius: calc(8 * var(--nm-u));
  }

  .nm-cap__slide {
    row-gap: calc(17 * var(--nm-u));
    text-align: center;
  }

  .nm-cap__slide-title {
    font-size: calc(37 * var(--nm-u));
    line-height: 1;
    letter-spacing: -.02em;
    margin-bottom: calc(17 * var(--nm-u));
  }

  .nm-cap__slide-body {
    /* The 44ch cap is a two-column measure; centred at full width it would
       leave the copy narrower than the photograph above it. */
    max-width: none;
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
    letter-spacing: -.02em;
    margin-bottom: calc(17 * var(--nm-u));
    color: rgba(255, 255, 255, .7);
  }

  /* The node's ghost button is GLASS, not the hero's solid panel — 10% white
     over a 12px blur with a 20% border. The hero's went opaque because it sits
     over a moving lighting rig; this one sits over a still, soft gradient with
     nothing in it to read through the blur.

     It also stops being full width. .nm-btn goes to width:100% at this
     breakpoint for the stacked hero pair; here the node draws a centred pill
     that hugs its label down to a 187px floor. */
  .nm-cap .nm-btn {
    width: auto;
    min-width: calc(187 * var(--nm-u));
    padding: calc(17 * var(--nm-u)) calc(25 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }

  /* 69px of air under the button before the controls — measured off the node,
     where the content block runs to 191px and the control row starts at 257. */
  .nm-cap__controls {
    justify-content: center;
    gap: calc(13 * var(--nm-u));
    margin-top: calc(69 * var(--nm-u));
  }
  .nm-cap__arrow {
    width: calc(50 * var(--nm-u));
    height: calc(50 * var(--nm-u));
  }
  .nm-cap__arrow svg {
    width: calc(17 * var(--nm-u));
    height: calc(17 * var(--nm-u));
  }

  /* ── Why-choose-us, phone ───────────────────────────────────────────────
     Figma node 321:2977 "why-choose-us-mobile", 375 frame, same 390/375
     conversion as the sections above.

     The shape is the desktop node's stacked, with two things resized rather
     than just reflowed: the photograph goes from a 2:1 letterbox to 1.675, and
     the claim titles go from Oswald REGULAR 24 to Oswald BOLD 20 — the phone
     frame leans on weight where the desktop leans on size. */
  .nm-why { padding-block: calc(50 * var(--nm-u)); }

  /* The grid's one remaining gap is the image-to-claims one. */
  .nm-why__grid { gap: calc(33 * var(--nm-u)); }

  .nm-why__title { margin-block: calc(17 * var(--nm-u)); }

  /* THIS HEADLINE IS SIZED TO ITS OWN LINE LENGTH, WHICH IS WHY IT IS A PAGE
     HOOK AND NOT A TOKEN. The frames do not agree on one size for this
     component: lighting s (5) draws "WHY PRODUCTION / TEAMS TRUST OUR /
     LIGHTING" at a 34px cap, led-walls s (7) draws "WHY EVENT PRODUCERS /
     CHOOSE OUR LED WALLS" at 26 — both filling the same column, because the
     second has five more characters a line to fit into it. Each frame sizes
     the type to the words.

     Matched on INK WIDTH rather than cap height: s (7) runs its second line to
     246 of a 271 column, 91%, and 37 puts Anton at 90% of ours. Cap height
     cannot be matched as well as width here — Anton is wider per cap than the
     comps' face, so a cap-for-cap match would push the line 30px past the
     gutter and break the two-line set the frame is built on. The vw cap is the
     usual guard for below 332, where --nm-u stops shrinking. */
  .nm--led .nm-why__title { font-size: min(calc(37 * var(--nm-u)), 9.6vw); }

  /* Lighting s (5), the same component sized to its own words. "TEAMS TRUST
     OUR" is 6.41x its font size and the token broke the headline into four
     lines (five at 300) against the frame's three. The frame runs that line to
     250 of a 268 column, 93%; 12.4vw holds ours at 91%.

     NO BREAKS NEEDED WITH IT. At this size "WHY PRODUCTION TEAMS" (8.84x) and
     "TEAMS TRUST OUR LIGHTING" (9.73x) both overrun the column by a wide
     margin, so the only wrap available is the frame's own. */
  .nm--lighting .nm-why__title { font-size: min(calc(67 * var(--nm-u)), 12.4vw); }

  /* STYLE/Paragraph at 19 (the frame's 18). */
  .nm-why__body { font-size: calc(19 * var(--nm-u)); }

  .nm-why__photo {
    margin-top: calc(33 * var(--nm-u));
    aspect-ratio: 335 / 200;            /* node 321:2984 */
  }

  /* THE CHECKMARK IS 24 WIDE HERE, NOT 32 — and the difference is inside the
     asset, not the layout. The frame draws a 32px icon box, but its SVG carries
     4px of padding on every side, so the mark itself is 24 (node 321:2988: a
     32x32 viewBox with the path running x=4 to x=28). This repo's check.svg is
     the DESKTOP export, whose ink fills its box edge to edge — the two files
     are otherwise the same path, and the desktop node ships exactly this file,
     which is why 48 is right up there and 32 would be a third too big down
     here. So the mark is set to the frame's 24 and the column gap absorbs the
     padding the asset does not have: 12 + 4 = 16. */
  .nm-why__check {
    width: calc(25 * var(--nm-u));
    align-self: center;
  }

  .nm-why__item {
    gap: calc(12 * var(--nm-u)) calc(17 * var(--nm-u));
  }

  /* Oswald BOLD 20 / 1.25, against Regular 24 on the desktop node. */
  .nm-why__item-title {
    font-size: calc(21 * var(--nm-u));
    font-weight: 700;
    line-height: 1.25;
  }

  .nm-why__item-body { font-size: calc(17 * var(--nm-u)); }

  /* 12 above the rule, 25 below it — the frame's own asymmetry again, and a
     tighter pair than the desktop's 12/32. */
  .nm-why__item + .nm-why__item {
    margin-top: calc(12 * var(--nm-u));
    padding-top: calc(25 * var(--nm-u));
  }

  /* ── Closing CTA band, phone ────────────────────────────────────────────
     Figma node 321:3025 "cta-section-mobile", 375 frame, 390/375 conversion.

     IT CENTRES, AND IT IS THE ONE SECTION THAT DOES NOT USE THE PHONE TYPE
     TOKENS. Every other band on this breakpoint takes Header/H3 at 64 and
     Label/C-md at 18 — this one draws its headline at 38 and its eyebrow at
     14, on .19em where the rest run .1em. That is not a token miss: the band
     is 526px tall in a frame where the stats band is 1527, and a 64px headline
     over a photograph would leave no room for the two buttons that are the
     whole point of it. So both are local overrides, the same way the carousel
     headline is on the desktop side.

     The split layout collapses to one centred column. .nm-cta--center exists
     for the destination page and does exactly this, but the homepage band is
     the split variant, so the centring is applied at the breakpoint rather
     than by swapping the modifier in the template. */
  .nm-cta { padding-block: calc(21 * var(--nm-u)); }

  /* THE CENTRED BAND IS TALLER THAN ITS PADDING WAS LETTING IT BE. 21 is the
     split band's number, read off the homepage frame, where a 512-tall stack
     of headline, lede, two buttons and a phone line fills the band on its own.
     The centred variant carries less — no aside, no phone line — so the same
     21 left its content sitting against the top and bottom edges of a
     full-bleed photograph instead of floating in it.

     Corporate s (10) measures the band at 429px of a 312-wide frame, which is
     536 at 390, against the 436 this was drawing. The content inside it is
     already right (386 in the frame, 394 here); the whole 100px difference was
     air. Split evenly that is 50 a side on top of the 21. */
  .nm-cta--center { padding-block: calc(71 * var(--nm-u)); }

  .nm-cta__grid {
    justify-items: center;
    text-align: center;
    gap: calc(25 * var(--nm-u));
  }

  .nm-cta .nm-eyebrow {
    font-size: calc(15 * var(--nm-u));
    letter-spacing: .19em;
  }

  /* .nm-cta .nm-h2, not .nm-cta__title, for the size. The phone block sets
     .nm-h2 to the 67px Header/H3 further down the file, and against a
     single-class selector that later rule wins on source order — the headline
     came out at 67 and the band was a screen and a half tall. Two classes puts
     it beyond reach, which is the same shape .nm-cap .nm-h2 uses above. */
  .nm-cta .nm-h2 {
    font-size: calc(40 * var(--nm-u));
    line-height: 1.1;
    letter-spacing: -.02em;
  }
  .nm-cta__title { margin-block: calc(25 * var(--nm-u)); }

  .nm-cta__body {
    /* The desktop node's 64.76% measure is a two-column idea; centred at full
       width it would sit narrower than the buttons under it. */
    max-width: none;
    margin-inline: auto;
    font-size: calc(17 * var(--nm-u));
    color: rgba(255, 255, 255, .8);
  }

  /* The node runs 24 either side of the button pair and 12 between the two
     buttons. A single grid gap cannot say both, so the gap is the inner 12 and
     the lede and phone line make up the difference. */
  .nm-cta__aside { gap: calc(12 * var(--nm-u)); }
  .nm-cta__aside-lede {
    margin-bottom: calc(13 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.4;
    color: rgba(255, 255, 255, .8);
  }
  .nm-cta__phone {
    margin-top: calc(13 * var(--nm-u));
    font-size: calc(15 * var(--nm-u));
    line-height: 1.4;
    color: rgba(255, 255, 255, .7);
  }

  .nm-cta .nm-btn {
    padding: calc(17 * var(--nm-u)) calc(33 * var(--nm-u));
    border-radius: calc(6 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }

  /* THE PHONE FRAMES SET THEIR BUTTON LABELS BOLD, where the desktop nodes use
     the medium weight .nm-btn carries. Three of them agree — the stats CTA
     (321:2841), the carousel's LEARN MORE (321:2960) and both buttons here
     (321:3036, 321:3040) — so it is the phone treatment rather than one
     section's quirk. The hero's pair is deliberately not in this list: its
     button node has not been read, and guessing a weight onto it is how the
     rest of this file went wrong the first time. */
  .nm-stats__cta .nm-btn,
  .nm-cap .nm-btn,
  .nm-cta .nm-btn { font-weight: 700; }

  /* ── Inner-page hero, phone ─────────────────────────────────────────────
     Figma node 321:6151, 375 frame, the same 390/375 conversion as the rest of
     this breakpoint.

     THE ORDER CHANGES, not just the widths. The desktop grid is [copy +
     buttons] beside [photograph + paragraphs]; the phone frame runs eyebrow,
     headline, kicker, photograph, paragraphs, and only then the buttons. The
     buttons live in the first column, so `display: contents` dissolves that
     column and lets them take an order past the art column — the same move the
     homepage hero makes, and for the same reason: no ordering reaches across a
     sibling while the column is still a box.

     The paragraphs also centre here, against left on the desktop node. */
  .nm-phero {
    padding-top: calc(var(--nm-header-h) + 48 * var(--nm-u));
    padding-bottom: calc(50 * var(--nm-u));
  }

  .nm-phero__lead { display: contents; }
  .nm-phero__grid { row-gap: 0; }
  .nm-phero__grid .nm-btns { order: 1; }

  /* Label/C-sm here, not the C-md the rest of the phone sections use: 16 on 5%
     tracking against 18 on 10%. The eyebrow rule at the foot of this block is
     the site-wide phone value; this is the inner hero's own. */
  .nm-phero .nm-eyebrow {
    font-size: calc(17 * var(--nm-u));
    letter-spacing: .05em;
    margin-bottom: calc(29 * var(--nm-u));
  }

  .nm-phero__title {
    font-size: calc(44 * var(--nm-u));
    line-height: 1.1;
    margin-bottom: calc(17 * var(--nm-u));
  }

  /* Oswald 20 at 85% — a size down and a shade brighter than the desktop
     node's 32 at 70%. */
  .nm-phero__grid .nm-kicker {
    font-size: calc(21 * var(--nm-u));
    line-height: 1.2;
    color: rgba(255, 255, 255, .85);
    margin-bottom: calc(29 * var(--nm-u));
  }

  .nm-phero__col { gap: calc(17 * var(--nm-u)); }

  .nm-phero__photo {
    aspect-ratio: 330 / 182;           /* node 321:6159 */
    border-radius: calc(2 * var(--nm-u));
  }

  .nm-phero__aside {
    font-size: calc(17 * var(--nm-u));
    color: rgba(255, 255, 255, .8);
    text-align: center;
    margin-bottom: calc(29 * var(--nm-u));
  }
  .nm-phero__aside p + p { margin-top: calc(17 * var(--nm-u)); }

  .nm-phero__grid .nm-btns { gap: calc(12 * var(--nm-u)); }
  .nm-phero__grid .nm-btn {
    min-width: 0;
    padding: calc(17 * var(--nm-u)) calc(24 * var(--nm-u));
    border-radius: calc(6 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }

  .nm-numgrid,
  .nm-adv,
  .nm-projects,
  .nm-kits,
  .nm-caps,
  .nm-team { grid-template-columns: minmax(0, 1fr); }

  /* The card's side padding exists to put air between two cards' copy. Stacked
     one per row there is no neighbour, so it stops being spacing and becomes a
     20px indent that pushes this section's text off the gutter every other
     section on the page lines up to. The vertical half stays — that is the gap
     between stacked cards. */
  .nm-numgrid:not(.nm-numgrid--4) .nm-numcard { padding-inline: 0; }

  .nm-numgrid--4 { grid-template-columns: minmax(0, 1fr); }
  .nm-numgrid--4 .nm-numcard:nth-child(2n) { padding-left: 0; border-left: 0; }
  .nm-numgrid--4 .nm-numcard + .nm-numcard { border-top: 1px solid var(--nm-line); }

  .nm-band { aspect-ratio: 3 / 2; }

  /* The filter row scrolls sideways rather than wrapping into four ragged rows
     that push the grid off the first screen. */
  .nm-filters {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scrollbar-width: none;
    /* Bleeds to the viewport edges so the last pill is not clipped mid-word by
       the shell's padding — the padding is re-added inside the scroller. */
    margin-inline: calc(-1 * var(--nm-gutter));
    padding-inline: var(--nm-gutter);
  }
  .nm-filters::-webkit-scrollbar { display: none; }
  .nm-filters__pill { flex: 0 0 auto; }

  /* One step per row on a phone: three across is back to two words a line. */
  .nm-steps { grid-template-columns: minmax(0, 1fr); }
  .nm-step:not(:nth-child(3n + 1)) { padding-left: 0; border-left: 0; }
  .nm-step + .nm-step { border-top: 1px solid var(--nm-line); }

  .nm-media4__item { flex: 0 0 78%; }

  /* One column of pairs: two columns of a label plus a full sentence at 320px
     is three words a line. */
  .nm-spec__pairs { grid-template-columns: minmax(0, 1fr); }

  /* Header/H3 token: Anton 64 / 1 / -2% on the 375 frame. This is a token, not
     a one-off — the stats headline (321:2819) and the why-choose-us headline
     (321:2982, a 335x320 box that is five lines at exactly this size) both sit
     on it, so it belongs on .nm-h2 rather than in one section. The type
     scale's own lower bound was handing out 29px, which is why every phone
     section heading has been reading as body copy rather than as a headline.
     Long headings run to five or six lines at this size; that is what the
     frames draw. */
  .nm-h2 { font-size: calc(67 * var(--nm-u)); }

  /* Label/C-md token: 18 / 10% tracking. The eyebrow is 12px at .19em on
     desktop; the phone frame runs it half again bigger on half the tracking. */
  .nm-eyebrow {
    font-size: calc(19 * var(--nm-u));
    letter-spacing: .1em;
  }
  .nm-head .nm-eyebrow { margin-bottom: calc(17 * var(--nm-u)); }

  .nm-btns { flex-direction: column; align-items: stretch; }
  .nm-btn { width: 100%; }

  /* One field per row. Two columns at 320px gives each field about 140px,
     which is too narrow to read "e.g. Madison Square Garden, NY" as a hint or
     to type a date comfortably. */
  .nm-form__grid { grid-template-columns: minmax(0, 1fr); }

  /* The meta row's fixed label column does not fit beside a value on a phone,
     so the value drops under the label — same shape as the location rows. */
  .nm-case__row {
    grid-template-columns: auto minmax(0, 1fr);
    row-gap: 4px;
  }
  .nm-case__row dd { grid-column: 2; }

  /* The reCAPTCHA iframe is a fixed 302px and does not shrink, so below about
     340px of usable card width it overflows. The whole widget is scaled down
     instead. transform-origin is load-bearing: the default centre origin
     leaves a gap at the left once it shrinks. */
  .nm-form__captcha {
    justify-content: flex-start;
    transform: scale(.86);
    transform-origin: left top;
  }

  /* The location row's fixed name column does not fit beside a description on a
     phone, so the description drops to its own line under the pinned name.
     The side padding goes with it, for the same reason as the service cards:
     one row per line has no neighbour to be spaced from, so it would only
     indent the list off the page gutter. */
  .nm-loc {
    grid-template-columns: auto minmax(0, 1fr);
    row-gap: 4px;
  }
  .nm-loc__body { grid-column: 2; }

  /* ── Footer, phone ──────────────────────────────────────────────────────
     Figma node 321:3443, 375 frame, same 390/375 conversion.

     EVERYTHING CENTRES. The desktop footer is three columns — badge left,
     links middle, contacts hard right with its values right-aligned — and the
     1023px breakpoint above already unpicks the right-alignment. The phone
     frame goes further: one column, every block centred on the page's axis,
     including the short rule under each heading and the legal row that the
     desktop draws as space-between.

     It is also a size up almost everywhere. The desktop footer is the smallest
     type on the site (13-17px); the frame runs its headings at 24 and every
     link and value at 16, which on a phone is the difference between a footer
     you can tap and one you cannot. */
  .nm-foot { padding-top: calc(25 * var(--nm-u)); }

  .nm-foot__cols {
    grid-template-columns: minmax(0, 1fr);
    gap: calc(25 * var(--nm-u));
    justify-items: center;
    text-align: center;
  }
  /* The tablet breakpoint sets justify-self: start on this column to undo the
     desktop's right-hang, and a child's justify-self always beats the parent's
     justify-items — so without this the contacts heading and its rule stayed
     hard left while everything around them centred. */
  .nm-foot__contacts-col { justify-self: stretch; }

  .nm-foot__logo { width: calc(96 * var(--nm-u)); }

  /* Anton 24/1.15 with the rule centred under it. The rule is a
     pseudo-element, so centring it is margin-inline rather than the parent's
     text-align — an ::after block box does not follow text alignment. */
  .nm-foot__title {
    font-size: calc(25 * var(--nm-u));
    line-height: 1.15;
  }
  .nm-foot__title::after {
    width: calc(24 * var(--nm-u));
    height: 1px;
    margin-top: calc(8 * var(--nm-u));
    margin-inline: auto;
  }

  /* Label/C-sm: DM Sans Medium 16 on 5% tracking, at 70% until it is the page
     you are on. The desktop's 300-weight 14px is a third lighter and two
     sizes down. */
  .nm-foot__links {
    justify-content: center;
    gap: calc(12 * var(--nm-u)) calc(17 * var(--nm-u));
    margin-top: calc(17 * var(--nm-u));
  }
  .nm-foot__link {
    font-size: calc(17 * var(--nm-u));
    font-weight: 500;
    color: rgba(255, 255, 255, .7);
  }

  /* Body/SM: DM Sans Regular 16 / 1.5 / -2%. */
  .nm-foot__contacts {
    margin-top: calc(17 * var(--nm-u));
    text-align: center;
  }
  .nm-foot__contact {
    margin-bottom: calc(12 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
    letter-spacing: -.02em;
    color: rgba(255, 255, 255, .7);
  }

  /* The legal row stacks and centres, and its two links stack under the
     copyright rather than sitting beside each other — the frame puts Privacy
     Policy and Terms of Service on their own lines. */
  .nm-foot__legal {
    flex-direction: column;
    align-items: center;
    gap: calc(12 * var(--nm-u));
    margin-top: calc(25 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    font-weight: 500;
    letter-spacing: .05em;
    text-align: center;
    color: rgba(255, 255, 255, .7);
  }
  .nm-foot__legal-links {
    flex-direction: column;
    align-items: center;
    gap: calc(8 * var(--nm-u));
  }

  /* The frame crops about 38% of the letterforms against the desktop's ~26%,
     and the margin is a percentage of the footer's WIDTH (see the note in
     section 6), so the number is not comparable between breakpoints — this is
     the phone's own measurement, not the desktop's scaled. */
  .nm-foot__wordmark {
    margin-top: calc(25 * var(--nm-u));
    margin-bottom: -7%;
  }

  /* == THE REST OF THE PAGES, AT THE FRAME =================================
     Everything above this line is the homepage's frames plus the inner hero
     and the footer. Everything below is the other twelve pages' — the same
     --nm-u frame unit, read off mobile-design/<page>/s (n).png the same way.

     ONE THING RUNS THROUGH ALL OF IT. Every component below was reaching this
     breakpoint on the LOWER BOUND OF ITS DESKTOP CLAMP — a 14px body, a 15px
     card heading, a 30px numeral — because a clamp's floor is a guard against
     a narrow column, not a phone design. The frames draw a phone type scale a
     third to three times bigger, and it is consistent across the twelve pages:

       body copy and list items          17 / 1.5
       card and row headings (Oswald)    23, or 25 where it heads a group
       small meta labels                 14-15
       an Anton block title              40
       a service numeral                 50
       a values numeral                  100  (the stat-figure token)

     So the sizes below repeat because the frames repeat them, not because one
     value was pasted around. Where a frame departs from its neighbours the
     departure is noted at the rule.

     WHAT IS DELIBERATELY NOT COPIED. The frames disagree with each other about
     two things, and following each one exactly would mean hard-coding a
     section's identity into the stylesheet:

       * SECTION-HEAD ALIGNMENT. Ten frames centre `section_head`, four
         left-align it (destination s (3), audio s (2), gallery s (2),
         contact s (5)) — including two on the SAME page as a centred one
         (audio s (2) against s (6)). Centred is both the majority and the
         desktop composition, so it stays.
       * SECTION-HEAD SIZE. The dominant measure is 67, drawn that way on
         eighteen frames across nine pages, which is the .nm-h2 value already
         set above. Six frames run 60 and one runs 40. Same call.

     The one departure that IS copied is the destination page's statement,
     which is drawn at twice the size the other four statement pages use —
     see the note there. */

  /* ── Statement block, phone ─────────────────────────────────────────────
     The centred claim with the monogram over it: about, gallery, inventory
     and contact all draw the headline at 34 on 1.1, against the 67 .nm-h2
     hands it. At 67 a five-line claim is a screen and a half of headline
     before the page has said anything — inventory s (2) fits the same
     sentence in three lines and still has the logo strip and the next section
     head on the same screen. */
  .nm-statement { padding-block: calc(46 * var(--nm-u)); }

  .nm-statement__mark {
    width: calc(32 * var(--nm-u));
    margin-bottom: calc(38 * var(--nm-u));
  }

  .nm-statement__title {
    font-size: calc(34 * var(--nm-u));
    line-height: 1.1;
    margin-bottom: calc(25 * var(--nm-u));
  }

  .nm-statement__body {
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }

  .nm-statement__quote {
    margin-top: calc(25 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }

  /* THE DESTINATION PAGE'S IS THE H2 SIZE, and it is the one per-page override
     in this block. Its frame (s (2)) draws the same component at 67 — six
     lines of it — where the other four statement pages draw 35. That is not a
     2px reading difference to average away: it is the difference between a
     claim that opens the page and one that sits under the fold. Scoped to the
     body class rather than fixed in the component, because four pages against
     one is what makes 35 the component's value and this the exception. */
  .nm--dest .nm-statement__title {
    font-size: calc(67 * var(--nm-u));
    line-height: 1;
  }

  /* ── Split statement, phone ─────────────────────────────────────────────
     The other statement shape — heading in one column, argument in the other.
     Stacked it CENTRES ITS HEADING and keeps the argument left: six of the
     seven frames that draw it put the headline on the page's axis (audio,
     lighting, led-walls, wedding, staging, event-type) and every one of them
     leaves the paragraphs and the pull quote ranged left. Corporate s (2) is
     the one that keeps the heading left, and it is outnumbered.

     49 rather than the section-head 67, and that one IS consistent: all seven
     frames agree on it within a pixel. */
  .nm-stmt2__grid { row-gap: calc(25 * var(--nm-u)); }

  .nm-stmt2__title {
    padding-top: 0;
    font-size: calc(49 * var(--nm-u));
    line-height: 1.06;
    text-align: center;
  }

  /* The seventh frame. Corporate s (2) ranges "BUILT ON / EXPERIENCE / THAT
     SHOWS" left against the same gutter its paragraphs start from, so the
     caller asks for it (`align="left-phone"`) instead of the majority above
     deciding for every page. See the macro note in _shared.html. */
  .nm-stmt2--left-phone .nm-stmt2__title { text-align: left; }

  /* "FIXTURE IN OUR RIG." IS THE LONGEST SECOND LINE ANY OF THESE SEVEN
     HEADLINES CARRIES — 6.78x its font size, where the 49 token assumes
     something shorter. With the phone break in place (see lighting.html) the
     token ran it past the gutter below 340 and the words fell to three lines
     with "RIG." alone on the last.

     11.9vw is the frame's own proportion: s (2) runs that line to 256 of a 271
     column, 94%, and this holds it at 93% of ours at every phone width. A page
     hook, not a token — the other six statement headlines fit the 49 as drawn,
     and this one is long, not mis-sized. */
  .nm--lighting .nm-stmt2__title { font-size: min(calc(49 * var(--nm-u)), 11.9vw); }

  /* Audio s (2), the same shape and the same cause: "FULL PROFESSIONAL" is
     7.0x its font size, the 49 token runs it to 343 in a 340 column, and the
     headline fell to three lines below 360 with "FULL" alone on the first. The
     frame sets that line at 97% of its column; 11.5vw holds ours at 92%, which
     leaves the descender-free caps a little air rather than pressing them into
     the gutter. */
  .nm--audio .nm-stmt2__title,
  .nm--staging .nm-stmt2__title { font-size: min(calc(49 * var(--nm-u)), 11.5vw); }
  /* Staging s (2) is the audio headline with one word swapped, and it measures
     as one: "FULL PROFESSIONAL" and "STAGING INVENTORY" are both 7.0x, the
     frame draws both lines to 259 of a 276 column, and at the token the phone
     version fell to FOUR lines below 360 — one word apiece. The two pages
     share the cap because they are the same headline, not because one was
     copied onto the other. */

  /* THE SAME PROBLEM ONE SECTION DOWN. "LIGHTING TEAM" is 5.23x its font size
     and the 67 token runs it to 350 in a 340 column, so the pair split and the
     head fell to four lines — WHAT OUR / LIGHTING / TEAM / DELIVERS — where
     s (2) draws three. 14.7vw is that frame's proportion: it sets its second
     line to 240 of a 271 column, 89%, and this holds ours at 88%. The three
     lines then fall out of the measure rather than being forced with breaks:
     at this size neither "WHAT OUR LIGHTING" (6.86x) nor "LIGHTING TEAM
     DELIVERS" (8.64x) comes close to fitting, so the words can only break
     where the frame breaks them. */
  /* "PROFESSIONAL" IS THE WIDEST SINGLE WORD ON THE SITE'S SECTION HEADS —
     5.15x its font size, where "MANAGEMENT" is 5.03 — and it appears twice on
     this page, over "audio" and over "lighting". At the 67 token it measured
     289 against a 276 column at 320, so .nm-h1/.nm-h2's overflow-wrap did what
     it is there for and split the word: PROFESSIONA / L.

     16vw is the frames' own proportion (s (5) and s (9) run the word to 264 of
     a 272 column, 97%; this holds ours at 94-96%) and it is applied to the
     page rather than to the two heads that overflow, so all five section heads
     here stay the same size as each other. */
  .nm--inventory .nm-h2 { font-size: min(calc(67 * var(--nm-u)), 16vw); }

  /* THREE FRAMES, ONE SIZE. Lighting s (2), audio s (2) and staging s (2) all
     draw the services head at a 40px cap on a ~310 frame — the same size, with
     the line lengths differing only because the words do ("LIGHTING TEAM"
     fills 89% of the column, "STAGING TEAM" 84%, "AUDIO TEAM" 72%). So they
     share the cap rather than carrying a number each. It is still a page hook
     and not a token: destination s (3) and corporate s (4) draw their own
     services heads at the full 67. */
  .nm--lighting .nm-head--left-phone .nm-h2,
  .nm--audio .nm-head--left-phone .nm-h2,
  .nm--staging .nm-head--left-phone .nm-h2 { font-size: min(calc(67 * var(--nm-u)), 14.7vw); }

  .nm-stmt2__body { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }
  .nm-stmt2__body p + p { margin-top: calc(17 * var(--nm-u)); }

  .nm-stmt2__quote {
    margin-top: calc(25 * var(--nm-u));
    padding-left: calc(12 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }

  /* ── Four-up media strip, phone ─────────────────────────────────────────
     ONE TILE PER SCREEN WITH A DOT ROW UNDER IT, which is a different object
     from the peeking scroller the tablet breakpoint leaves behind. The frame
     (corporate s (3), wedding s (3)) draws a single square picture filling the
     column and a row of dots beneath — so the strip stops reading as a row
     that happens to be cut off and starts reading as the carousel it is.

     The dots are built by norman.js and the row is absent without it, so the
     strip degrades to exactly what it is underneath: a swipeable scroller. */
  .nm-media4 {
    gap: calc(12 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-media4__item { flex: 0 0 100%; }

  /* The desktop tile is a 316x350 portrait, four to a row. At one to a row the
     frame squares it off — a full-column portrait would be 430px tall on a
     390 phone and push the dots off the screen. */
  .nm-media4__media { aspect-ratio: 1 / 1; }

  /* ── Numbered service grid, phone ───────────────────────────────────────
     THE CARD GETS ITS BORDER BACK. On desktop these are padded boxes with a
     transparent fill and no stroke — the padding alone spaces two cards' copy
     apart, which is all a row of three needs. Stacked one per row there is no
     neighbour, so the padding came off at this breakpoint and the cards became
     six undifferentiated stacks. Every frame that draws them (audio s (3),
     destination s (4), lighting, led-walls, staging, wedding) draws a hairline
     box with 26 of padding instead, on the 8px radius the desktop card already
     carries. So the padding comes back and the stroke comes with it. */
  .nm-numgrid {
    gap: calc(20 * var(--nm-u));
    margin-top: calc(33 * var(--nm-u));
  }
  .nm-numgrid:not(.nm-numgrid--4) .nm-numcard {
    padding: calc(26 * var(--nm-u));
    border: 1px solid var(--nm-line-soft);
  }

  .nm-numcard__num {
    font-size: calc(50 * var(--nm-u));
    margin-bottom: calc(20 * var(--nm-u));
  }
  .nm-numcard__title {
    font-size: calc(21 * var(--nm-u));
    line-height: 1.25;
    margin-bottom: calc(17 * var(--nm-u));
  }
  .nm-numcard__body { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }

  /* ── Values grid, phone ─────────────────────────────────────────────────
     The about page's four numbered values. Same card, but the numeral is the
     STAT FIGURE, not the service numeral: about s (3) and s (4) draw it at
     100, the size the homepage's "500+" runs at, which is why this is the one
     place the two variants of .nm-numcard part company on more than a column
     count. The hairline between cards is the frame's too. */
  .nm-numgrid--4 .nm-numcard { padding-block: calc(33 * var(--nm-u)); }
  .nm-numgrid--4 .nm-numcard:first-child { padding-top: 0; }
  .nm-numgrid--4 .nm-numcard__num {
    font-size: calc(100 * var(--nm-u));
    margin-bottom: calc(24 * var(--nm-u));
  }

  /* ── Advantage cards, phone ─────────────────────────────────────────────
     destination s (8)/s (9): the icon runs 40, not the 32 the desktop clamp
     bottoms out at, and whitespace still does the separating — no rules, same
     as the desktop row. */
  .nm-adv {
    gap: calc(33 * var(--nm-u));
    margin-top: calc(33 * var(--nm-u));
  }
  .nm-adv__icon {
    width: calc(40 * var(--nm-u));
    margin-bottom: calc(17 * var(--nm-u));
  }
  .nm-adv__title {
    font-size: calc(21 * var(--nm-u));
    margin-bottom: calc(14 * var(--nm-u));
  }
  .nm-adv__body { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }

  /* ── Location list, phone ───────────────────────────────────────────────
     The rows already drop their description under the pinned name higher up
     in this block; this is the type scale that goes with it. */
  .nm-locs__lede { font-size: calc(17 * var(--nm-u)); }

  /* "DESTINATIONS WE OPERATE IN": the same width guard as .nm-services below,
     for the same reason — see the long note there. This head is already left
     off the gutter (it is a bare eyebrow + h2 in the shell, not a .nm-head),
     so only the size is at issue.

     THE CAP IS NOT THE SAME NUMBER because it is derived, not copied: it is
     the widest LINE's ink width divided into the 87.2vw column, and the lines
     here are set by the <br> in the template rather than by where the words
     happen to fall. Measured against Anton at this tracking:

       "MANAGEMENT"     5.03x its font size   (the .nm-services line)
       "DESTINATIONS"   4.87x
       "WE OPERATE IN"  5.27x   <- this head's widest line

     So 15.3vw, which holds that line about 20px clear of the gutter at 320 —
     the width where --nm-u has clamped and the token alone was running
     "DESTINATIONS" to exactly the column with nothing to spare. It is a ninth
     smaller than the token, which is what a second word on the second line
     costs; the alternative break ("DESTINATIONS WE" / "OPERATE IN") is 6.19x
     and would cost a quarter. */
  .nm-locs__title { font-size: min(calc(67 * var(--nm-u)), 15.3vw); }
  .nm-locs__cols {
    gap: calc(14 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-loc {
    column-gap: calc(14 * var(--nm-u));
    padding-block: calc(15 * var(--nm-u));
  }
  .nm-loc__pin { width: calc(26 * var(--nm-u)); }
  .nm-loc__name { font-size: calc(26 * var(--nm-u)); }
  .nm-loc__body { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }

  /* ── Process steps, phone ───────────────────────────────────────────────
     THE STEP TURNS ON ITS SIDE. Higher up in this block the five columns
     become five rows, which leaves the disc on its own line above the heading
     with the page's full width beside it doing nothing. Every frame that draws
     the steps (audio s (7)/s (8), lighting, staging, corporate, wedding,
     event-type, contact s (5)) puts the disc in its own column with the
     heading beside it and the paragraph under the heading — so the disc spans
     both rows of a two-column grid and the text keeps one measure.

     The hairlines go. They divide five columns on desktop; between stacked
     rows the frames draw nothing but space. */
  .nm-steps {
    grid-template-columns: minmax(0, 1fr);
    row-gap: calc(28 * var(--nm-u));
    margin-top: calc(33 * var(--nm-u));
  }
  .nm-step {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: calc(22 * var(--nm-u));
    padding: 0;
  }
  .nm-step + .nm-step { border-top: 0; }
  .nm-step:not(:nth-child(3n + 1)) { padding-left: 0; border-left: 0; }

  .nm-step__num {
    grid-row: span 2;
    align-self: start;
    width: calc(40 * var(--nm-u));
    height: calc(40 * var(--nm-u));
    margin-bottom: 0;
    font-size: calc(15 * var(--nm-u));
  }
  /* Oswald regular at 21 rather than .nm-label's medium on .035em tracking —
     the same step the why-choose-us claims and the service cards make at this
     breakpoint. */
  .nm-step__title {
    align-self: center;
    margin-bottom: calc(9 * var(--nm-u));
    font-size: calc(21 * var(--nm-u));
    font-weight: 400;
    line-height: 1.25;
    letter-spacing: 0;
  }
  .nm-step__body { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }

  /* ── Filter pills, phone ────────────────────────────────────────────────
     THEY WRAP, THEY DO NOT SCROLL. An earlier pass made this row a horizontal
     scroller on the reasoning that eight pills would wrap into four ragged
     rows and push the grid off the first screen. Gallery s (2) draws exactly
     those four rows and keeps them — every category is visible without a
     gesture, which on a filter row is the point of it, and a scroller also
     hides that there are more categories at all.

     So the bleed, the nowrap and the overflow all come off, and the pills grow
     to the frame's tap size. */
  .nm-filters {
    flex-wrap: wrap;
    justify-content: flex-start;
    overflow-x: visible;
    margin-inline: 0;
    padding-inline: 0;
    gap: calc(12 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-filters__pill {
    padding: calc(9 * var(--nm-u)) calc(19 * var(--nm-u));
    font-size: calc(15 * var(--nm-u));
  }
  /* The rule after "ALL EVENTS" divides a single row. Wrapped, it is a 1px
     mark floating in the middle of the first line, and the frame has none. */
  .nm-filters__sep { display: none; }

  /* ── Project cards, phone ───────────────────────────────────────────────
     gallery s (3)-s (5) and the our-works grid: the desktop card with the
     frame's type scale and a slightly wider crop. */
  .nm-projects {
    gap: calc(20 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-proj {
    padding: calc(17 * var(--nm-u));
    border-radius: calc(8 * var(--nm-u));
  }
  .nm-proj__media { aspect-ratio: 3 / 2; }
  .nm-proj__cat {
    margin-top: calc(17 * var(--nm-u));
    padding: calc(7 * var(--nm-u)) calc(15 * var(--nm-u));
    font-size: calc(13 * var(--nm-u));
  }
  .nm-proj__title {
    margin-top: calc(14 * var(--nm-u));
    font-size: calc(21 * var(--nm-u));
  }
  .nm-proj__meta {
    margin-top: calc(6 * var(--nm-u));
    font-size: calc(16 * var(--nm-u));
  }
  .nm-proj__body {
    margin-top: calc(14 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }
  .nm-projects__empty { font-size: calc(17 * var(--nm-u)); }

  /* ── Spec blocks, phone ─────────────────────────────────────────────────
     The LED-walls kit sections. Stacked, the block is a 40px Anton title over
     its rule and then one column of pairs — led-walls s (6)/s (7) run the
     title at 40 against the 19 the desktop clamp bottoms out at, which is the
     difference between a section title and a paragraph in bold. The pair
     labels drop .nm-label's tracking for the same reason the step titles do:
     at 21 the letterspacing that makes a 13px label legible reads as a gap. */
  .nm-specs { gap: calc(46 * var(--nm-u)); margin-top: calc(30 * var(--nm-u)); }
  .nm-spec { row-gap: calc(25 * var(--nm-u)); }

  .nm-spec__title { font-size: calc(40 * var(--nm-u)); }
  .nm-spec__title::after { margin-top: calc(17 * var(--nm-u)); }

  .nm-spec__pairs {
    gap: calc(22 * var(--nm-u));
    margin-top: calc(22 * var(--nm-u));
  }
  .nm-spec__pairs dt {
    font-size: calc(21 * var(--nm-u));
    font-weight: 400;
    letter-spacing: 0;
  }
  .nm-spec__pairs dd {
    margin-top: calc(6 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }

  /* ── Format profile, phone ──────────────────────────────────────────────
     event-type s (4)/s (6). The labelled rows keep their hairlines — unlike
     the process steps, these are a table of five comparable answers and the
     rules are what make them scan as one. The label runs 25, a size up from
     everything else at this breakpoint, because it is the only thing marking
     where one answer ends and the next begins. */
  .nm-format__grid {
    row-gap: calc(25 * var(--nm-u));
    margin-top: calc(25 * var(--nm-u));
  }
  .nm-format__photo { aspect-ratio: 16 / 11; }
  .nm-format__rows dt {
    font-size: calc(21 * var(--nm-u));
    letter-spacing: 0;
  }
  .nm-format__rows dd {
    margin-top: calc(10 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }
  .nm-format__row { padding-block: calc(20 * var(--nm-u)); }

  /* ── Kit categories, phone ──────────────────────────────────────────────
     The inventory page's twenty categories. inventory s (4)/s (5) put a
     HAIRLINE UNDER EACH CATEGORY HEAD that the desktop four-column layout
     does not have — with four columns the whitespace between them separates
     the categories, and stacked there is nothing left doing that job. */
  .nm-kits {
    gap: calc(33 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-kit__head {
    gap: calc(18 * var(--nm-u));
    margin-bottom: calc(17 * var(--nm-u));
    padding-bottom: calc(14 * var(--nm-u));
    border-bottom: 1px solid var(--nm-line-soft);
  }

  /* THE em NEVER REACHED THE HEADING IT WAS MEANT TO TRACK. .nm-kit__icon is
     1.15em, and the note beside it says that is so the icon follows the title
     next to it — but the two are siblings, so the em resolves against
     .nm-kit__head's inherited 16px, not the title's size. Every category icon
     on this page was drawing at 18.4 whatever the heading did.

     The frames draw it at 23 on a 307-wide frame (s (4)), 29 against the 390
     --nm-u is referenced to, which is also where the other phone icons sit:
     the location pin is 26, the why-list check 25, the assurance tick 21. So
     the size is stated here rather than inherited by accident. */
  .nm-kit__icon {
    width: calc(29 * var(--nm-u));
    height: calc(29 * var(--nm-u));
  }
  .nm-kit__title {
    font-size: calc(20 * var(--nm-u));
    letter-spacing: .02em;
  }
  .nm-kit__items dt {
    font-size: calc(19 * var(--nm-u));
    font-weight: 400;
    line-height: 1.3;
  }
  .nm-kit__items dd {
    margin-top: calc(6 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }
  .nm-kit__item + .nm-kit__item { margin-top: calc(20 * var(--nm-u)); }

  /* ── Photo band, phone ──────────────────────────────────────────────────
     IT STOPS BLEEDING. On desktop this is a full-width photograph between two
     kit sections, and the bleed is what makes it a break rather than another
     card. inventory s (3) and s (5) inset it to the page gutter and round it,
     which is the same photograph doing the same job in a column where a
     full-bleed strip would be the only thing on the page touching the screen
     edges. The 3:2 the earlier pass set is also too tall here: the frames draw
     roughly 12:7.

     The width is a calc rather than `auto` plus margins, because the band is
     an <img> carrying intrinsic width/height attributes on the pages where a
     photograph exists: `width: auto` there resolves to the file's own 1341,
     which max-width:100% then caps at the CONTAINING BLOCK's width — so the
     picture came out full-width with the gutter added outside it, and the
     inventory page gained 25px of horizontal scroll. */
  .nm-band {
    width: calc(100% - 2 * var(--nm-gutter));
    margin-inline: auto;
    aspect-ratio: 12 / 7;
    border-radius: calc(8 * var(--nm-u));
  }
  .nm-band--pending { border-radius: calc(8 * var(--nm-u)); }

  /* ── Capability lists, phone ────────────────────────────────────────────
     about s (4)/s (5). Three groups of ticked items, one under the other, with
     a rule between groups — the same reasoning as the kit heads: three columns
     are separated by the space between them, and stacked they are not. */
  .nm-caps {
    gap: calc(33 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-caps > section + section {
    padding-top: calc(33 * var(--nm-u));
    border-top: 1px solid var(--nm-line-soft);
  }
  .nm-caps__title {
    margin-bottom: calc(17 * var(--nm-u));
    font-size: calc(21 * var(--nm-u));
  }
  .nm-caps__item {
    padding-left: calc(26 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }
  .nm-caps__item + .nm-caps__item { margin-top: calc(13 * var(--nm-u)); }
  /* The chevron grows with the line it marks; at 17px copy the desktop's
     3.5x5 triangle is a speck. */
  .nm-caps__item::before {
    left: calc(3 * var(--nm-u));
    border-width: calc(5 * var(--nm-u)) 0 calc(5 * var(--nm-u)) calc(7 * var(--nm-u));
  }

  /* ── Team cards, phone ──────────────────────────────────────────────────
     about s (6)/s (7). The portrait goes SQUARE — the desktop crop is a
     253x176 landscape, three to a row, and the frame draws a 231x231 square
     filling the card. The name is the biggest type in the card by a long way,
     which is what makes a stack of three read as three people rather than as
     three paragraphs. */
  .nm-team {
    gap: calc(20 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-team-card {
    padding: calc(24 * var(--nm-u));
    border-radius: calc(8 * var(--nm-u));
  }
  .nm-team-card__photo { aspect-ratio: 1 / 1; }
  .nm-team-card__name {
    margin-top: calc(20 * var(--nm-u));
    font-size: calc(32 * var(--nm-u));
  }
  .nm-team-card__role {
    margin-top: calc(6 * var(--nm-u));
    font-size: calc(20 * var(--nm-u));
  }
  .nm-team-card__bio {
    margin-top: calc(14 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }

  /* ── Enquiry form, phone ────────────────────────────────────────────────
     contact s (2)-s (4). The card loses the desktop's 44px inset — at 390 that
     is a quarter of the column spent on padding — and every control grows to
     the frame's 55px tap target.

     NOTHING HERE MAY GO BELOW 16px. iOS Safari zooms the page when a field
     smaller than that takes focus, and the zoom does not come back; the frame
     draws 17, so this is not a compromise, but it is the floor. */
  .nm-form-sec__head { margin-bottom: calc(28 * var(--nm-u)); }
  .nm-form-card {
    padding: calc(20 * var(--nm-u));
    border-radius: calc(10 * var(--nm-u));
  }
  .nm-form__grid { gap: calc(20 * var(--nm-u)); }
  .nm-form__label {
    margin-bottom: calc(10 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }
  .nm-form__input,
  .nm-form__select,
  .nm-form__area {
    padding: calc(16 * var(--nm-u));
    border-radius: calc(8 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
  }
  .nm-form__select {
    padding-right: calc(40 * var(--nm-u));
    background-position: right calc(16 * var(--nm-u)) center;
  }
  .nm-form__area { min-height: calc(150 * var(--nm-u)); }
  .nm-form__actions { margin-top: calc(24 * var(--nm-u)); }
  .nm-form__consent {
    margin-top: calc(18 * var(--nm-u));
    font-size: calc(14 * var(--nm-u));
  }

  /* ── Direct-contact rows, phone ─────────────────────────────────────────
     contact s (4)/s (5). Same shape as the desktop rows — icon in its own
     column spanning the three text lines — at the frame's scale. */
  .nm-direct__row {
    column-gap: calc(16 * var(--nm-u));
    padding-block: calc(22 * var(--nm-u));
  }
  .nm-direct__icon {
    width: calc(30 * var(--nm-u));
    margin-top: calc(3 * var(--nm-u));
  }
  .nm-direct__value {
    margin-top: calc(8 * var(--nm-u));
    font-size: calc(17 * var(--nm-u));
    line-height: 1.5;
  }
  .nm-direct__note {
    margin-top: calc(8 * var(--nm-u));
    font-size: calc(16 * var(--nm-u));
    line-height: 1.5;
  }

  /* ── Case study, phone ──────────────────────────────────────────────────
     our-works s (3)-s (6). The meta rows already drop their value under the
     label higher up in this block; this is the scale, plus the one thing the
     frame changes about the write-up itself:

     THE BLOCK LABELS ARE SET AS HEADINGS, NOT AS BOLD BODY. "THE BRIEF:",
     "WHAT WE DELIVERED:", "OUTCOME:" are drawn in condensed caps in every
     frame, where the desktop card runs them as 13px semibold Outfit. At the
     desktop's size and beside a photograph the distinction barely registers;
     stacked, three prose blocks with no visual break between them run
     together, and the caps are what separates them. */
  /* THE PHOTOGRAPH LEADS EVERY CASE. On desktop it sits beside the write-up
     and alternates sides; the 1023px breakpoint stacks it and undoes the
     alternation by putting it under the text on every case, so that the
     picture never lands above the heading it belongs to. The phone frames
     (our-works s (2), s (4), s (5), s (6)) put it ABOVE the eyebrow on all
     three — the picture is what identifies the project, and a reader scrolling
     past three long write-ups needs it at the top of each. So the order is
     reversed here rather than merely un-alternated, and both cases have to be
     named: the flip rule from 1023 is still in force otherwise. */
  .nm-case__art,
  .nm-case--flip .nm-case__art { order: -1; }

  .nm-case__grid { row-gap: calc(25 * var(--nm-u)); }

  /* 50, not the 67 .nm-h2 hands it. Measured off s (5), where "[ORGANISATION
     NAME] ANNUAL AWARDS GALA" is three lines; at 67 it is five, and the meta
     rows that identify the project drop off the screen under it. */
  .nm-case__title {
    font-size: calc(50 * var(--nm-u));
    line-height: 1.02;
    margin-block: calc(14 * var(--nm-u)) calc(22 * var(--nm-u));
  }
  .nm-case__meta { margin-bottom: calc(22 * var(--nm-u)); }

  .nm-case__row {
    column-gap: calc(12 * var(--nm-u));
    padding-block: calc(14 * var(--nm-u));
  }
  .nm-case__icon { width: calc(20 * var(--nm-u)); }
  .nm-case__row dt { font-size: calc(15 * var(--nm-u)); }
  .nm-case__row dd { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }

  .nm-case__block + .nm-case__block { margin-top: calc(20 * var(--nm-u)); }
  .nm-case__block-title {
    margin-bottom: calc(8 * var(--nm-u));
    font-family: var(--nm-font-cond);
    font-size: calc(21 * var(--nm-u));
    font-weight: 500;
    letter-spacing: .02em;
    text-transform: uppercase;
  }
  .nm-case__block-body { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }
  .nm-case__cta { margin-top: calc(24 * var(--nm-u)); }
  .nm-case__photo { aspect-ratio: 7 / 5; }

  /* ── Sub-block headings, phone ──────────────────────────────────────────
     A HEADING THAT OPENS A SECTION IS 67; A HEADING INSIDE ONE IS NOT. The
     eighteen `section_head` frames agree on 67 and that is what .nm-h2 sets
     above, but three headings on these pages are not section heads — they sit
     inside a section that already has one, or head a block within a page, and
     the frames draw them at roughly 40:

       gallery s (2)    "Selected projects from our portfolio"   39
       contact s (6)    "Prefer to speak directly?"              40
       event-type s (4) the format profile's own headline        47

     They had no class of their own until now, which is why they were taking
     .nm-h2 whole and running to four and five lines here. The one bare .nm-h2
     that is NOT in this list is the contact page's "Send us your event brief"
     (s (2)), which its frame draws at the full 67 — it opens its section the
     way a section head does, so it keeps the token and needs no hook. */
  .nm-projects__title { font-size: calc(39 * var(--nm-u)); }
  .nm-direct__title { font-size: calc(40 * var(--nm-u)); }
  .nm-format__title { font-size: calc(47 * var(--nm-u)); }

  /* ── Assurance bar, phone ───────────────────────────────────────────────
     ONE CLAIM AT A TIME, SWIPEABLE. Every frame that includes this band
     (homepage s (7), about s (8), audio s (8), gallery s (6), inventory
     s (13)) draws a single centred claim at ~21px, not the five wrapped 13px
     ones the tablet breakpoint leaves behind — a claim that reads as a
     guarantee rather than as small print.

     Five of them at that size come to about 1400px, so a band that draws one
     has to be able to reach the other four somehow. IT SCROLLS RATHER THAN
     AUTO-PLAYING. A marquee was tried on this component once and removed as a
     misreading of the desktop comp (see section 7); reintroducing one here
     would put the same moving band back on the page, and a claim you have to
     wait for is worse than one you can swipe to. Snapping one item per view
     means a screenshot of this band shows exactly what the frames show, and
     the remaining four are a thumb away.

     Nothing here is load-bearing for the content: all five claims are in the
     markup and in the accessibility tree either way. */
  .nm-assure { padding-inline: 0; }

  /* `flex-start` IS LOAD-BEARING, not tidying. The desktop row is
     `space-between`, and a flex container whose content overflows under any
     alignment but flex-start spills off the START edge as well as the end —
     where scrolling cannot reach it. With five full-width items that put the
     band's resting position on "24/7 Support" with the first two claims
     stranded off-screen to the left, which is neither the frames' first item
     nor anything a swipe could recover. */
  .nm-assure__row {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
    gap: 0;
  }
  .nm-assure__row::-webkit-scrollbar { display: none; }

  .nm-assure__item {
    flex: 0 0 100%;
    justify-content: center;
    scroll-snap-align: start;
    gap: calc(9 * var(--nm-u));
    padding-block: calc(9 * var(--nm-u));
    font-size: calc(21 * var(--nm-u));
  }
  .nm-assure__icon { width: calc(21 * var(--nm-u)); }

  /* ── Section heads and shared type, phone ───────────────────────────────
     The lede under a section head is the same 17 as every other paragraph at
     this breakpoint. */
  .nm-head__lede { font-size: calc(17 * var(--nm-u)); line-height: 1.5; }

  /* A head that is centred on desktop and left on a phone. Only the frames ask
     for this — see the macro note in _shared.html for why it is per caller and
     not a blanket flip of .nm-head at this width. The lede loses its auto
     margins for the same reason .nm-head--left's does. */
  .nm-head--left-phone { text-align: left; }
  .nm-head--left-phone .nm-head__lede { margin-inline: 0; }

  /* "FULL DESTINATION MANAGEMENT SERVICES" WAS BREAKING MID-WORD. The 67 token
     above is a proportion of the viewport only while --nm-u is (332px to
     507px); below 332 the unit clamps at .86px and the headline stops shrinking
     while .nm-shell's 6.4vw gutters keep taking width off the column. At 320
     that leaves "MANAGEMENT" about 11px wider than the line it sits on, and
     .nm-h1/.nm-h2's overflow-wrap last resort does exactly what it is there to
     do: it splits the word rather than the page. The word is the problem, not
     the wrap.

     So this headline is capped as a share of the viewport instead. 16.2vw is
     the comp's own proportion — destination s (3) draws the line at ~62.5 on a
     375 frame, 16.7vw — and it holds the longest word about 20px clear of the
     gutter at every phone width, 320 included. `min()` keeps the 67 token in
     charge at the wide end of the breakpoint, where it is the smaller of the
     two and the frame's measured size. Scoped to this section because it is
     the one head on the site with two eleven-letter words in it; the token
     stays untouched for the other seventeen frames. */
  .nm-services .nm-h2 { font-size: min(calc(67 * var(--nm-u)), 16.2vw); }

  /* THE CENTRED CTA BAND'S BODY WAS 161px WIDE. .nm-cta__body is released to
     full width further up this block, but `.nm-cta--center .nm-cta__body`
     (section 13) carries a 47.43% cap on two classes, so it outranked the
     single-class release and every page using the centred variant — audio,
     lighting, staging, led-walls, event-type, wedding, corporate, gallery,
     our-works — ran its closing paragraph in a 47%-wide ribbon down the middle
     of the screen. The percentage is a proportion of a 1920 band; there is no
     width to take a proportion of here. */
  .nm-cta--center .nm-cta__body { max-width: none; }
}


/* ──────────────────────────────────────────────
   34. Landing pages — corporate-events-2, led-walls-2, wedding-events-2
   ──────────────────────────────────────────────
   Three quote pages built from new-design/*-design-for-*.jpg, which share one
   layout and one type scale, so everything here is under `.nm-lp` (the body
   class) and the per-page differences are custom properties. The components
   are in templates/_lp.html.

   THE COMPS ARE 1920 WIDE AND MEASURED IN THEIR OWN PIXELS. --lp is a length
   that is exactly 1px at a 1920 viewport and shrinks with it, so
   calc(64 * var(--lp)) is "the 64px the comp draws". Below about 1150 it
   stops shrinking (.6px) and the tablet rules take over. Type also has a px
   floor, because a proportion of a 1280 laptop is 67% of the comp and a 12px
   label is not legible on black.

   The phone frames (390 wide) use the site's own --nm-u for the same reason
   section 99 does; see the note at the top of its 640 block.

   A DIFFERENT BODY FACE, ON PURPOSE. These comps set their copy, labels and
   buttons in DM Sans, where the fourteen site pages use Outfit; the display
   faces (Anton, Oswald) are the site's. DM Sans is loaded by the three
   templates only. */
.nm-lp {
  --lp: clamp(.6px, .05208vw, 1px);
  --lp-g: calc(70 * var(--lp));
  --lp-font: "DM Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  --lp-line: rgba(255, 255, 255, .1);
  --lp-dim: rgba(255, 255, 255, .72);
  --lp-hero-size: 96;               /* the hero headline, px at 1920 */
  --lp-rev-show: 3;                 /* reviews in view; read by norman.js */
  font-family: var(--lp-font);
}
.nm-lp--led { --lp-hero-size: 128; }
/* The LED comp's headline is a third bigger and the comp tightens the space
   around it instead of letting the line box grow: 36px eyebrow-to-cap and 32px
   cap-to-button, against 48 and 44 on the other two. */
.nm-lp--led .nm-lp-hero .nm-lp-eyebrow { margin-bottom: calc(23 * var(--lp)); }
.nm-lp--led .nm-lp-hero__row { margin-top: calc(22 * var(--lp)); }
/* The LED and wedding comps hang the form heading's rule and line lower than
   the corporate one does: 25px below the cap line and 31 under the rule,
   against 19 and 25. Measured on all six forms. */
.nm-lp--led .nm-lp-rule,
.nm-lp--wed .nm-lp-rule { margin-top: calc(20 * var(--lp)); }
.nm-lp--led .nm-lp-quote__sub,
.nm-lp--wed .nm-lp-quote__sub { margin-top: calc(24 * var(--lp)); }

.nm-lp-shell {
  max-width: calc(1780px + 2 * var(--lp-g));
  margin-inline: auto;
  padding-inline: var(--lp-g);
}
/* The why-us and reviews blocks sit in a narrower column (220-1700 on the
   comp) than everything else (70-1850). */
.nm-lp-narrow { padding-inline: calc(150 * var(--lp)); }

/* ── Type ──────────────────────────────────────────────────────────────── */
.nm-lp-h2 {
  font-family: var(--nm-font-display);
  font-size: max(30px, calc(62 * var(--lp)));
  font-weight: 400;
  line-height: 1;
  letter-spacing: -.005em;
  text-transform: uppercase;
}

/* The site resets every <p> to a 1em bottom margin (section 2); every gap on
   these pages is set on the element that owns it instead. */
.nm-lp :where(p) { margin-bottom: 0; }

/* Running copy is the comps' "Paragraph" style, DM Sans on -2% — measured,
   every paragraph on the three comps is 4-5% narrower than DM Sans at 0.
   Labels and buttons are not; they are set at 0. */
.nm-lph__ask,
.nm-lp-hero__sub,
.nm-lp-lede,
.nm-lp-quote__sub,
.nm-lp-form__trust,
.nm-lp-card__body,
.nm-lp-adv__body,
.nm-lp-rev__text,
.nm-lp-rev__by,
.nm-lp-cta__body { letter-spacing: -.02em; }

.nm-lp-lede {
  margin-top: calc(22 * var(--lp));
  font-size: max(16px, calc(20 * var(--lp)));
  line-height: 1.45;
  color: var(--lp-dim);
}

/* "| SIZED, BUILT AND RUN FOR YOU". The hero's version is smaller, with the
   brand ramp for its bar where the section eyebrows have plain white. */
.nm-lp-eyebrow {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--lp));
  margin-bottom: calc(18 * var(--lp));
  font-size: max(13px, calc(16 * var(--lp)));
  line-height: 1.3;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #fff;
}
.nm-lp-eyebrow::before {
  content: "";
  flex: 0 0 auto;
  width: 2px;
  height: max(18px, calc(24 * var(--lp)));
  background: #fff;
}
.nm-lp-eyebrow--grad {
  gap: calc(13 * var(--lp));
  font-size: max(12px, calc(14 * var(--lp)));
}
.nm-lp-eyebrow--grad::before {
  width: 3px;
  height: max(14px, calc(18 * var(--lp)));
  background: var(--nm-grad);
}

/* The short rule under a form heading. */
.nm-lp-rule {
  display: block;
  width: calc(64 * var(--lp));
  height: 3px;
  margin-top: calc(14 * var(--lp));
  background: linear-gradient(90deg, #3f6ff0, #6a46e6);
}

/* ── Buttons ───────────────────────────────────────────────────────────── */
.nm-lpbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: calc(14 * var(--lp));
  min-height: max(46px, calc(53 * var(--lp)));
  padding: 0 max(20px, calc(27 * var(--lp)));
  border: 1px solid #fff;
  border-radius: 3px;
  font: 400 max(14px, calc(18 * var(--lp)))/1 var(--lp-font);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .18s ease, color .18s ease;
}
.nm-lpbtn__icon { flex: 0 0 auto; width: max(16px, calc(21 * var(--lp))); height: auto; }
.nm-lpbtn--solid { background: #fff; color: #000; }
.nm-lpbtn--solid:hover { background: #dcdee3; }
.nm-lpbtn--line { background: transparent; color: #fff; }
.nm-lpbtn--line:hover { background: rgba(255, 255, 255, .1); }

/* ── 1. Header ─────────────────────────────────────────────────────────── */
.nm-lph {
  position: sticky;
  top: 0;
  z-index: 50;
  background: #000;
  border-bottom: 1px solid var(--lp-line);
}
.nm-lph__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  max-width: calc(1814px + 106 * var(--lp));
  margin-inline: auto;
  padding: calc(16 * var(--lp)) calc(53 * var(--lp));
}
.nm-lph__logo { width: max(60px, calc(92 * var(--lp))); height: auto; }
.nm-lph__end { display: flex; align-items: center; gap: calc(38 * var(--lp)); }
.nm-lph__ask { margin: 0; font-size: max(14px, calc(18 * var(--lp))); }
.nm-lph__ask a:hover { text-decoration: underline; }
.nm-lph__call { display: none; }

/* An anchor jump to a form must not land under the sticky bar. */
.nm-lp-quote { scroll-margin-top: max(76px, calc(124 * var(--lp))); }

/* ── 2. Hero ───────────────────────────────────────────────────────────── */
.nm-lp-hero {
  padding-block: calc(48 * var(--lp));
  border-bottom: 1px solid var(--lp-line);
}
.nm-lp-hero .nm-lp-eyebrow { margin-bottom: calc(37 * var(--lp)); }
.nm-lp-hero__title {
  font-family: var(--nm-font-display);
  font-size: max(40px, calc(var(--lp-hero-size) * var(--lp)));
  font-weight: 400;
  line-height: 1;
  /* The comps' hero lines are 8% narrower than Anton at the same cap height
     on all three pages, which is a constant tracking, not a size. */
  letter-spacing: -.035em;
  text-transform: uppercase;
}
.nm-lp-hero__row {
  display: flex;
  align-items: center;
  gap: calc(32 * var(--lp));
  margin-top: calc(36 * var(--lp));
}
.nm-lp-hero__sub {
  margin: 0;
  font-size: max(16px, calc(20 * var(--lp)));
  line-height: 1.4;
}
/* The photograph runs into the black: the bottom of the frame is a fade, not
   an edge. */
.nm-lp-hero__media {
  position: relative;
  margin-top: calc(33 * var(--lp));
}
.nm-lp-hero__media::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 42%;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0), #000 92%);
  pointer-events: none;
}
.nm-lp-hero__img {
  width: 100%;
  height: calc(480 * var(--lp));
  object-fit: cover;
}

/* ── 3 / 10. Quote form ────────────────────────────────────────────────── */
.nm-lp-quote {
  padding-block: calc(56 * var(--lp));
  border-bottom: 1px solid var(--lp-line);
}
.nm-lp-quote__sub {
  margin-top: calc(18 * var(--lp));
  font-size: max(15px, calc(18 * var(--lp)));
  line-height: 1.5;
  color: var(--lp-dim);
}

.nm-lp-form__fields {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: calc(16 * var(--lp));
  margin-top: calc(34 * var(--lp));
}
.nm-lp-form__label {
  display: block;
  margin-bottom: calc(13 * var(--lp));
  font-size: max(14px, calc(16 * var(--lp)));
  line-height: 1.2;
}
.nm-lp-form__wrap { position: relative; }
.nm-lp-form__input {
  display: block;
  width: 100%;
  height: max(46px, calc(53 * var(--lp)));
  padding: 0 max(12px, calc(16 * var(--lp)));
  background: #0b0b0b;
  border: 1px solid rgba(255, 255, 255, .13);
  border-radius: 3px;
  color: #fff;
  font: 400 max(15px, calc(17 * var(--lp)))/1.2 var(--lp-font);
  color-scheme: dark;
  -webkit-appearance: none;
  appearance: none;
}
.nm-lp-form__input:focus { outline: none; border-color: rgba(255, 255, 255, .55); }
.nm-lp-form__input:focus-visible { outline: 2px solid var(--nm-blue); outline-offset: 1px; }
.nm-lp-form__select { padding-right: max(40px, calc(48 * var(--lp))); cursor: pointer; }
.nm-lp-form__select option { background: #111; color: #fff; }
.nm-lp-form__icon {
  position: absolute;
  top: 50%;
  right: max(12px, calc(16 * var(--lp)));
  width: max(16px, calc(20 * var(--lp)));
  height: auto;
  transform: translateY(-50%);
  color: rgba(255, 255, 255, .75);
  pointer-events: none;
}
/* The date field draws its own calendar mark, so the browser's is stretched
   invisibly over the whole box (a press anywhere opens the picker) and the
   mm/dd/yyyy mask stays hidden until there is a date or the field has focus,
   so the empty field looks like the comp's. norman.js keeps data-empty. */
.nm-lp-form__date { position: relative; cursor: pointer; }
.nm-lp-form__date::-webkit-calendar-picker-indicator {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  opacity: 0;
  cursor: pointer;
}
.nm-lp-form__date[data-empty]:not(:focus)::-webkit-datetime-edit { color: transparent; }

.nm-lp-form__captcha {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  margin-top: calc(28 * var(--lp));
}
.nm-lp-form__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(28 * var(--lp));
  margin-top: calc(33 * var(--lp));
}
.nm-lp-form__trust {
  margin: 0;
  font-size: max(14px, calc(16 * var(--lp)));
  line-height: 1.45;
  color: var(--lp-dim);
}
.nm-lp-form button[disabled] { opacity: .6; cursor: progress; }

/* layout="split": heading left, the fields two-up in an 854px column. */
.nm-lp-quote--split .nm-lp-quote__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) calc(854 * var(--lp));
  column-gap: calc(70 * var(--lp));
  align-items: start;
}
.nm-lp-quote--split .nm-lp-form__fields {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: calc(17 * var(--lp)) calc(24 * var(--lp));
  margin-top: 0;
}

/* ── 4. Why trust us ───────────────────────────────────────────────────── */
.nm-lp-why { padding-block: calc(93 * var(--lp)) calc(100 * var(--lp)); }
.nm-lp-why__grid {
  display: grid;
  grid-template-columns: calc(704 * var(--lp)) minmax(0, 1fr);
  column-gap: calc(72 * var(--lp));
  align-items: start;
}
.nm-lp-why__img {
  width: 100%;
  height: auto;
  margin-top: calc(48 * var(--lp));
  aspect-ratio: 704 / 419;
  object-fit: cover;
  border-radius: 8px;
}
.nm-lp-checks__item {
  display: flex;
  align-items: center;
  gap: calc(24 * var(--lp));
  padding-block: calc(14 * var(--lp));
  border-bottom: 1px solid var(--lp-line);
  font-family: var(--nm-font-cond);
  font-size: max(17px, calc(24 * var(--lp)));
  line-height: 1.25;
  text-transform: uppercase;
}
.nm-lp-checks__item:first-child { padding-top: 0; }
.nm-lp-checks__item:last-child { border-bottom: 0; }
.nm-lp-checks__mark { flex: 0 0 auto; width: max(32px, calc(48 * var(--lp))); height: auto; }
.nm-lp-why__cta { margin-top: calc(17 * var(--lp)); }

/* The comps let the longest claim run past the column's rules rather than
   wrap it ("ONE CONTRACT COVERS STAGE, AV, ..." overhangs by ~20px), so on a
   desktop the lines hold; the narrow-padding column leaves room for it. */
@media (min-width: 1151px) {
  .nm-lp-checks__item span { white-space: nowrap; }
}

/* ── 5. Services ───────────────────────────────────────────────────────── */
.nm-lp-svc { padding-block: calc(92 * var(--lp)) calc(96 * var(--lp)); }
.nm-lp-svc .nm-lp-eyebrow { margin-bottom: calc(22 * var(--lp)); }
.nm-lp-svc__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: calc(32 * var(--lp)) calc(48 * var(--lp));
  margin-top: calc(50 * var(--lp));
}
.nm-lp-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding-bottom: calc(24 * var(--lp));
  border-bottom: 1px solid var(--lp-line);
}
.nm-lp-card__img {
  width: 100%;
  height: auto;
  aspect-ratio: 561 / 250;
  object-fit: cover;
  border-radius: 8px;
}
/* Two lines' height whether or not the title needs them, so the copy and the
   buttons line up across a row the way the comp draws them. */
.nm-lp-card__title {
  min-height: 2.5em;
  margin-top: calc(23 * var(--lp));
  font-family: var(--nm-font-cond);
  font-size: max(18px, calc(24 * var(--lp)));
  line-height: 1.25;
  text-transform: uppercase;
}
.nm-lp-card__body {
  flex: 1 0 auto;
  margin-top: calc(22 * var(--lp));
  font-size: max(15px, calc(18 * var(--lp)));
  line-height: 1.5;
  color: var(--lp-dim);
}
.nm-lp-card .nm-lpbtn { margin-top: calc(22 * var(--lp)); }

/* ── 6. Advantages ─────────────────────────────────────────────────────── */
.nm-lp-adv {
  position: relative;
  overflow: hidden;
  padding-block: calc(92 * var(--lp)) calc(100 * var(--lp));
}
.nm-lp-adv__glow {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(38% 46% at 58% 72%, rgba(122, 38, 214, .42), transparent 70%),
    radial-gradient(30% 40% at 36% 34%, rgba(30, 40, 120, .32), transparent 72%),
    radial-gradient(22% 60% at 86% 60%, rgba(118, 30, 190, .28), transparent 70%);
  pointer-events: none;
}
.nm-lp-adv > .nm-lp-shell { position: relative; }
.nm-lp-adv__pics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: calc(16 * var(--lp));
  margin-top: calc(50 * var(--lp));
}
.nm-lp-adv__img {
  width: 100%;
  height: auto;
  aspect-ratio: 882 / 349;
  object-fit: cover;
  border-radius: 8px;
}
.nm-lp-adv__list {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: calc(32 * var(--lp));
  margin-top: calc(66 * var(--lp));
}
.nm-lp-adv__item {
  padding-bottom: calc(14 * var(--lp));
  border-bottom: 1px solid var(--lp-line);
}
.nm-lp-adv__title {
  display: flex;
  align-items: center;
  gap: calc(22 * var(--lp));
  font-family: var(--nm-font-display);
  font-size: max(17px, calc(24 * var(--lp)));
  line-height: 1.2;
  text-transform: uppercase;
}
.nm-lp-adv__mark { flex: 0 0 auto; width: max(22px, calc(32 * var(--lp))); height: auto; }
.nm-lp-adv__body {
  margin-top: calc(14 * var(--lp));
  font-size: max(15px, calc(18 * var(--lp)));
  line-height: 1.5;
  color: var(--lp-dim);
}
.nm-lp-adv__cta { margin-top: calc(45 * var(--lp)); }

/* ── 7. Results gallery ────────────────────────────────────────────────── */
.nm-lp-gal {
  padding-block: calc(92 * var(--lp)) calc(96 * var(--lp));
  border-bottom: 1px solid var(--lp-line);
}
.nm-lp-gal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.nm-lp-gal__grid {
  display: grid;
  gap: calc(16 * var(--lp));
  margin-top: calc(40 * var(--lp));
}
.nm-lp-gal__tile { min-width: 0; }
.nm-lp-gal__img,
.nm-lp-gal__tile .nm-lp-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 6px;
}
/* grid: four across, two rows (corporate). */
.nm-lp-gal--grid .nm-lp-gal__grid {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: calc(298 * var(--lp));
}
/* lead2: two wide over four (LED), on an eight-track grid because the comp's
   1100/656 split is not a multiple of its four-up row. */
.nm-lp-gal--lead2 .nm-lp-gal__grid {
  grid-template-columns: repeat(8, minmax(0, 1fr));
  grid-template-rows: calc(500 * var(--lp)) calc(250 * var(--lp));
  gap: calc(24 * var(--lp));
}
.nm-lp-gal--lead2 .nm-lp-gal__tile { grid-column: span 2; }
.nm-lp-gal--lead2 .nm-lp-gal__tile:nth-child(1) { grid-column: span 5; }
.nm-lp-gal--lead2 .nm-lp-gal__tile:nth-child(2) { grid-column: span 3; }
/* lead1: one full width over three (wedding). */
.nm-lp-gal--lead1 .nm-lp-gal__grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: calc(720 * var(--lp)) calc(300 * var(--lp));
  gap: calc(24 * var(--lp));
}
.nm-lp-gal--lead1 .nm-lp-gal__tile:nth-child(1) { grid-column: 1 / -1; }

/* A video tile is the poster until pressed; the badge is .nm-media4__play's. */
.nm-lp-video { position: relative; overflow: hidden; background: #08080a; }
.nm-lp-video__media { width: 100%; height: 100%; object-fit: cover; }
.nm-lp-video--playing .nm-media4__play { opacity: 0; pointer-events: none; }

/* ── 8. Reviews ────────────────────────────────────────────────────────── */
.nm-lp-rev { padding-block: calc(96 * var(--lp)) calc(116 * var(--lp)); }
.nm-lp-rev__grid {
  display: grid;
  grid-template-columns: calc(400 * var(--lp)) minmax(0, 1fr);
  column-gap: calc(48 * var(--lp));
  align-items: start;
}
.nm-lp-rev__marks { display: flex; align-items: center; justify-content: space-between; }
.nm-lp-rev__quote { width: max(56px, calc(90 * var(--lp))); height: auto; }
.nm-lp-rev__rating { display: flex; align-items: center; gap: calc(4 * var(--lp)); }
.nm-lp-rev__g { width: max(22px, calc(32 * var(--lp))); height: auto; margin-right: calc(12 * var(--lp)); }
.nm-lp-rev__star { width: max(18px, calc(26 * var(--lp))); height: auto; }
.nm-lp-rev__lead .nm-lp-h2 { margin-top: calc(32 * var(--lp)); }
.nm-lp-rev__nav {
  display: flex;
  align-items: center;
  gap: calc(28 * var(--lp));
  margin-top: calc(36 * var(--lp));
}
.nm-lp-rev__arrow {
  flex: 0 0 auto;
  padding: 0;
  background: none;
  border: 0;
  color: #fff;
  cursor: pointer;
}
.nm-lp-rev__arrow svg { width: max(26px, calc(32 * var(--lp))); height: auto; }
.nm-lp-rev__arrow:disabled { color: rgba(255, 255, 255, .45); cursor: default; }
.nm-lp-rev__bar {
  position: relative;
  flex: 1 1 auto;
  height: 3px;
  background: rgba(255, 255, 255, .35);
}
.nm-lp-rev__bar > span {
  position: absolute;
  inset: 0 auto 0 0;
  width: 75%;
  background: #fff;
  transition: width .25s ease;
}
.nm-lp-rev__list { display: flex; gap: calc(24 * var(--lp)); }
.nm-lp-rev__card {
  display: flex;
  flex: 0 0 calc((100% - (var(--lp-rev-show) - 1) * 24 * var(--lp)) / var(--lp-rev-show));
  flex-direction: column;
  justify-content: space-between;
  gap: calc(40 * var(--lp));
  min-height: calc(323 * var(--lp));
  padding: calc(28 * var(--lp));
  background: #0c0c0d;
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 6px;
}
.nm-lp-rev__text {
  margin: 0;
  font-size: max(15px, calc(18 * var(--lp)));
  line-height: 1.5;
  color: var(--lp-dim);
}
/* The ramp down the card's left edge beside the byline. */
.nm-lp-rev__by {
  position: relative;
  margin: 0;
  font-size: max(14px, calc(16 * var(--lp)));
  line-height: 1.5;
  color: var(--lp-dim);
}
.nm-lp-rev__by strong { font-weight: 700; color: #fff; }
.nm-lp-rev__by::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(-28 * var(--lp) - 1px);
  width: max(4px, calc(6 * var(--lp)));
  border-radius: 0 3px 3px 0;
  background: var(--nm-grad);
}

/* ── 9. Closing call band ──────────────────────────────────────────────── */
.nm-lp-cta {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.nm-lp-cta__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.nm-lp-cta__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, .15) 0%, rgba(0, 0, 0, 0) 45%, #000 100%),
    linear-gradient(90deg, rgba(0, 0, 0, .55) 0%, rgba(0, 0, 0, .2) 60%, rgba(0, 0, 0, .1) 100%);
}
.nm-lp-cta__mark {
  position: absolute;
  top: 50%;
  right: calc(150 * var(--lp));
  z-index: -1;
  width: calc(290 * var(--lp));
  aspect-ratio: 1;
  background: center / contain no-repeat;
  opacity: .22;
  transform: translateY(-50%);
}
.nm-lp-cta__grid {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  min-height: calc(395 * var(--lp));
  padding-block: calc(60 * var(--lp));
}
.nm-lp-cta__title {
  font-family: var(--nm-font-display);
  font-size: max(36px, calc(92 * var(--lp)));
  font-weight: 400;
  line-height: 1;
  letter-spacing: -.035em;           /* the hero's tracking; see 2. Hero */
  text-transform: uppercase;
}
.nm-lp-cta__body {
  margin-top: calc(30 * var(--lp));
  font-size: max(16px, calc(20 * var(--lp)));
  line-height: 1.4;
}
.nm-lp-cta__btn { flex: 0 0 auto; }
.nm-lp--led .nm-lp-cta__grid { min-height: calc(417 * var(--lp)); }

/* ── 11. Footer ────────────────────────────────────────────────────────── */
.nm-lpf { padding-top: calc(32 * var(--lp)); }
.nm-lpf__row {
  display: flex;
  align-items: center;
  gap: calc(70 * var(--lp));
}
.nm-lpf__logo { width: max(64px, calc(92 * var(--lp))); height: auto; }
.nm-lpf__legal,
.nm-lpf__credit {
  margin: 0;
  font-size: max(12px, calc(16 * var(--lp)));
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--lp-dim);
}
.nm-lpf__credit { margin-left: auto; }
/* The comps crop the wordmark harder than the site footer does: half of it
   is below the fold of the page, against the site's quarter. */
.nm-lpf .nm-foot__wordmark { margin-bottom: -10.5%; }

/* ── Tablet ────────────────────────────────────────────────────────────── */
@media (max-width: 1150px) {
  .nm-lp { --lp-g: 40px; --lp-rev-show: 2; }
  .nm-lp-narrow { padding-inline: 0; }

  .nm-lp-hero__row { flex-wrap: wrap; }

  .nm-lp-quote--row .nm-lp-form__fields { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .nm-lp-quote--split .nm-lp-quote__grid { grid-template-columns: minmax(0, 1fr); }
  .nm-lp-quote--split .nm-lp-form { margin-top: 36px; }

  .nm-lp-why__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 40px; }

  .nm-lp-svc__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nm-lp-adv__list { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 28px; }

  .nm-lp-gal--grid .nm-lp-gal__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 240px; }
  .nm-lp-gal--lead2 .nm-lp-gal__grid,
  .nm-lp-gal--lead1 .nm-lp-gal__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: none;
    grid-auto-rows: 240px;
  }
  .nm-lp-gal--lead2 .nm-lp-gal__tile,
  .nm-lp-gal--lead2 .nm-lp-gal__tile:nth-child(n) { grid-column: auto; }
  .nm-lp-gal--lead1 .nm-lp-gal__tile:nth-child(1) { grid-column: 1 / -1; grid-row: span 2; }
  /* One lead over three: three across, or the third would sit alone. */
  .nm-lp-gal--lead1 .nm-lp-gal__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  .nm-lp-rev__grid { grid-template-columns: minmax(0, 1fr); row-gap: 36px; }
  .nm-lp-rev__lead { max-width: 480px; }
}

/* ── Phone ─────────────────────────────────────────────────────────────────
   Read off the 390 frames in --nm-u (see section 99). The order is the
   desktop's with every row stacked; the header trades the question and the
   quote button for one call button. */
@media (max-width: 640px) {
  .nm-lp {
    --lp-g: calc(25 * var(--nm-u));
    --lp-rev-show: 3;
  }

  .nm-lp-h2,
  .nm-lp-h2--lg { font-size: calc(38 * var(--nm-u)); }
  .nm-lp-lede { margin-top: calc(18 * var(--nm-u)); font-size: calc(17 * var(--nm-u)); line-height: 1.6; }
  .nm-lp-eyebrow { gap: calc(10 * var(--nm-u)); margin-bottom: calc(14 * var(--nm-u)); font-size: calc(15 * var(--nm-u)); }
  .nm-lp-eyebrow::before { height: calc(22 * var(--nm-u)); }
  .nm-lp-eyebrow--grad::before { height: calc(18 * var(--nm-u)); }
  .nm-lp-rule { width: calc(66 * var(--nm-u)); margin-top: calc(18 * var(--nm-u)); }

  .nm-lpbtn {
    gap: calc(16 * var(--nm-u));
    min-height: calc(54 * var(--nm-u));
    padding-inline: calc(32 * var(--nm-u));
    font-size: calc(18 * var(--nm-u));
  }
  .nm-lpbtn__icon { width: calc(22 * var(--nm-u)); }

  /* Header */
  .nm-lph__inner { padding: calc(26 * var(--nm-u)) var(--lp-g); }
  .nm-lph__logo { width: calc(60 * var(--nm-u)); }
  .nm-lph__ask,
  .nm-lph__cta { display: none; }
  .nm-lph__call { display: inline-flex; padding-inline: calc(17 * var(--nm-u)); }
  .nm-lp-quote { scroll-margin-top: calc(107 * var(--nm-u)); }

  /* Hero */
  .nm-lp-hero { padding-block: calc(60 * var(--nm-u)); }
  .nm-lp-hero .nm-lp-eyebrow { margin-bottom: calc(28 * var(--nm-u)); }
  /* The phone frames set the headline at the font's own tracking. */
  .nm-lp-hero__title { font-size: calc(50 * var(--nm-u)); line-height: .98; letter-spacing: -.005em; }
  .nm-lp-hero__row {
    flex-direction: column;
    align-items: flex-start;
    gap: calc(40 * var(--nm-u));
    margin-top: calc(34 * var(--nm-u));
  }
  .nm-lp-hero__sub { font-size: calc(18 * var(--nm-u)); line-height: 1.55; }
  .nm-lp-hero__media { margin-top: calc(36 * var(--nm-u)); }
  .nm-lp-hero__img { height: calc(490 * var(--nm-u)); border-radius: 6px 6px 0 0; }

  /* Quote form */
  .nm-lp-quote { padding-block: calc(50 * var(--nm-u)); }
  .nm-lp-quote__sub { margin-top: calc(22 * var(--nm-u)); font-size: calc(17 * var(--nm-u)); line-height: 1.55; }
  .nm-lp-quote--row .nm-lp-form__fields,
  .nm-lp-quote--split .nm-lp-form__fields {
    grid-template-columns: minmax(0, 1fr);
    gap: calc(24 * var(--nm-u));
    margin-top: calc(32 * var(--nm-u));
  }
  .nm-lp-quote--split .nm-lp-form { margin-top: 0; }
  .nm-lp-form__label { margin-bottom: calc(12 * var(--nm-u)); font-size: calc(15 * var(--nm-u)); }
  .nm-lp-form__input { height: calc(50 * var(--nm-u)); font-size: 16px; }
  .nm-lp-form__captcha { align-items: center; }
  .nm-lp-form__actions {
    flex-direction: column;
    gap: calc(16 * var(--nm-u));
    margin-top: calc(38 * var(--nm-u));
  }
  .nm-lp-form__trust {
    max-width: calc(250 * var(--nm-u));
    font-size: calc(16 * var(--nm-u));
    text-align: center;
  }

  /* Why */
  .nm-lp-why { padding-block: calc(50 * var(--nm-u)) calc(60 * var(--nm-u)); }
  .nm-lp-why__grid { grid-template-columns: minmax(0, 1fr); }
  .nm-lp-why__img { margin-top: calc(34 * var(--nm-u)); aspect-ratio: 340 / 260; }
  .nm-lp-checks { margin-top: calc(34 * var(--nm-u)); }
  .nm-lp-checks__item {
    gap: calc(16 * var(--nm-u));
    padding-block: calc(14 * var(--nm-u));
    font-size: calc(24 * var(--nm-u));
    line-height: 1.08;
  }
  .nm-lp-checks__item:first-child { padding-top: calc(14 * var(--nm-u)); }
  .nm-lp-checks__mark { width: calc(32 * var(--nm-u)); }
  .nm-lp-why__cta { margin-top: calc(34 * var(--nm-u)); }

  /* Services */
  .nm-lp-svc { padding-block: calc(60 * var(--nm-u)); }
  .nm-lp-svc__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: calc(18 * var(--nm-u));
    margin-top: calc(28 * var(--nm-u));
  }
  .nm-lp-card { padding-bottom: calc(24 * var(--nm-u)); }
  .nm-lp-card__img { aspect-ratio: 340 / 197; }
  .nm-lp-card__title { min-height: 0; margin-top: calc(20 * var(--nm-u)); font-size: calc(24 * var(--nm-u)); }
  .nm-lp-card__body { margin-top: calc(14 * var(--nm-u)); font-size: calc(17 * var(--nm-u)); }
  .nm-lp-card .nm-lpbtn { margin-top: calc(20 * var(--nm-u)); }

  /* Advantages */
  .nm-lp-adv { padding-block: calc(60 * var(--nm-u)); }
  .nm-lp-adv__pics { grid-template-columns: minmax(0, 1fr); gap: calc(12 * var(--nm-u)); margin-top: calc(34 * var(--nm-u)); }
  .nm-lp-adv__img { aspect-ratio: 340 / 178; }
  .nm-lp-adv__list { grid-template-columns: minmax(0, 1fr); gap: 0; margin-top: calc(10 * var(--nm-u)); }
  .nm-lp-adv__item { padding-block: calc(18 * var(--nm-u)); }
  .nm-lp-adv__title { gap: calc(14 * var(--nm-u)); font-family: var(--nm-font-cond); font-size: calc(22 * var(--nm-u)); }
  .nm-lp-adv__mark { width: calc(24 * var(--nm-u)); }
  .nm-lp-adv__body { margin-top: calc(10 * var(--nm-u)); font-size: calc(17 * var(--nm-u)); }
  .nm-lp-adv__cta { margin-top: calc(34 * var(--nm-u)); }

  /* Gallery */
  .nm-lp-gal { padding-block: calc(60 * var(--nm-u)); }
  .nm-lp-gal__head { flex-direction: column; align-items: flex-start; gap: calc(22 * var(--nm-u)); }
  .nm-lp-gal__grid { margin-top: calc(26 * var(--nm-u)); }
  .nm-lp-gal--grid .nm-lp-gal__grid {
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: calc(187 * var(--nm-u));
    gap: calc(16 * var(--nm-u));
  }
  /* LED and wedding: the frames draw the first two tiles full width at 218
     and the rest two-up at 128, so the heights are the tiles' own rather than
     row spans. */
  .nm-lp-gal--lead2 .nm-lp-gal__grid,
  .nm-lp-gal--lead1 .nm-lp-gal__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: none;
    grid-auto-rows: auto;
    gap: calc(16 * var(--nm-u));
  }
  .nm-lp-gal--lead2 .nm-lp-gal__tile,
  .nm-lp-gal--lead1 .nm-lp-gal__tile,
  .nm-lp-gal--lead2 .nm-lp-gal__tile:nth-child(n),
  .nm-lp-gal--lead1 .nm-lp-gal__tile:nth-child(n) {
    grid-column: auto;
    grid-row: auto;
    height: calc(128 * var(--nm-u));
  }
  .nm-lp-gal--lead2 .nm-lp-gal__tile:nth-child(-n + 2),
  .nm-lp-gal--lead1 .nm-lp-gal__tile:nth-child(-n + 2) {
    grid-column: 1 / -1;
    height: calc(218 * var(--nm-u));
  }

  /* Reviews: the lead centres over a stack of cards. */
  .nm-lp-rev { padding-block: calc(60 * var(--nm-u)); }
  .nm-lp-rev__grid { row-gap: calc(28 * var(--nm-u)); }
  .nm-lp-rev__lead { max-width: none; text-align: center; }
  .nm-lp-rev__marks { flex-direction: column; gap: calc(16 * var(--nm-u)); }
  .nm-lp-rev__quote { width: calc(48 * var(--nm-u)); }
  .nm-lp-rev__g { width: calc(26 * var(--nm-u)); margin-right: calc(8 * var(--nm-u)); }
  .nm-lp-rev__star { width: calc(20 * var(--nm-u)); }
  .nm-lp-rev__lead .nm-lp-h2 { margin-top: calc(14 * var(--nm-u)); }
  .nm-lp-rev__nav { gap: calc(32 * var(--nm-u)); margin-top: calc(22 * var(--nm-u)); }
  .nm-lp-rev__list { flex-direction: column; gap: calc(16 * var(--nm-u)); }
  .nm-lp-rev__card {
    flex-basis: auto;
    gap: calc(28 * var(--nm-u));
    min-height: 0;
    padding: calc(28 * var(--nm-u)) calc(24 * var(--nm-u));
  }
  .nm-lp-rev__text { font-size: calc(18 * var(--nm-u)); }
  .nm-lp-rev__by { font-size: calc(16 * var(--nm-u)); }
  .nm-lp-rev__by::before { left: calc(-24 * var(--nm-u) - 1px); width: calc(6 * var(--nm-u)); }

  /* Call band */
  .nm-lp-cta__grid {
    flex-direction: column;
    align-items: flex-start;
    gap: calc(30 * var(--nm-u));
    min-height: 0;
    padding-block: calc(50 * var(--nm-u));
  }
  .nm-lp-cta__title { font-size: calc(52 * var(--nm-u)); line-height: .98; letter-spacing: -.005em; }
  .nm-lp-cta__body { margin-top: calc(16 * var(--nm-u)); font-size: calc(18 * var(--nm-u)); }
  .nm-lp-cta__mark { top: 6%; right: calc(-70 * var(--nm-u)); width: calc(230 * var(--nm-u)); transform: none; }

  /* Footer: everything centres and stacks. */
  .nm-lpf { padding-top: calc(25 * var(--nm-u)); }
  .nm-lpf__row { flex-direction: column; gap: calc(14 * var(--nm-u)); text-align: center; }
  .nm-lpf__logo { width: calc(150 * var(--nm-u)); margin-bottom: calc(10 * var(--nm-u)); }
  .nm-lpf__legal,
  .nm-lpf__credit { font-size: calc(15 * var(--nm-u)); }
  .nm-lpf__credit { margin-left: 0; }
}
