/* ============================================================
   NORFORGE — Layout, transitions, and interactive effects
   Hover/focus/active states, GSAP reveal placeholders, responsive.
   ============================================================ */

/* Skip-to-content link: visually hidden until focused by keyboard. */
.nf-skip-link {
  position: absolute;
  top: -100px;
  left: 16px;
  z-index: 100;
  padding: 8px 16px;
  background: var(--verdigris-400);
  color: var(--iron-900);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 500;
  border-radius: 4px;
  text-decoration: none;
  transition: top 150ms ease;
}
.nf-skip-link:focus { top: 16px; }
html, body { margin: 0; background: var(--bg); scroll-behavior: smooth; }
* { box-sizing: border-box; }

/* Offset anchor targets so the sticky 60px nav doesn't cover section titles
   when scrolling to a #hash (both same-page clicks and cross-page landings). */
section[id] { scroll-margin-top: 80px; }

/* Link normalization */
a { color: inherit; text-decoration: none; }

/* Nav link hover — base colour lives here (not inline) so :hover can win.
   Used by the GitHub icon (and footer links): brighten to white on hover. */
.nf-nav-link { color: var(--iron-200); transition: color 200ms cubic-bezier(0.2, 0, 0, 1); }
.nf-nav-link:hover { color: var(--paper-pure); }

/* Header text links: a thin verdigris underline appears on hover (instead of
   brightening — that treatment is reserved for the GitHub icon). */
.nf-nav-textlink { position: relative; color: var(--iron-200); }
.nf-nav-textlink::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 1.5px;
  background: var(--accent); border-radius: 1px; opacity: 0;
}
.nf-nav-textlink:hover::after { opacity: 1; }

/* Buttons - a verdigris glow radiates from the button on hover (no movement),
   echoing the aurora. Secondary buttons are ghost by default and fill with the
   accent (verdigris ground, dark text) on hover. */
.nf-btn-primary { transition: box-shadow 240ms cubic-bezier(0.2, 0, 0, 1); }
.nf-btn-primary:hover { box-shadow: 0 0 20px rgba(71,160,140,0.55), 0 0 44px rgba(71,160,140,0.3); }
.nf-btn-secondary {
  background: transparent;
  color: var(--fog);
  border: 1px solid var(--border-strong);
  transition: background-color 200ms cubic-bezier(0.2, 0, 0, 1), border-color 200ms cubic-bezier(0.2, 0, 0, 1), color 200ms cubic-bezier(0.2, 0, 0, 1), box-shadow 240ms cubic-bezier(0.2, 0, 0, 1);
}
.nf-btn-secondary:hover {
  background: var(--verdigris-400);
  border-color: var(--verdigris-400);
  color: var(--iron-900);
  box-shadow: 0 0 16px rgba(71,160,140,0.4), 0 0 36px rgba(71,160,140,0.22);
}

/* Title-line mask for GSAP reveal */
.nf-mask { display: block; overflow: hidden; padding-bottom: 0.08em; }
.nf-mask > span { display: block; will-change: transform; }

/* Pillar cards */
.nf-pillar { transition: border-color 250ms cubic-bezier(0.2, 0, 0, 1), transform 250ms cubic-bezier(0.2, 0, 0, 1); }
.nf-pillar:hover { border-color: rgba(71,160,140,0.42); transform: translateY(-2px); }

/* Team cards */
.nf-team-card { position: relative; cursor: default; overflow: hidden; transition: transform 350ms cubic-bezier(0.2, 0, 0, 1), border-color 350ms cubic-bezier(0.2, 0, 0, 1); }
.nf-team-card:hover { transform: translateY(-3px); border-color: rgba(71,160,140,0.45); }
.nf-team-img-wrap { overflow: hidden; }
.nf-team-img { transition: transform 600ms cubic-bezier(0.2, 0, 0, 1), filter 600ms cubic-bezier(0.2, 0, 0, 1); filter: grayscale(0.55) contrast(1.02); }
.nf-team-card:hover .nf-team-img { transform: scale(1.04); filter: grayscale(0) contrast(1.05); }
.nf-team-card::before {
  content: ""; position: absolute; left: 20px; top: 20px; width: 6px; height: 6px;
  background: var(--accent); border-radius: 999px; opacity: 0; transform: scale(0.6);
  transition: opacity 300ms cubic-bezier(0.2, 0, 0, 1), transform 300ms cubic-bezier(0.2, 0, 0, 1);
  box-shadow: 0 0 0 4px rgba(71,160,140,0.18);
  z-index: 2;
}
.nf-team-card:hover::before { opacity: 1; transform: scale(1); }

/* LinkedIn profile icon (next to names in the team + about rosters) */
.nf-linkedin { display: inline-flex; align-items: center; color: var(--iron-300); transition: color 200ms cubic-bezier(0.2, 0, 0, 1); position: relative; z-index: 3; }
.nf-linkedin:hover { color: var(--accent); }

/* Section accent underline */
.nf-accent-bar { display: inline-block; width: 28px; height: 2px; background: var(--accent); margin-bottom: 18px; transform-origin: left center; }

/* Hero grid layer uses will-change for smoother parallax */
.nf-hero-grid { will-change: transform; }

/* The hero title breaks after "in" at every width: the two-column layout
   gives it a narrower column, so the controlled break yields a stable
   two-line title everywhere instead of an unpredictably wrapping one. */
.nf-hero-br { display: inline; }

/* Hero title/tagline two columns with a hairline divider between. Below the
   mobile ground the columns stack and center, and the divider drops out.
   !important: the inline styles carry textAlign:left; there is no
   non-important path past an inline declaration. */
@media (max-width: 760px) {
  .nf-hero-cols { flex-direction: column; gap: 20px; }
  .nf-hero-title, .nf-hero-tagline { text-align: center !important; }
  .nf-hero-divider { display: none; }
}

/* ============================================================
   Hero — three core messages side by side.
   All three show at once as a 3-column grid over the aurora, no card chrome,
   and fade/rise in on entry with a stagger (driven in JS). All three text
   blocks live in the DOM and are visible with no JS, so nothing is hidden
   from search or non-JS visitors.
   ============================================================ */
.nf-hero-stage { position: relative; width: 100%; display: grid; grid-template-columns: repeat(3, 1fr); gap: 40px 32px; }

/* No card chrome - the message text floats directly over the aurora. */
.nf-hero-card { position: relative; box-sizing: border-box; min-width: 0; will-change: transform, opacity; }

/* Oversized index numeral, set high and to the right as a faint watermark. */
.nf-hero-card-num {
  position: absolute; top: -0.28em; right: -4px; z-index: 0;
  font-family: 'Space Grotesk', sans-serif; font-weight: 700;
  font-size: clamp(64px, 7vw, 100px); line-height: 0.8; letter-spacing: -0.05em;
  color: rgba(71,160,140,0.13); pointer-events: none; user-select: none;
}
.nf-hero-bar {
  position: relative; z-index: 1;
  display: block; width: 40px; height: 2px; background: var(--accent); margin-bottom: 20px;
  box-shadow: 0 0 12px rgba(71,160,140,0.4);
}
.nf-hero-card-head {
  position: relative; z-index: 1;
  font-family: 'Space Grotesk', sans-serif; font-size: clamp(20px, 1.7vw, 26px);
  font-weight: 500; line-height: 1.15; letter-spacing: -0.02em;
  color: var(--fog); margin: 0 0 14px; overflow-wrap: break-word;
}
.nf-hero-card-sub {
  position: relative; z-index: 1;
  font-family: 'Inter Tight', sans-serif; font-size: clamp(14px, 1vw, 16px);
  line-height: 1.55; color: var(--iron-100); margin: 0;
}

@media (max-width: 760px) {
  .nf-hero-stage { grid-template-columns: 1fr; gap: 36px; }
  .nf-hero-card-num { font-size: clamp(70px, 20vw, 110px); }
}

/* Reveal defaults (hidden until GSAP fires) — gated behind `.nf-js` so they
   only hide when JavaScript is present to reveal them again. With no JS the
   `.nf-js` class is never added (see Layout.jsx), so the content stays fully
   visible for no-JS visitors and crawlers. */
.nf-js .nf-reveal { opacity: 0; }
.nf-js .nf-reveal-x { opacity: 0; }

/* Reduced motion: don't leave reveal content waiting on a scroll animation —
   show it straight away. `!important` overrides GSAP's inline opacity:0. */
@media (prefers-reduced-motion: reduce) {
  .nf-js .nf-reveal, .nf-js .nf-reveal-x { opacity: 1 !important; }
}

/* ============================================================
   Render-without-animation fallback.
   The entrance/scroll animations reveal content by tweening it from an
   opacity:0 (or transformed) start state back to visible, which relies on the
   animation loop (requestAnimationFrame) advancing. A renderer that runs the
   JS but never advances rAF — notably Google's Web Rendering Service — applies
   the hidden start state and then captures it, so the page looks blank.

   The inline head script (Layout.jsx) adds `nf-no-anim` to <html> when a rAF
   callback hasn't fired within ~1s, i.e. the loop isn't running. That forces
   every animated-hidden element visible here; `!important` in the stylesheet
   overrides GSAP's non-important inline opacity/transform. Real users' rAF
   fires, so `nf-no-anim` is never added and the animations play as normal.

   Every element GSAP hides/tweens from a hidden start state carries the shared
   `.nf-anim` marker (added alongside its existing classes), so this one rule
   covers them all — no enumerated selector list to keep in sync when a new
   animated element is added.
   ============================================================ */
.nf-no-anim .nf-anim {
  opacity: 1 !important;
  visibility: visible !important;
  transform: none !important;
}

/* Hero - wireframe cube drifting on all three axes behind the title and
   tagline. Pure CSS 3D: six bordered faces on a preserve-3d box, spun by one
   linear keyframe. Size comes from --cube (set inline on the scene) so the
   faces' translateZ can be derived with calc. Gated off for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  .nf-hero-cube { animation: nf-cube-spin 46s linear infinite; }
}
.nf-hero-cube {
  position: relative;
  transform-style: preserve-3d;
}
.nf-hero-cube-face {
  position: absolute;
  inset: 0;
  border: 1px solid color-mix(in srgb, var(--verdigris-400) 55%, transparent);
}
@keyframes nf-cube-spin {
  from { transform: rotateX(-22deg) rotateY(0deg) rotateZ(0deg); }
  to   { transform: rotateX(-22deg) rotateY(360deg) rotateZ(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .nf-hero-cube { animation: none; }
}

/* Hero promise line ("Know your agent") - static paint plus two slow
   phosphor behaviors: the scanline pattern drifts one period (3px) per
   2.5s, a seamless vertical crawl, and the whole readout flickers on an
   irregular ~3.4s cycle like a real tube's bloom. The block cursor shares
   the same scanline paint and drifts with it, while blinking on its own
   1.15s beat. All three are gated off for reduced motion: the paint stays
   static, the line holds full opacity, the cursor unblinks. */
@media (prefers-reduced-motion: no-preference) {
  .nf-know-text { animation: nf-know-drift 2.5s linear infinite; }
  .nf-hero-know { animation: nf-know-flicker 3.4s linear infinite; }
  .nf-hero-caret {
    animation: nf-know-blink 1.15s linear infinite,
               nf-know-drift 2.5s linear infinite;
  }
}
@keyframes nf-know-drift {
  from { background-position: 0 0; }
  to   { background-position: 0 3px; }
}
@keyframes nf-know-flicker {
  0%, 100% { opacity: 1; }
  6%  { opacity: 0.95; }
  9%  { opacity: 0.99; }
  13% { opacity: 0.9; }
  15%, 40% { opacity: 1; }
  44% { opacity: 0.96; }
  47% { opacity: 0.92; }
  49%, 70% { opacity: 0.98; }
  73% { opacity: 1; }
}
@keyframes nf-know-blink {
  0%, 55% { opacity: 1; }
  56%, 100% { opacity: 0; }
}

/* ============================================================
   Team carousel (homepage #team) - Glide.js (@glidejs/glide, ISC).
   React renders the slides; Glide owns the track after mount. Active
   slide emphasis via .glide__slide--active: full-color image, stronger
   verdigris shade, larger caption. Slider behavior is gated behind
   .nf-js so no-JS visitors get a wrapped grid of equal cards instead.
   ============================================================ */
/* Minimal stand-in for glide.core.css (the pipeline ships no library CSS):
   the track hides overflow, slides sit side by side and shrink to their
   JS-assigned width. */
.glide { position: relative; width: 100%; box-sizing: border-box; cursor: grab; }
.glide--dragging { cursor: grabbing; }
.glide__track { overflow: hidden; }
.glide__slides {
  display: flex; flex-wrap: nowrap; list-style: none;
  margin: 0; padding: 0; overflow: hidden; touch-action: pan-Y;
  will-change: transform;
}
.glide__slide { flex-shrink: 0; width: 100%; }
/* Drag hygiene from Glide's core CSS: no text selection or native image /
   link dragging while swiping. */
.glide__slides, .glide__slide { user-select: none; -webkit-user-select: none; }
.glide__slide a {
  -webkit-user-drag: none; user-select: none; -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
/* The frame: a 3D box after glidejs.com's hero. Green (verdigris) borders
   replace the site's translucent white ones; the front face carries the
   person, the two side walls rotate into depth and pick up the tilt. */
.nf-tc-frame {
  position: relative; height: 100%;
  transform-style: preserve-3d;
  transition: transform 1s ease-in-out;
  transform-origin: 50% 50%;
  will-change: transform;
}
.nf-tc-face {
  position: absolute; inset: 0; overflow: hidden;
  background: transparent;
  border: 2px solid color-mix(in srgb, var(--verdigris-400) 60%, transparent);
  border-radius: 2px;
  box-shadow: 0 .5rem 4rem 0 rgba(0, 0, 0, 0.5);
}
/* Each face is a link to that person's card on /team; covers the whole
   face so image and caption are both clickable. */
.nf-tc-link {
  position: absolute; inset: 0; z-index: 1; display: block;
  color: inherit; text-decoration: none;
}
.nf-tc-wall {
  position: absolute; top: 0; width: 3rem; height: 100%;
  border: 3px solid color-mix(in srgb, var(--verdigris-400) 60%, transparent);
  backface-visibility: hidden;
}
.nf-tc-wall--right {
  right: 0; border-right-width: 9px;
  transform: translate3d(-1px, 0, -3rem) rotateY(90deg);
  transform-origin: 100% 50%;
}
.nf-tc-wall--left {
  left: 0; border-left-width: 9px;
  transform: translate3d(1px, 0, -3rem) rotateY(-90deg);
  transform-origin: 0 50%;
}
.nf-tc-img {
  display: block; position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center top;
  filter: saturate(0.72) brightness(0.82);
  transition: filter 1s ease-in-out, opacity 1s ease-in-out;
}
.glide__slide--active .nf-tc-img { filter: none; }
/* Verdigris shade, mixed from the palette greens so the caption sits on
   the card's own accent rather than a flat black box. */
.nf-tc-shade {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--verdigris-400) 0%, transparent) 40%,
    color-mix(in srgb, var(--verdigris-500) 30%, transparent) 72%,
    color-mix(in srgb, var(--iron-900) 82%, transparent) 100%
  );
  opacity: 0.85;
}
.nf-tc-caption {
  position: absolute; left: 16px; bottom: 16px; z-index: 1;
  display: flex; flex-direction: column; gap: 5px;
  /* Text changes size and position as a slide becomes active; easing these
     over Glide's own 1s move keeps the swap from snapping. */
  transition: left 1s ease-in-out, bottom 1s ease-in-out;
}
.nf-tc-name {
  font-family: var(--font-display); font-weight: 500; letter-spacing: -0.01em;
  font-size: 14px; color: var(--paper-pure); line-height: 1.2;
  transition: font-size 1s ease-in-out, color 1s ease-in-out;
}
.nf-tc-focus {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--verdigris-200); line-height: 1.3;
  transition: font-size 1s ease-in-out, color 1s ease-in-out;
}
.glide__slide--active .nf-tc-caption { left: 24px; bottom: 22px; }
.glide__slide--active .nf-tc-name { font-size: clamp(17px, 1.8vw, 22px); }
.glide__slide--active .nf-tc-focus { font-size: 11px; color: var(--verdigris-400); }
@media (prefers-reduced-motion: reduce) {
  .nf-tc-caption, .nf-tc-name, .nf-tc-focus { transition: none; }
}
/* Round side arrows, positioned over the row like glidejs.com's; hover
   fills with the palette green instead of their pink. */
.nf-tc-arrow {
  position: absolute; top: 50%; z-index: 2;
  width: 46px; height: 46px; margin-top: -23px;
  border-radius: 50%; border: 0; cursor: pointer; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background-color: var(--iron-300); color: var(--iron-900);
  box-shadow: 0 .5rem 4rem 0 rgba(0, 0, 0, 0.5);
  transition: background-color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.nf-tc-arrow:hover { background-color: var(--verdigris-400); }
.nf-tc-arrow--next { right: 1.5rem; }
.nf-tc-arrow--prev { left: 1.5rem; }
/* Cards on /team carry person-<userid> ids so the carousel links and the
   JSON-LD person nodes can deep-link to them; the offset keeps the sticky
   header from covering the landing position. */
[id^="person-"] { scroll-margin-top: 80px; }
.nf-tc-arrow:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
/* Edge vignettes: static gradient layers fading into the section ground so
   the outermost cards dissolve into the background. Pseudo-elements sit
   above the track but below the arrows, and never intercept pointers. */
.nf-tc::before, .nf-tc::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 20%;
  z-index: 1; pointer-events: none;
}
.nf-tc::before { left: 0; background: linear-gradient(90deg, var(--iron-900) 25%, transparent); }
.nf-tc::after { right: 0; background: linear-gradient(270deg, var(--iron-900) 25%, transparent); }
/* On very narrow screens the inactive captions clip; hide them there. */
@media (max-width: 560px) {
  .glide__slide:not(.glide__slide--active) .nf-tc-caption { display: none; }
}
/* Mobile: the overlay arrows sit where thumbs swipe, so below 900px they
   become a static centered row under the track (glidejs.com does the same
   under 42.4375em). The edge fade also eases off: narrower and never fully
   opaque, so more of the outermost frames stays visible on phones. */
@media (max-width: 900px) {
  .nf-tc [data-glide-el="controls"] {
    display: flex; justify-content: center; gap: 12px; margin-top: 18px;
  }
  .nf-tc-arrow { position: static; margin-top: 0; }
  .nf-tc::before, .nf-tc::after { width: 10%; }
  .nf-tc::before { background: linear-gradient(90deg, color-mix(in srgb, var(--iron-900) 78%, transparent), transparent); }
  .nf-tc::after { background: linear-gradient(270deg, color-mix(in srgb, var(--iron-900) 78%, transparent), transparent); }
}
/* No-JS fallback: without Glide the track wraps into equal green-bordered
   cards; the 3D walls would render as flat slivers, so hide them. */
html:not(.nf-js) .glide__slides { flex-wrap: wrap; gap: 24px; }
html:not(.nf-js) .glide__slide { width: 220px !important; }
html:not(.nf-js) .nf-tc-wall { display: none; }
html:not(.nf-js) .nf-tc-arrow { display: none; }
html:not(.nf-js) .nf-tc::before, html:not(.nf-js) .nf-tc::after { display: none; }


/* ============================================================
   Work records as filing folders: a tab strip of folder tabs sits on the
   active dossier; the active tab shares the panel's ground and merges
   with its top border (no bottom edge, overlapping by 1px), while the
   inactive tabs sit a step lower and dimmer - index tabs sticking out of
   the stack. No-JS: tabs hide, every dossier un-hides and stacks.
   ============================================================ */
.nf-wk-tabs { position: relative; display: flex; gap: 6px; }
.nf-wk-tab {
  position: relative; z-index: 0;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--iron-300);
  background: var(--iron-700); border: 1px solid var(--border-subtle); border-bottom: none;
  border-radius: 8px 8px 0 0; padding: 9px 14px 11px; cursor: pointer;
  margin-bottom: -1px; transform: translateY(7px);
  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1), background-color 200ms cubic-bezier(0.2, 0, 0, 1), color 200ms cubic-bezier(0.2, 0, 0, 1);
}
.nf-wk-tab:hover { color: var(--fog); }
.nf-wk-tab:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
/* Layering: the panel covers the inactive tabs (they tuck behind its top
   edge), and the selected tab rises above the panel so its ground merges
   with the dossier's frame. */
.nf-wk-panel { position: relative; z-index: 1; }
.nf-wk-tab[aria-selected="true"] { background: var(--iron-700); color: var(--fog); transform: none; z-index: 2; }
@media (max-width: 760px) {
  .nf-wk-tabs { flex-wrap: wrap; }
  /* Compact phone tabs: index only, the sector rides on wider screens. */
  .nf-wk-tab { transform: none; padding: 8px 10px 9px; font-size: 9px; }
  .nf-wk-tab-sector { display: none; }
  .nf-wk-tab[aria-selected="true"] { border-radius: 8px 8px 0 0; }
}
/* No-JS fallback: the active index never changes, so un-hide every panel
   and drop the tab strip - the dossiers stack like plain documents. */
html:not(.nf-js) .nf-wk-tab { display: none !important; }
html:not(.nf-js) .nf-wk-record[hidden] { display: block !important; }

/* Dossier panels: at phone widths the 56px panel padding is too much. */
@media (max-width: 760px) {
  .nf-wk-panel { padding: 28px 22px !important; }
}

/* ============================================================
   About-page register popup - one click-through panel per person.
   Backdrop and panel fade/rise in; the keyframe pair is gated off for
   reduced motion (static appearance, instant dismiss).
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  .nf-person-modal { animation: nf-person-backdrop 200ms cubic-bezier(0.2, 0, 0, 1); }
  .nf-person-modal-panel { animation: nf-person-panel 200ms cubic-bezier(0.2, 0, 0, 1); }
}
@keyframes nf-person-backdrop {
  from { opacity: 0; }
}
@keyframes nf-person-panel {
  from { opacity: 0; transform: translateY(14px); }
}

/* On phones the desktop 80px indent would squeeze the copy; sections 2
   and 4 drop it and go flush like the others. */
@media (max-width: 760px) {
  .nf-svcs-inner { padding-left: 0 !important; }
}

/* Phone FAQ backdrop: gentler than desktop, and dimmed further - on
   phones the aurora fades darker so only a whisper of texture shows.
   The image also shifts rightward so the visible slice is the darker
   sky beside the arc, not the bright sweep itself. */
@media (max-width: 760px) {
  .nf-faq-bg { background-position: right center !important; opacity: 0.6 !important; }
  .nf-faq-bg-fade {
    background: linear-gradient(180deg, rgba(20,23,29,0.7) 0%, rgba(20,23,29,0) 55%), linear-gradient(90deg, rgba(27,31,39,1) 0%, rgba(27,31,39,1) 20%, rgba(27,31,39,0.18) 65%, rgba(27,31,39,0.1) 100%) !important;
  }
}

/* Visually hidden, but available to screen readers and search engines. */
.nf-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Responsive */
@media (max-width: 900px) {
  .nf-nav-links { display: none !important; }
  .nf-nav-burger { display: flex !important; }
  .nf-nav-panel { display: flex; }
  /* The header's Contact-us button drops below 900px: the burger panel
     carries the same link, and keeping both spends header width the logo
     needs. Desktop keeps the inline button. */
  .nf-nav-cta { display: none !important; }
  /* Author display beats the UA's [hidden] rule, so the collapsed panel
     needs its own override or it would render open below 900px. */
  .nf-nav-panel[hidden] { display: none !important; }
  .nf-pillar-grid { grid-template-columns: 1fr !important; }
  /* Two link columns side by side beat three stacked ones on a phone. */
  .nf-footer-cols { grid-template-columns: repeat(3, 1fr) !important; }
  .nf-footer-inner { grid-template-columns: 1fr !important; }
  /* Mobile footer compaction: the stacked brand block + 3 link columns with
     desktop spacing runs very tall on a phone. Tighter paddings/margins and
     smaller link rhythm cut the height without dropping any content. */
  .nf-footer { padding: 48px 32px 24px !important; }
  .nf-footer-inner { gap: 32px !important; }
  .nf-footer-cols { gap: 20px !important; }
  .nf-footer-cols > div > a { margin-bottom: 2px !important; }
  .nf-footer-cols > div > div { margin-bottom: 8px !important; }
  .nf-footer-bt { margin-bottom: 0 !important; }
  .nf-footer-address { margin-top: 12px !important; }
  .nf-footer-bottom { margin-top: 32px !important; padding-top: 16px !important; }
}
@media (max-width: 560px) {
  /* Phone footer: the link columns go two-up (Services | Company, then
     Contact), the tagline drops - the logo carries identity - and the
     address runs inline on one or two lines instead of three. */
  .nf-footer { padding: 36px 24px 20px !important; }
  .nf-footer-inner { gap: 24px !important; }
  .nf-footer-bt { display: none !important; }
  .nf-footer-address { margin-top: 8px !important; font-size: 11px !important; }
  .nf-footer-address br { display: none !important; }
  .nf-footer-address div { margin-bottom: 4px !important; }
  .nf-footer-cols { gap: 16px !important; }
  .nf-footer-cols > div > a { font-size: 12px !important; margin-bottom: 3px !important; }
  .nf-footer-cols > div > div { margin-bottom: 6px !important; }
  .nf-footer-bottom { margin-top: 20px !important; padding-top: 12px !important; font-size: 10px !important; }
}
@media (min-width: 901px) {
  .nf-nav-burger { display: none !important; }
  .nf-nav-panel { display: none !important; }
}
@media (max-width: 560px) {
  .nf-section-h2 { font-size: 32px !important; }
}
/* Mobile proof: the 6-column card grid cannot fit a phone - and the cards'
   chrome would only pad it. Below 900px the pillars become full-width
   readout lines (dot, index, title, body) under the same drawing rules:
   no card background or border, identical copy, all visible. */
@media (max-width: 900px) {
  .nf-proof-grid { grid-template-columns: 1fr !important; gap: 0 !important; }
  .nf-proof-row {
    background: transparent !important;
    border: none !important;
    border-radius: 0 !important;
    padding: 22px 0 4px !important;
  }
}

/* On phones the hero is tall and narrow, so object-fit:cover magnifies the 16:9
   aurora until it dominates. Keep the sky at the top and the mountains at the
   bottom, but dim the middle band behind the text so the copy stays legible. */
@media (max-width: 760px) {
  .nf-hero-aurora {
    opacity: 0.7;
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 20%, rgba(0,0,0,0.28) 48%, rgba(0,0,0,0.5) 72%, #000 96%);
            mask-image: linear-gradient(to bottom, #000 0%, #000 20%, rgba(0,0,0,0.28) 48%, rgba(0,0,0,0.5) 72%, #000 96%);
  }
}

/* ============================================================
   Audience toggle — a large segmented pill (Investors | Leaders) with a
   sliding verdigris indicator; the content panel below swaps with a GSAP fade.
   ============================================================ */
.nf-aud-toggle { position: relative; display: inline-flex; padding: 5px; border-radius: 999px; background: var(--iron-800); border: 1px solid rgba(210,212,213,0.12); }
.nf-aud-toggle-ind {
  position: absolute; top: 5px; bottom: 5px; left: 5px; width: 150px; border-radius: 999px;
  background: var(--accent); box-shadow: 0 6px 18px -6px rgba(71,160,140,0.6);
  transition: transform 380ms cubic-bezier(0.2,0,0,1);
}
.nf-aud-toggle-ind[data-pos="leaders"] { transform: translateX(150px); }
.nf-aud-seg {
  position: relative; z-index: 1; width: 150px; border: 0; background: transparent; cursor: pointer;
  padding: 13px 0; font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--iron-200); transition: color 380ms cubic-bezier(0.2,0,0,1);
}
.nf-aud-seg:hover { color: var(--fog); }
.nf-aud-seg.is-active, .nf-aud-seg.is-active:hover { color: var(--on-accent); font-weight: 500; }

@media (max-width: 380px) {
  .nf-aud-toggle-ind { width: 130px; }
  .nf-aud-toggle-ind[data-pos="leaders"] { transform: translateX(130px); }
  .nf-aud-seg { width: 130px; }
}
@media (prefers-reduced-motion: reduce) {
  .nf-aud-toggle-ind, .nf-aud-seg { transition: none; }
}

/* ============================================================
   Investor lifecycle timeline — diligence → hold → exit read as a connected
   path. A verdigris line draws across three glowing nodes; the phase label is
   the prominent headline of each pillar (highlighted), the value line sits
   under it in verdigris. (Animated in JS; static + legible with no JS.)
   ============================================================ */
.nf-lifecycle { position: relative; display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; margin: 40px 0 8px; }
/* Connecting line, drawn between the first and last node centres. */
.nf-lc-line { position: absolute; top: 7px; left: 16.66%; right: 16.66%; height: 2px; background: rgba(71,160,140,0.3); z-index: 0; }
.nf-lc-stage { position: relative; z-index: 1; text-align: center; }
.nf-lc-node {
  display: block; width: 16px; height: 16px; margin: 0 auto 26px; border-radius: 50%;
  background: var(--iron-900); border: 2px solid var(--accent);
  box-shadow: 0 0 0 5px rgba(71,160,140,0.1), 0 0 16px rgba(71,160,140,0.55);
}
.nf-lc-label { font-family: 'Space Grotesk', sans-serif; font-size: clamp(20px, 2.3vw, 26px); font-weight: 500; color: var(--fog); letter-spacing: -0.02em; line-height: 1.15; margin: 0 0 10px; }
.nf-lc-head { font-family: 'Inter Tight', sans-serif; font-size: 15px; font-weight: 500; color: var(--accent); line-height: 1.4; margin: 0 0 12px; }
.nf-lc-body { font-family: 'Inter Tight', sans-serif; font-size: 14px; color: var(--iron-200); line-height: 1.6; margin: 0 auto; max-width: 300px; }

@media (max-width: 760px) {
  .nf-lifecycle { grid-template-columns: 1fr; gap: 38px; }
  .nf-lc-line { display: none; }
  .nf-lc-node { margin-bottom: 16px; }
}

/* ============================================================
   Engagement journey — Feel It → Reach It → Anchor It read as a connected
   path (arrows between the steps), with a lift + verdigris glow on hover and
   the number badge inverting to a solid chip.
   ============================================================ */
.nf-eng-flow { display: flex; align-items: stretch; gap: 4px; }
.nf-eng-flow > .nf-eng-card { flex: 1 1 0; min-width: 0; }

.nf-eng-card { position: relative; transition: transform 300ms cubic-bezier(0.2,0,0,1), box-shadow 300ms cubic-bezier(0.2,0,0,1); }
.nf-eng-card:hover { transform: translateY(-5px); box-shadow: 0 22px 44px -24px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(71,160,140,0.5); }
/* Accent bar at the card's top edge - drawn in left-to-right as the step activates. */
.nf-eng-bar { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--accent); border-radius: 6px 6px 0 0; transform-origin: left center; }

.nf-eng-num {
  flex-shrink: 0; font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.05em;
  color: var(--accent); background: transparent; border: 1px solid rgba(71,160,140,0.4); border-radius: 6px; padding: 6px 9px;
  transition: background 300ms cubic-bezier(0.2,0,0,1), color 300ms cubic-bezier(0.2,0,0,1), border-color 300ms cubic-bezier(0.2,0,0,1);
}
.nf-eng-card:hover .nf-eng-num { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

.nf-eng-arrow { flex: 0 0 auto; align-self: center; display: flex; align-items: center; justify-content: center; padding: 0 8px; color: var(--accent); opacity: 0.6; }
.nf-eng-arrow span { display: block; font-family: 'Inter Tight', sans-serif; font-size: 22px; line-height: 1; }

@media (max-width: 900px) {
  .nf-eng-flow { flex-direction: column; align-items: stretch; gap: 4px; }
  .nf-eng-arrow { padding: 4px 0; }
  .nf-eng-arrow span { transform: rotate(90deg); }
  .nf-ingress-br { display: none; }
}

.nf-share { transition: color 300ms cubic-bezier(0.2,0,0,1), border-color 300ms cubic-bezier(0.2,0,0,1); }
.nf-share:hover { color: var(--verdigris-400); border-color: var(--verdigris-400); }

/* Diagram band, phone fallback: a live render that truly fits would drop
   the mono labels to ~3px and the 0.6-scale pan shows only a third of the
   art at a glance, so below 640px the live box is replaced by a frozen
   render of the same art (assets/how-norforge-works.png). Regenerate that
   PNG from the live DOM when the stage art changes. The render is a zoom
   button: the open overlay shows it at 2.4x the viewport (labels readable,
   scrollable), backdrop tap / x / Escape closes. */
.nf-diagram-static { display: none; width: 100%; height: auto; }
.nf-diagram-zoom {
  display: none; width: 100%; padding: 0; border: 0; background: none;
  cursor: zoom-in;
}
.nf-diagram-lightbox {
  position: fixed; inset: 0; z-index: 60;
  background: var(--overlay-scrim);
  backdrop-filter: blur(6px);
  cursor: zoom-out;
}
/* The panning lives on an inner layer: backdrop-filter makes the lightbox
   the containing block for fixed-position descendants, so a fixed close
   button inside it design-anchors to the overlay and scrolls with it. The
   button therefore sits outside the scrolling layer. */
.nf-diagram-lightbox-inner { position: absolute; inset: 0; overflow: auto; cursor: auto; }
.nf-diagram-lightbox img {
  display: block; margin: 40px auto 40px;
  width: max(240vw, 860px); height: auto; max-width: none;
  border-radius: var(--radius-md);
}
.nf-diagram-lightbox-close {
  position: absolute; top: 14px; right: 14px; z-index: 61;
  width: 40px; height: 40px; border-radius: var(--radius-full);
  border: 1px solid var(--border-strong); background: var(--iron-800);
  color: var(--fog); font-size: 20px; line-height: 1; cursor: pointer;
}
@media (max-width: 640px) {
  .nf-diagram-band .nf-diagram { display: none; }
  .nf-diagram-zoom, .nf-diagram-static { display: block; }
}

/* ============================================================
   Blog article body (/blog/<slug>) - every construct the markdown
   parser produces, rendered by Blog.jsx. Site-owned: a post brings
   content only, never its own CSS. Every post gets the
   reading-progress rule, and the contents rail and back-to-top unless
   its frontmatter sets `contents: false`.
   ============================================================ */

/* Layout: the article alone runs to 900px inside the 1000px container;
   with a rail the container splits into article column + rail. */
.nf-post { max-width: 900px; min-width: 0; }
.nf-post-layout.has-rail {
  display: grid; grid-template-columns: minmax(0, 1fr) 190px; gap: 56px;
  align-items: start;
}
.nf-post-layout.has-rail .nf-post { max-width: none; }

/* Sections. The first follows the hero directly, so it keeps the band's own
   breathing room; later ones are spaced by margin, which the last
   paragraph's bottom margin collapses into. A long-form post with a rail
   reads as chapters and gets the wider gap. */
.nf-post-section[id] { scroll-margin-top: 84px; } /* outranks section[id] */
.nf-post-section:first-child { padding-top: 8px; }
.nf-post-section + .nf-post-section { margin-top: 40px; }
.has-rail .nf-post-section + .nf-post-section { margin-top: 72px; }
.nf-post h2 {
  font-family: var(--font-display); font-size: clamp(22px, 2.6vw, 30px); font-weight: var(--fw-semibold);
  letter-spacing: -0.02em; line-height: 1.2; color: var(--fog); margin: 0 0 16px;
}
/* Section number, matching the contents rail: 01, 02 ... then A, B for
   appendices. */
.nf-post-num { color: var(--verdigris-400); margin-right: 0.5em; font-variant-numeric: tabular-nums; }
.nf-post p,
.nf-post-list {
  font-family: var(--font-ui); font-size: clamp(16px, 1.4vw, 17px); line-height: var(--lh-loose);
  color: var(--iron-100); margin: 0 0 16px;
}
.nf-post-list { padding-left: 1.4em; }
.nf-post-list li { margin-bottom: 6px; }
.nf-post-list li::marker { color: var(--iron-250); }
.nf-post code {
  font-family: var(--font-mono); font-size: 0.86em; color: var(--fog);
  background: var(--iron-700); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xs); padding: 1px 6px; overflow-wrap: anywhere;
}
.nf-post-link {
  color: var(--fog); text-decoration: underline; text-underline-offset: 3px;
  transition: color var(--dur-slow) var(--ease-out);
}
.nf-post-link:hover { color: var(--verdigris-400); }

/* Mono caps labels: rail heading, quote label, table caption, fact and
   stat labels, kickers. */
.nf-post-rail-label,
.nf-post-quote-label,
.nf-post-table figcaption,
.nf-post-facts dt,
.nf-post-stats dt,
.nf-post-kicker {
  font-family: var(--font-mono); font-size: 11px; font-weight: var(--fw-medium);
  letter-spacing: 0.12em; text-transform: uppercase;
}
.nf-post-kicker { color: var(--verdigris-400); }

/* Subheading, with an optional kicker line above ("Case 1"). */
.nf-post-h3 {
  display: flex; flex-direction: column; gap: 6px; margin: 40px 0 14px;
  font-family: var(--font-display); font-size: 20px; font-weight: var(--fw-semibold);
  letter-spacing: -0.02em; line-height: var(--lh-snug); color: var(--fog);
}

/* Code block. */
.nf-post-code {
  margin: 8px 0 24px; padding: 16px 20px; overflow-x: auto;
  background: var(--iron-900); border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.nf-post .nf-post-code code {
  display: block; background: none; border: 0; padding: 0; overflow-wrap: normal;
  font-size: 13.5px; line-height: 1.6; white-space: pre;
}

/* Facts card: optional kicker + title, then up to six label/value pairs. */
.nf-post-facts {
  margin: 40px 0 20px; padding: 20px 24px;
  background: var(--iron-700); border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.nf-post-facts h3 {
  display: flex; align-items: baseline; gap: 14px; margin: 0 0 16px;
  font-family: var(--font-display); font-size: 24px; font-weight: var(--fw-bold);
  letter-spacing: -0.02em; color: var(--fog);
}
.nf-post-facts dl {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 16px; margin: 0;
}
.nf-post-facts dt, .nf-post-stats dt { color: var(--iron-200); margin-bottom: 4px; }
.nf-post-facts dd {
  margin: 0; font-family: var(--font-ui); font-size: 16px; font-weight: var(--fw-medium); color: var(--fog);
}

/* Key figures. */
.nf-post-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px;
  margin: 28px 0 8px;
}
.nf-post-stats > div {
  padding: 18px 20px; background: var(--iron-700);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.nf-post-stats dd {
  margin: 6px 0 0; font-family: var(--font-display); font-size: clamp(28px, 3.4vw, 38px);
  font-weight: var(--fw-bold); letter-spacing: var(--tracking-tightest); line-height: 1; color: var(--fog);
}

/* Figures open full size in the lightbox. A captioned figure is framed; an
   uncaptioned one is plain artwork straight on the band's ground. */
.nf-post-zoom { display: block; cursor: zoom-in; }
.nf-post-zoom img { display: block; width: 100%; height: auto; }
.nf-post-plain { margin: 6px 0 24px; }
.nf-post-figure { margin: 28px 0 32px; }
.nf-post-figure .nf-post-zoom {
  border-radius: var(--radius-md); overflow: hidden;
  background: var(--iron-700); border: 1px solid var(--border-subtle);
  transition: border-color var(--dur-slow) var(--ease-out);
}
.nf-post-figure .nf-post-zoom:hover,
.nf-post-figure .nf-post-zoom:focus-visible { border-color: var(--verdigris-400); }
.nf-post-figure figcaption {
  margin-top: 10px; font-family: var(--font-ui); font-size: 14px; line-height: var(--lh-body);
  color: var(--iron-200);
}

/* Gallery: framed figures one at a time on a stage shaped like the most
   common slide; other shapes sit contained on the raised ground. Inactive
   slides stay laid out (so lazy loading sees them) but hidden. A shuffled gallery
   hides its contents until the browser has drawn the order; without JS,
   or when the animation loop never runs, it shows source order. */
.nf-gallery-stage {
  position: relative; overflow: hidden; border-radius: var(--radius-md);
  background: var(--iron-700); border: 1px solid var(--border-subtle);
  transition: border-color var(--dur-slow) var(--ease-out);
}
.nf-gallery-stage:hover,
.nf-gallery-stage:focus-within { border-color: var(--verdigris-400); }
.nf-gallery .nf-gallery-slide {
  position: absolute; inset: 0; border: 0; border-radius: 0; background: none;
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-slow) var(--ease-out), visibility 0s linear var(--dur-slow);
}
.nf-gallery .nf-gallery-slide.is-active {
  opacity: 1; visibility: visible;
  transition: opacity var(--dur-slow) var(--ease-out), visibility 0s;
}
.nf-gallery-slide img { height: 100%; object-fit: contain; }
/* Controls sit under the stage, never over the artwork: marks, counter,
   then prev/next. */
.nf-gallery-arrow {
  flex: none; width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center; padding: 0;
  background: var(--iron-700); border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  color: var(--fog); font-family: var(--font-ui); font-size: 18px; line-height: 1; cursor: pointer;
  transition: color var(--dur-slow) var(--ease-out), border-color var(--dur-slow) var(--ease-out);
}
.nf-gallery-arrow:hover,
.nf-gallery-arrow:focus-visible { color: var(--verdigris-400); border-color: var(--verdigris-400); }
.nf-gallery-foot {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
}
.nf-gallery-foot .nf-gallery-count { margin: 0 4px 0 8px; }
/* The marks share one row and narrow to fit however many slides there are;
   the counter never wraps. */
.nf-gallery-dots { display: flex; flex: 1 1 auto; min-width: 0; gap: 6px; }
.nf-gallery-dots button {
  flex: 0 1 22px; min-width: 0; height: 20px; padding: 0; background: none; border: 0; cursor: pointer;
}
.nf-gallery-dots button::before {
  content: ''; display: block; height: 3px; border-radius: var(--radius-xs);
  background: var(--iron-400); transition: background var(--dur-slow) var(--ease-out);
}
.nf-gallery-dots button:hover::before { background: var(--iron-200); }
.nf-gallery-dots button[aria-current]::before { background: var(--verdigris-400); }
.nf-gallery-count {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.12em; color: var(--iron-200);
  font-variant-numeric: tabular-nums; white-space: nowrap; flex: none;
}
.nf-js .nf-gallery-pending > * { opacity: 0; }
.nf-no-anim .nf-gallery-pending > * { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .nf-gallery .nf-gallery-slide,
  .nf-gallery .nf-gallery-slide.is-active { transition: none; }
}
@media (max-width: 560px) {
  .nf-gallery-dots { gap: 3px; }
}

/* Quote, with an optional label ("Translation"). */
.nf-post-quote {
  margin: 8px 0 28px; padding: 4px 0 4px 22px;
  border-left: 2px solid var(--verdigris-400);
}
.nf-post-quote-label { color: var(--verdigris-400); margin-bottom: 10px; }
.nf-post .nf-post-quote p { color: var(--iron-200); margin-bottom: 8px; }

/* Data tables. Inline code in a cell reads as plain mono and never wraps
   (identifiers, addresses). */
.nf-post-table { margin: 28px 0 32px; }
.nf-post-table figcaption { color: var(--verdigris-400); margin-bottom: 12px; }
.nf-post-table-scroll {
  overflow-x: auto; background: var(--iron-700);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.nf-post-table table { width: 100%; border-collapse: collapse; }
.nf-post-table th {
  text-align: left; padding: 12px 16px; background: var(--iron-900);
  font-family: var(--font-mono); font-size: 10.5px; font-weight: var(--fw-medium);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--iron-200);
}
.nf-post-table td {
  padding: 11px 16px; vertical-align: top;
  font-family: var(--font-ui); font-size: 14px; line-height: var(--lh-body); color: var(--iron-100);
}
.nf-post-table tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--iron-800) 55%, transparent); }
.nf-post-table td:first-child { color: var(--fog); font-weight: var(--fw-medium); min-width: 150px; }
.nf-post-table td code { background: none; border: 0; padding: 0; white-space: nowrap; overflow-wrap: normal; }

/* Back to top, at the end of a post with a contents rail. */
.nf-post-top {
  display: block; margin-top: 36px; padding: 0; background: none; border: 0; cursor: pointer;
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--iron-200); transition: color var(--dur-slow) var(--ease-out);
}
.nf-post-top:hover { color: var(--verdigris-400); }

/* Reading progress: a thin accent rule under the sticky nav. */
.nf-post-progress {
  position: fixed; top: 60px; left: 0; right: 0; height: 2px; z-index: 19;
  pointer-events: none;
}
.nf-post-progress span {
  display: block; height: 100%; background: var(--verdigris-400);
  transform-origin: left center;
}

/* Contents rail: follows the reader; the current section is marked. */
.nf-post-rail { position: sticky; top: 100px; padding-top: 10px; }
.nf-post-rail-label { color: var(--iron-250); margin-bottom: 14px; }
.nf-post-rail-nav { display: flex; flex-direction: column; }
.nf-post-rail-nav a {
  display: flex; gap: 10px; padding: 7px 0 7px 14px; margin-left: -1px;
  border-left: 1px solid var(--border-strong);
  font-family: var(--font-ui); font-size: 13px; line-height: 1.35;
  color: var(--iron-200); text-decoration: none;
  transition: color var(--dur-slow) var(--ease-out), border-color var(--dur-slow) var(--ease-out);
}
.nf-post-rail-nav a span {
  flex: 0 0 18px; font-family: var(--font-mono); font-size: 11px; color: var(--iron-250);
  padding-top: 1px;
}
.nf-post-rail-nav a:hover { color: var(--fog); }
.nf-post-rail-nav a.is-active { color: var(--fog); border-left-color: var(--verdigris-400); }
.nf-post-rail-nav a.is-active span { color: var(--verdigris-400); }

/* Lightbox: a figure at full size over the scrim. */
.nf-post-lightbox {
  position: fixed; inset: 0; z-index: 40; display: flex;
  align-items: center; justify-content: center; padding: 48px 24px;
  background: var(--overlay-scrim); backdrop-filter: blur(6px); cursor: zoom-out;
}
.nf-post-lightbox img {
  max-width: min(1600px, 100%); max-height: 100%; width: auto; height: auto;
  border-radius: var(--radius-md); border: 1px solid var(--border-strong); background: var(--iron-700);
  cursor: default;
}
.nf-post-lightbox button {
  position: absolute; top: 16px; right: 20px; width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  background: var(--iron-700); border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  color: var(--fog); font-family: var(--font-ui); font-size: 22px; line-height: 1; cursor: pointer;
}
.nf-post-lightbox button:hover { border-color: var(--verdigris-400); color: var(--verdigris-400); }

/* Narrow screens: the rail folds away and the article takes the width. */
@media (max-width: 960px) {
  .nf-post-layout.has-rail { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .nf-post-rail { display: none; }
}
@media (max-width: 560px) {
  .nf-post-facts dl { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ============================================================
   Sandbox diagram band (homepage, between hero and proof) - the
   founder-approved embed art, transplanted verbatim into
   SandboxDiagram.jsx (dangerouslySetInnerHTML, so SSR and hydration
   emit byte-identical stage markup) with its fit script as a React
   effect. Every color was mapped to its design token at transplant;
   the two flash states are color-mix derivations (verdigris/ember
   over the raised surface) because they are not tokens themselves.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .nf-diagram * { animation-duration: 0s !important; animation-iteration-count: 1 !important; }
}

@keyframes nf-pk1{0%{left:633px;top:286px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:286px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--verdigris-300)}10.5%{left:1194px;top:466px}12%{left:1194px;top:250px}13%{left:1233px;top:250px;opacity:1}14%{opacity:0}100%{left:1233px;top:250px;opacity:0;background:var(--verdigris-300)}}
@keyframes nf-pk2{0%{left:633px;top:286px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:286px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--ember)}10.5%{left:1194px;top:466px}12%{left:1194px;top:322px}13%{left:1233px;top:322px;opacity:1}14%{opacity:0}100%{left:1233px;top:322px;opacity:0;background:var(--ember)}}
@keyframes nf-pk3{0%{left:633px;top:406px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:406px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--verdigris-300)}10.5%{left:1194px;top:466px}12%{left:1194px;top:394px}13%{left:1233px;top:394px;opacity:1}14%{opacity:0}100%{left:1233px;top:394px;opacity:0;background:var(--verdigris-300)}}
@keyframes nf-pk4{0%{left:633px;top:406px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:406px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--ember)}10.5%{left:1194px;top:466px}12%{left:1194px;top:466px}13%{left:1233px;top:466px;opacity:1}14%{opacity:0}100%{left:1233px;top:466px;opacity:0;background:var(--ember)}}
@keyframes nf-pk5{0%{left:633px;top:526px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:526px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--verdigris-300)}10.5%{left:1194px;top:466px}12%{left:1194px;top:538px}13%{left:1233px;top:538px;opacity:1}14%{opacity:0}100%{left:1233px;top:538px;opacity:0;background:var(--verdigris-300)}}
@keyframes nf-pk6{0%{left:633px;top:526px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:526px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--ember)}10.5%{left:1194px;top:466px}12%{left:1194px;top:610px}13%{left:1233px;top:610px;opacity:1}14%{opacity:0}100%{left:1233px;top:610px;opacity:0;background:var(--ember)}}
@keyframes nf-pk7{0%{left:633px;top:646px;opacity:0;background:var(--iron-250)}1%{opacity:1}3%{left:677px;top:646px}5%{left:677px;top:466px}8%{left:1038px;top:466px;background:var(--iron-250)}9.5%{left:1038px;top:466px;background:var(--fog)}10.5%{left:1194px;top:466px}12%{left:1194px;top:682px}13%{left:1233px;top:682px;opacity:1}14%{opacity:0}100%{left:1233px;top:682px;opacity:0;background:var(--fog)}}
/* Card flashes pulse the border colour only: the former background tints
   (color-mix over iron-700 / solid iron-500) lightened the surface beneath
   the ALLOW/BLOCK/INSPECT labels and pushed them under WCAG AA for the
   seconds the flash ran. Borders carry the state signal; labels now sit on
   the passing iron-700 ground in every keyframe state. */
@keyframes nf-flashAllow{0%,12.5%{border-color:var(--iron-500);background:var(--iron-700)}14%,30%{border-color:var(--verdigris-300)}42%,100%{border-color:var(--iron-500);background:var(--iron-700)}}
@keyframes nf-flashBlock{0%,12.5%{border-color:var(--iron-500);background:var(--iron-700)}14%,30%{border-color:var(--ember)}42%,100%{border-color:var(--iron-500);background:var(--iron-700)}}
@keyframes nf-flashInspect{0%,12.5%{border-color:var(--iron-500);background:var(--iron-700)}14%,30%{border-color:var(--fog)}42%,100%{border-color:var(--iron-500);background:var(--iron-700)}}
@keyframes nf-pulsePolicy{0%,50%,72%,100%{box-shadow:0 0 0 0 transparent}58%{box-shadow:0 0 0 8px color-mix(in srgb, var(--verdigris-400) 28%, transparent)}}
@keyframes nf-pulseEgress{0%,36%,52%,100%{box-shadow:0 0 0 0 transparent}42%{box-shadow:0 0 0 10px color-mix(in srgb, var(--verdigris-400) 30%, transparent)}}
@keyframes nf-blink{0%,100%{opacity:1}50%{opacity:0.3}}

/* Footer row: the three zones (brand / link columns / offices) flow as one
   grid row on desktop, the offices pair hugging the right edge. Below
   1000px the zones stack — the pair is too wide to share a row with the
   centered link columns and the brand track starts strangling — and the
   offices left-hug to match every other stacked footer row. On phones the
   pair's inline addresses turn into two equal columns with the divider
   centered. ft.offices/ft.inner ship as INLINE styles, so these overrides
   need !important to win the cascade at all. */
@media (max-width: 1000px) {
  .nf-footer-inner { grid-template-columns: 1fr !important; }
  .nf-footer-offices { justify-self: start !important; }
}

@media (max-width: 640px) {
  .nf-footer-offices {
    display: grid !important;
    grid-template-columns: 1fr 2px 1fr !important;
    gap: 20px !important;
    justify-items: stretch;
  }
}
