﻿/* ==========================================================================
   NSi4 brand layer
   Values, families, shapes and usage rules: /knowledge/design-system
   (NSi4 Design System, version 6 "midnight ink", signed off 2026-09-30).

   Layers, in cascade order:
     1. Tokens          every value the system names, on :root
     2. Fonts           self-hosted IBM Plex, metric-matched fallbacks
     3. Worlds          one body class per product page sets the accent
     4. Base            the .nsi4 wrapper
     5. Type scale      the ten styles in the system's Parts
     6. Components      the system's Composition
     7. Page layout     the shells the rebuilt pages compose from
     8. Legacy          the pre-redesign class names, kept working
     9. Icons           the Lucide sprite
    10. Phoenix         scoped overrides so Phoenix lands on tokens
    11. Motion guard
    12. View transitions   page-to-page crossfade, Chromium only

   No hex literal appears below layer 1. This sheet is linked after
   theme.min.css, so an equal-specificity rule here wins on source order.
   ========================================================================== */

/* ==========================================================================
   1. Tokens
   ========================================================================== */
:root {
	color-scheme: dark;

	/* Surfaces */
	--nsi4-ground: #0A0D14;
	--nsi4-surface: #11151E;
	--nsi4-raised: #181C27;
	--nsi4-hairline: rgba(255, 255, 255, 0.08);
	--nsi4-control-border: #6A7080;

	/* The one light surface in the system, and the line on it. The colour
	   logos are artwork for paper: the press kit previews them on paper and
	   nowhere else does the site leave the ground. */
	--nsi4-paper: #F4F5F7;
	--nsi4-paper-line: rgba(10, 13, 20, 0.16);

	/* Text levels */
	--nsi4-text: #EDEFF3;
	--nsi4-text-2: #A0A6B3;
	--nsi4-text-3: #838A98;
	--nsi4-focus: var(--nsi4-text);

	/* Brand */
	--nsi4-brand-cyan: #20B7E0;
	--nsi4-brand-purple: #8B2092;
	--nsi4-cta-start: #0E7DB8;
	--nsi4-cta-end: #B02DC0;
	--nsi4-on-cta: #FFFFFF;

	/* Product worlds */
	--nsi4-world-wrs: #FF6B2C;
	--nsi4-world-trinity: #2BE38A;
	--nsi4-world-fromscratch: #4D7CFE;
	--nsi4-world-tycho: #22D3EE;
	--nsi4-world-alivemd: #F0609E;
	--nsi4-on-world: var(--nsi4-ground);

	/* Accent: the corporate shell is the quiet default */
	--nsi4-accent: var(--nsi4-text);

	/* Evidence-component colours from the PO-04 mockup: named here so every
	   hex stays in this block. */
	--nsi4-redaction: #2A3040;
	--nsi4-diff-minus: #F08080;

	/* The home hero's task field, from the board itself: the indigo its legend
	   swatches are drawn in, and the border around its tooltip. Neither is a
	   brand colour, so both are named here and nowhere else. */
	--nsi4-field-key: #5061ad;
	--nsi4-field-line: rgba(255, 255, 255, 0.12);

	/* Spacing. Index is the multiple of 4px (8px grid with a 4px half-step):
	   space-6 = 24, space-8 = 32, space-12 = 48, space-16 = 64, space-24 = 96,
	   space-40 = 160. */
	--nsi4-space-1: 4px;
	--nsi4-space-2: 8px;
	--nsi4-space-3: 12px;
	--nsi4-space-4: 16px;
	--nsi4-space-6: 24px;
	--nsi4-space-8: 32px;
	--nsi4-space-12: 48px;
	--nsi4-space-16: 64px;
	--nsi4-space-24: 96px;
	--nsi4-space-40: 160px;

	/* Radii. No pills. */
	--nsi4-radius-sm: 4px;
	--nsi4-radius-md: 8px;
	--nsi4-radius-lg: 12px;

	/* Depth. The only shadow in the system. */
	--nsi4-highlight-top: inset 0 1px 0 rgba(255, 255, 255, 0.04);

	/* Layout */
	--nsi4-content-max: 1200px;
	--nsi4-gutter: 16px;
	/* A sticky rail or guide sits 96px from the viewport top, clear of the
	   64px nav. The case guide reads it today; anything else that sticks under
	   the nav is meant to read the same offset rather than restate it. */
	--nsi4-sticky-top: 96px;
	/* Section spacing. Full sections take --nsi4-section-pad; a section whose
	   body measures under 240px tall at 1280px takes --nsi4-section-pad-compact:
	   56/40 on a phone, the clamp and 56 from 900px (media query below). */
	--nsi4-section-pad: 56px;
	--nsi4-section-pad-compact: 40px;
	--nsi4-measure: 50ch;
	--nsi4-touch-min: 44px;
	/* Hero top padding: the hero copy's top edge sits this far below the
	   band's top. .nsi4-hero's own padding-block start reads it, and so do
	   the hero texture's mask and the Trinity ticker strip — the two must
	   not paint inside the copy. Moves to 120px with the two-column hero
	   (media query below), and the three must move together. */
	--nsi4-hero-pad-top: var(--nsi4-space-16);

	/* Motion */
	--nsi4-dur: 160ms;
	--nsi4-ease: cubic-bezier(0.2, 0.8, 0.2, 1);

	/* Families */
	--nsi4-font-sans: "IBM Plex Sans", "IBM Plex Sans Fallback", system-ui, -apple-system, "Segoe UI", sans-serif;
	--nsi4-font-mono: "IBM Plex Mono", "IBM Plex Mono Fallback", ui-monospace, "SF Mono", Consolas, monospace;

	/* Type scale: size / line / weight / tracking.
	   display-xl and display-l are the two responsive styles; the desktop
	   half is applied in the media query below. */
	--nsi4-fs-display-xl: 40px;
	--nsi4-lh-display-xl: 44px;
	--nsi4-fw-display-xl: 600;
	--nsi4-ls-display-xl: -0.02em;

	--nsi4-fs-display-l: 36px;
	--nsi4-lh-display-l: 42px;
	--nsi4-fw-display-l: 600;
	--nsi4-ls-display-l: -0.02em;

	--nsi4-fs-heading-2: 32px;
	--nsi4-lh-heading-2: 40px;
	--nsi4-fw-heading-2: 600;
	--nsi4-ls-heading-2: -0.01em;

	--nsi4-fs-heading-3: 24px;
	--nsi4-lh-heading-3: 32px;
	--nsi4-fw-heading-3: 600;
	--nsi4-ls-heading-3: 0;

	--nsi4-fs-lede: 20px;
	--nsi4-lh-lede: 30px;
	--nsi4-fw-lede: 400;
	--nsi4-ls-lede: 0;

	--nsi4-fs-body: 17px;
	--nsi4-lh-body: 27px;
	--nsi4-fw-body: 400;
	--nsi4-ls-body: 0;

	--nsi4-fs-small: 15px;
	--nsi4-lh-small: 22px;
	--nsi4-fw-small: 400;
	--nsi4-ls-small: 0;

	--nsi4-fs-eyebrow: 13px;
	--nsi4-lh-eyebrow: 16px;
	--nsi4-fw-eyebrow: 500;
	--nsi4-ls-eyebrow: 0.08em;

	--nsi4-fs-data: 15px;
	--nsi4-lh-data: 22px;
	--nsi4-fw-data: 400;
	--nsi4-ls-data: 0;

	--nsi4-fs-data-xl: 40px;
	--nsi4-lh-data-xl: 44px;
	--nsi4-fw-data-xl: 500;
	--nsi4-ls-data-xl: -0.01em;

	/* Texture defaults — each world overrides these (layer 3) */
	--nsi4-texture-image: none;
	--nsi4-texture-size: auto;
	--nsi4-texture-opacity: 0.14;
}

@media (min-width: 480px) {
	:root {
		--nsi4-fs-display-xl: 64px;
		--nsi4-lh-display-xl: 68px;
		--nsi4-fs-display-l: 48px;
		--nsi4-lh-display-l: 54px;
	}
}

@media (min-width: 768px) {
	:root {
		--nsi4-gutter: 24px;
	}
}

@media (min-width: 900px) {
	:root {
		--nsi4-section-pad: clamp(72px, 8vw, 112px);
		--nsi4-section-pad-compact: 56px;
	}
}

/* The hero takes its tall desktop padding with its two-column split
   (page layout, below): 120px over the copy. */
@media (min-width: 1000px) {
	:root {
		--nsi4-hero-pad-top: 120px;
	}
}

/* ==========================================================================
   2. Fonts
   IBM Plex Sans 400/500/600 and IBM Plex Mono 400/500, self-hosted as woff2
   and split by unicode-range so a page fetches only the subset it renders.
   ========================================================================== */
@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-ext-400-normal.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-ext-500-normal.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Sans";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("../fonts/ibm-plex-sans-latin-ext-600-normal.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-latin-ext-400-normal.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/ibm-plex-mono-latin-ext-500-normal.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Metric-matched fallbacks, so a swap to the webfont shifts nothing.
   Overrides derived from the two fonts' own tables (IBM Plex Sans
   xWidthAvg 451/1000 against Arial 913/2048; IBM Plex Mono 600/1000
   against Courier New 1229/2048), not estimated. */
@font-face {
	font-family: "IBM Plex Sans Fallback";
	src: local("Arial"), local("ArialMT");
	ascent-override: 101.3184%;
	descent-override: 27.183%;
	line-gap-override: 0%;
	size-adjust: 101.1663%;
}

@font-face {
	font-family: "IBM Plex Mono Fallback";
	src: local("Courier New"), local("CourierNewPSMT");
	ascent-override: 102.5167%;
	descent-override: 27.5045%;
	line-gap-override: 0%;
	size-adjust: 99.9837%;
}

/* ==========================================================================
   3. Product worlds
   One class sets the accent and the hero texture. Quiet shell, loud
   products: the accent marks eyebrows, callout markers, the world button and
   in-text links; it never fills a section background.
   ========================================================================== */
.world-wrs {
	--nsi4-accent: var(--nsi4-world-wrs);
	--nsi4-texture-image: radial-gradient(circle, var(--nsi4-accent) 1px, transparent 1.5px);
	--nsi4-texture-size: 6px 6px;
}

.world-trinity {
	--nsi4-accent: var(--nsi4-world-trinity);
	--nsi4-texture-image: linear-gradient(to bottom, var(--nsi4-accent) 1px, transparent 1px);
	--nsi4-texture-size: 100% 8px;
}

.world-fromscratch {
	--nsi4-accent: var(--nsi4-world-fromscratch);
	--nsi4-texture-image: radial-gradient(circle, var(--nsi4-accent) 1.2px, transparent 1.6px);
	--nsi4-texture-size: 16px 16px;
}

.world-tycho {
	--nsi4-accent: var(--nsi4-world-tycho);
	--nsi4-texture-image: linear-gradient(to bottom, var(--nsi4-accent) 1px, transparent 1px), linear-gradient(to right, var(--nsi4-accent) 1px, transparent 1px);
	--nsi4-texture-size: 24px 24px, 24px 24px;
	--nsi4-texture-opacity: 0.1;
}

.world-alivemd {
	--nsi4-accent: var(--nsi4-world-alivemd);
	--nsi4-texture-image: linear-gradient(to bottom, transparent 26px, var(--nsi4-accent) 26px, var(--nsi4-accent) 27px);
	--nsi4-texture-size: 100% 27px;
}

/* ==========================================================================
   4. Base
   ========================================================================== */
/* An in-page jump lands at the sticky line, not at the top of the viewport:
   --nsi4-sticky-top is where the rails and the case guide already sit, so a
   reader who follows a link arrives where a reader who scrolls arrives.
   theme.min.css sets scroll-margin-top on every [id] and that value is
   83.2px, so it has to go before this can be the only offset - the two
   stack, and with the id rule left in place this rule alone lands every
   jump at 179.2px. Both numbers measured at 1280. */
html {
	scroll-padding-top: var(--nsi4-sticky-top);
}

:root [id] {
	scroll-margin-top: 0;
}

.nsi4 {
	background: var(--nsi4-ground);
	color: var(--nsi4-text);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

.nsi4 :focus-visible {
	outline: 2px solid var(--nsi4-focus);
	outline-offset: 2px;
}

/* Body copy breaks greedily by default, which leaves paragraphs ending on a
   single short word. `pretty` spends a little of the paragraph's own slack to
   avoid that; the headings keep `balance`, which is the same idea at their
   size. Set on the shell's own text elements rather than on `.nsi4`, so a
   component that sets its own wrap (the ticker's items, the ledger's terms)
   keeps it. */
.nsi4 p,
.nsi4 li,
.nsi4 dd,
.nsi4 figcaption {
	text-wrap: pretty;
}

/* A compound the writer does not want broken at its hyphen: 'in-game' reads
   as two words once the line breaks after the hyphen. Applied to the word
   alone, never to the paragraph it sits in. */
.nsi4-nowrap {
	white-space: nowrap;
}

.nsi4-shell {
	max-width: var(--nsi4-content-max);
	margin-inline: auto;
	padding-inline: var(--nsi4-gutter);
}

.nsi4-measure {
	max-width: var(--nsi4-measure);
}

.nsi4-section {
	padding-block: var(--nsi4-section-pad);
}

/* ==========================================================================
   5. Type scale
   ========================================================================== */
.nsi4-type-display-xl {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-display-xl);
	line-height: var(--nsi4-lh-display-xl);
	font-weight: var(--nsi4-fw-display-xl);
	letter-spacing: var(--nsi4-ls-display-xl);
	text-wrap: balance;
}

.nsi4-type-display-l {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-display-l);
	line-height: var(--nsi4-lh-display-l);
	font-weight: var(--nsi4-fw-display-l);
	letter-spacing: var(--nsi4-ls-display-l);
	text-wrap: balance;
}

.nsi4-type-heading-2 {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-2);
	line-height: var(--nsi4-lh-heading-2);
	font-weight: var(--nsi4-fw-heading-2);
	letter-spacing: var(--nsi4-ls-heading-2);
	text-wrap: balance;
}

.nsi4-type-heading-3 {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-3);
	line-height: var(--nsi4-lh-heading-3);
	font-weight: var(--nsi4-fw-heading-3);
	letter-spacing: var(--nsi4-ls-heading-3);
}

.nsi4-type-lede {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-lede);
	line-height: var(--nsi4-lh-lede);
	font-weight: var(--nsi4-fw-lede);
	letter-spacing: var(--nsi4-ls-lede);
	color: var(--nsi4-text-2);
	max-width: var(--nsi4-measure);
}

.nsi4-type-body {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	font-weight: var(--nsi4-fw-body);
	letter-spacing: var(--nsi4-ls-body);
	max-width: var(--nsi4-measure);
}

.nsi4-type-small {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	font-weight: var(--nsi4-fw-small);
	letter-spacing: var(--nsi4-ls-small);
}

.nsi4-type-eyebrow {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-type-data {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data);
	line-height: var(--nsi4-lh-data);
	font-weight: var(--nsi4-fw-data);
	letter-spacing: var(--nsi4-ls-data);
}

.nsi4-type-data-xl {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data-xl);
	line-height: var(--nsi4-lh-data-xl);
	font-weight: var(--nsi4-fw-data-xl);
	letter-spacing: var(--nsi4-ls-data-xl);
	font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   6. Components
   ========================================================================== */

/* Buttons */
.nsi4-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--nsi4-space-2);
	min-height: var(--nsi4-touch-min);
	padding: 0 20px;
	border: 1px solid transparent;
	border-radius: var(--nsi4-radius-md);
	font-family: var(--nsi4-font-sans);
	font-size: 16px;
	font-weight: 600;
	line-height: 20px;
	text-decoration: none;
	cursor: pointer;
	transition: filter var(--nsi4-dur) var(--nsi4-ease), transform var(--nsi4-dur) var(--nsi4-ease), border-color var(--nsi4-dur) var(--nsi4-ease), background-color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-btn:active {
	transform: translateY(1px);
}

.nsi4-btn--primary {
	background: linear-gradient(90deg, var(--nsi4-cta-start), var(--nsi4-cta-end));
	/* The shorthand leaves background-origin at padding-box, so under the 1px
	   transparent border the gradient re-enters from its far end and paints a
	   1px sliver on each edge. Sized to the border box, the stops run edge to
	   edge at 1x and 2x. */
	background-origin: border-box;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
	color: var(--nsi4-on-cta);
}

.nsi4-btn--primary:hover {
	filter: brightness(1.12);
}

.nsi4-btn--secondary {
	background: transparent;
	color: var(--nsi4-text);
	border-color: var(--nsi4-control-border);
}

.nsi4-btn--secondary:hover {
	border-color: var(--nsi4-text);
	background: var(--nsi4-raised);
}

.nsi4-btn--world {
	background: var(--nsi4-accent);
	color: var(--nsi4-on-world);
}

.nsi4-btn--world:hover {
	filter: brightness(1.08);
}

.nsi4-btn[aria-disabled="true"] {
	opacity: 0.45;
	pointer-events: none;
}

/* The mirror's Rules put in-text links under the accent; the corporate
   accent is the text colour, so this is the same colour there and the world
   accent on a product page. */
.nsi4-link {
	color: var(--nsi4-accent);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	text-decoration-color: var(--nsi4-control-border);
}

.nsi4-link:hover {
	color: var(--nsi4-text);
	text-decoration-color: var(--nsi4-accent);
}

/* Eyebrow and badge */
.nsi4-eyebrow {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-eyebrow--world {
	color: var(--nsi4-accent);
}

/* Brand strings keep the casing they are written in — fromScratch, AliveMD,
   NSi4 — so the eyebrow's uppercase does not restate them. Applied to the
   five product hero eyebrows and the home hero's; section eyebrows ("01
   Problem") keep their uppercase. The class does not depend on the eyebrow,
   so a dt or dd naming a product can take the same one. */
.nsi4-keep-case {
	text-transform: none;
}

/* The home eyebrow names two cities, and the copy carries the break between
   them. Where the string fits on one line the break is not there and the
   middot joins the two cities; where it does not, the middot goes with the
   break so neither line starts or ends on one. The type is the same at every
   width — the line count comes from the copy, not from the size. */
.nsi4-eyebrow__break {
	display: none;
}

@media (max-width: 419.98px) {
	.nsi4-eyebrow__sep {
		display: none;
	}

	.nsi4-eyebrow__break {
		display: inline;
	}
}

/* The chip never wraps: a status broken over two lines in the ledger's narrow
   status column reads as two words on two lines, and leaves the dot standing
   alone on the first. The status text rides the uppercase; a brand name inside
   it takes .nsi4-keep-case on its own span. */
.nsi4-badge {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: var(--nsi4-space-1) var(--nsi4-space-2);
	border: 1px solid var(--nsi4-control-border);
	border-radius: var(--nsi4-radius-sm);
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: 500;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	white-space: nowrap;
	color: var(--nsi4-text-2);
}

.nsi4-badge__dot {
	width: 8px;
	height: 8px;
	border-radius: 2px;
	background: var(--nsi4-accent);
}

/* Crop frame — an annotated 2x crop with numbered callouts */
.nsi4-crop {
	margin: 0;
	display: grid;
	gap: var(--nsi4-space-4);
}

.nsi4-crop__frame {
	position: relative;
	overflow: hidden;
	border-radius: var(--nsi4-radius-lg);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	box-shadow: var(--nsi4-highlight-top);
}

.nsi4-crop__frame img {
	display: block;
	width: 100%;
	height: auto;
}

/* A phone crop arrives in a <picture> in front of the image.  Left inline, the
   image's own width would resolve against an inline box rather than the frame. */
.nsi4-crop__frame picture {
	display: block;
}

/* The shot opens full size.  The link wraps the image and the mark and nothing
   else: the notes stay ordinary selectable text below it, and are not read as
   the link's name. */
.nsi4-crop__link {
	display: block;
	cursor: zoom-in;
}

/* The link fills the frame, so a ring drawn on it would sit outside the frame and
   be clipped by the frame's own overflow.  The frame draws it instead, one rule
   down, on the same `:focus-visible` the shell rings everything with. */
.nsi4-crop__link:focus-visible {
	outline: none;
}

.nsi4-crop__frame:has(.nsi4-crop__link:focus-visible) {
	outline: 2px solid var(--nsi4-focus);
	outline-offset: 2px;
}

/* The enlarge mark: a 32px square 8px inside the frame's bottom-right corner, on
   the ground at 72% so the shot under it still reads (a literal, like
   --nsi4-paper-line). */
.nsi4-crop__mark {
	position: absolute;
	right: 8px;
	bottom: 8px;
	width: 32px;
	height: 32px;
	display: grid;
	place-items: center;
	border-radius: var(--nsi4-radius-sm);
	background: rgba(10, 13, 20, 0.72);
	color: var(--nsi4-text-2);
}

.nsi4-crop__mark .nsi4-icon {
	font-size: 16px;
}

/* Viewer — the shot full size, over the page it was clicked from.  The dialog
   is built by shot-viewer.js, so nothing here is markup the pages carry. */
.nsi4-viewer {
	position: fixed;
	inset: 0;
	/* The UA sizes a dialog to fit its content and centres it with auto margins.
	   Left to itself it would shrink to the shot and stop being a viewer; the
	   insets above only fill once width and height are given back as auto. */
	width: auto;
	height: auto;
	max-width: none;
	max-height: none;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	overflow: hidden;
}

/* The UA hides a dialog that is not open. An author `display` outranks a UA
   rule whatever its specificity, so a `display` here written unscoped would put
   the viewer over the page from the moment the script appends it; binding it to
   [open] leaves the closed state to the UA and needs nothing beside it. */
.nsi4-viewer[open] {
	display: grid;
	place-items: center;
}

.nsi4-viewer::backdrop {
	background: rgba(10, 13, 20, 0.92);
}

/* The viewport less 24px on every side.  Only the two maxima are declared: the
   width and height attributes the markup carries are left to speak for
   themselves, because a declared `width: auto` would beat them and the box
   would sit at nothing until the file arrived.  From there the browser scales
   the shot down by its own aspect ratio, and never up past its own pixels. */
.nsi4-viewer__shot {
	display: block;
	max-width: calc(100% - 48px);
	max-height: calc(100% - 48px);
}

/* Close: a touch target, pinned to the viewport's top-right so it stays put
   while a phone pans the shot underneath it. */
.nsi4-viewer__close {
	position: fixed;
	top: 16px;
	right: 16px;
	width: var(--nsi4-touch-min);
	height: var(--nsi4-touch-min);
	display: grid;
	place-items: center;
	padding: 0;
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-md);
	background: var(--nsi4-raised);
	color: var(--nsi4-text);
	cursor: pointer;
}

.nsi4-viewer__close .nsi4-icon {
	font-size: 20px;
}

.nsi4-viewer__close:focus-visible {
	outline: 2px solid var(--nsi4-focus);
	outline-offset: 2px;
}

/* Below 640px the shot is read at its own pixels — a full screenshot carries
   detail a phone screen has no business shrinking to fit — and the viewer
   scrolls on both axes so it can be panned, opening at the top-left corner
   where the shot begins.  Centring an image wider than the screen would put
   both of its edges out of reach. */
@media (max-width: 639.98px) {
	.nsi4-viewer[open] {
		place-items: start start;
	}

	.nsi4-viewer {
		overflow: auto;
	}

	.nsi4-viewer__shot {
		max-width: none;
		max-height: none;
	}
}

/* The viewer fades in over the page it came from, and only in: the shot is
   already standing, so anything on the way out would be the page showing
   through a backdrop that is still on its way off. */
@media (prefers-reduced-motion: no-preference) {
	.nsi4-viewer[open] {
		animation: nsi4-viewer-in var(--nsi4-dur) var(--nsi4-ease);
	}
}

@keyframes nsi4-viewer-in {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

.nsi4-crop__marker {
	position: absolute;
	transform: translate(-50%, -50%);
	width: 24px;
	height: 24px;
	display: grid;
	place-items: center;
	border-radius: var(--nsi4-radius-sm);
	border: 2px solid var(--nsi4-ground);
	background: var(--nsi4-accent);
	color: var(--nsi4-on-world);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	font-weight: 500;
	line-height: 1;
}

.nsi4-crop__notes {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: var(--nsi4-space-3);
	counter-reset: note;
}

/* The marker is taken out of flow so the label and the body stay one text run:
   in flow, a grid makes the <b> and the text after it two separate items. */
.nsi4-crop__notes li {
	position: relative;
	padding-left: calc(24px + var(--nsi4-space-3));
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

.nsi4-crop__notes li::before {
	position: absolute;
	top: 0;
	left: 0;
	counter-increment: note;
	content: counter(note);
	width: 24px;
	height: 24px;
	display: grid;
	place-items: center;
	border-radius: var(--nsi4-radius-sm);
	border: 1px solid var(--nsi4-accent);
	color: var(--nsi4-accent);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	font-weight: 500;
	line-height: 1;
}

.nsi4-crop__notes b {
	color: var(--nsi4-text);
	font-weight: 600;
}

@media (min-width: 900px) {
	.nsi4-crop--side {
		grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
		align-items: start;
		gap: var(--nsi4-space-8);
	}
}

/* Below 640px a shot can be served as a 1:1 phone crop instead of the desktop
   one.  Three of the four are narrower than the column, so they are drawn at
   their own width and the frame closes on the image rather than leaving a strip
   of surface beside it; a crop wider than the column (the WRS hero) stops at
   the column. */
@media (max-width: 639.98px) {
	.nsi4-crop__frame--phone {
		width: fit-content;
		max-width: 100%;
		justify-self: center;
	}

	.nsi4-crop__frame--phone img {
		width: auto;
		max-width: 100%;
	}
}

/* Spec block */
.nsi4-spec {
	margin: 0;
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
}

.nsi4-spec__row {
	display: grid;
	grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
	gap: var(--nsi4-space-4);
	padding: 14px var(--nsi4-space-6);
	border-top: 1px solid var(--nsi4-hairline);
}

.nsi4-spec__row:first-child {
	border-top: 0;
}

.nsi4-spec dt {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-data);
	font-weight: 500;
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

/* The press kit's product keys name brands, so a dt carrying PO-03's
   modifier keeps its own casing — this rule's specificity would otherwise
   outrank .nsi4-keep-case on its own. */
.nsi4-spec dt.nsi4-keep-case {
	text-transform: none;
}

/* Values read as prose — the keys stay mono above them. A literal technical
   token inside a value keeps the mono the document gives <code>. */
.nsi4-spec dd {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	font-weight: var(--nsi4-fw-small);
	color: var(--nsi4-text);
}

@media (max-width: 480px) {
	.nsi4-spec__row {
		grid-template-columns: 1fr;
		gap: var(--nsi4-space-1);
		padding: 12px var(--nsi4-space-4);
	}
}

/* Measured stat — for a number that has a source line */
.nsi4-stat {
	display: grid;
	gap: var(--nsi4-space-2);
	padding-top: var(--nsi4-space-4);
	border-top: 1px solid var(--nsi4-hairline);
}

.nsi4-stat__value {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data-xl);
	line-height: var(--nsi4-lh-data-xl);
	font-weight: var(--nsi4-fw-data-xl);
	letter-spacing: var(--nsi4-ls-data-xl);
	color: var(--nsi4-text);
	font-variant-numeric: tabular-nums;
}

.nsi4-stat__unit {
	font-size: 20px;
	color: var(--nsi4-text-2);
	margin-left: 4px;
}

.nsi4-stat__label {
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

.nsi4-stat__source {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: 400;
	color: var(--nsi4-text-3);
}

/* The source line as its own line: the one Armory line under RETRY's stat
   row, and the source in the RETRY card. The stat's .nsi4-stat__source is
   off /community/retry, so this carries the same type and colour as a link. */
.nsi4-source {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: 400;
	color: var(--nsi4-text-3);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	text-decoration-color: var(--nsi4-control-border);
}

.nsi4-source:hover {
	color: var(--nsi4-text-2);
	text-decoration-color: var(--nsi4-text-3);
}

/* The founding-partner card — the proof object in the Community and
   Partnerships heroes (Pages/Shared/_RetryCard.cshtml). The system's one
   card shape: surface, hairline, 12px radius, the 32px padding step. */
.nsi4-retry-card {
	display: grid;
	gap: var(--nsi4-space-6);
	align-content: start;
	padding: var(--nsi4-space-8);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
}

/* Case-study section shell */
.nsi4-case {
	padding-block: var(--nsi4-section-pad);
	display: grid;
	gap: var(--nsi4-space-8);
}

@media (min-width: 900px) {
	.nsi4-case {
		padding-block: var(--nsi4-section-pad);
		grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
		gap: var(--nsi4-gutter);
	}
}

.nsi4-case__head {
	display: grid;
	gap: var(--nsi4-space-2);
	align-content: start;
}

.nsi4-case__head h2 {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-2);
	line-height: var(--nsi4-lh-heading-2);
	font-weight: var(--nsi4-fw-heading-2);
	letter-spacing: var(--nsi4-ls-heading-2);
	text-wrap: balance;
}

.nsi4-case__body {
	display: grid;
	gap: var(--nsi4-space-6);
	min-width: 0;
}

.nsi4-case__body p {
	margin: 0;
	max-width: var(--nsi4-measure);
}

/* A labelled sub-block inside a case-study body — a sub-heading over the grid
   it introduces (the WRS case study's Surfaces / What it does / The assistant).
   The body's own gap separates the blocks; this gap binds a sub-heading to what
   it labels, at the same 16px the tiles sit to each other. */
.nsi4-case__block {
	display: grid;
	gap: var(--nsi4-space-4);
}

/* The section guide: a guided section's head column carries the eyebrow, the
   h2, a short intro and — where the section runs to several groups — an index
   of them (03 What We Built). The modifier is set on the section, never on
   .nsi4-case__head: the head is the site-wide section shell (01/02/04/05, the
   RETRY page, the styleguide demo) and none of those may stick. From 900px the
   whole guide sticks 96px under the viewport top; a sticky grid item is held
   inside its own grid area, so it stops at the section's end instead of
   following the reader into the next one. */
@media (min-width: 900px) {
	.nsi4-case--guided .nsi4-case__head {
		position: sticky;
		top: var(--nsi4-sticky-top);
		align-self: start;
	}
}

/* Groups in a guided section sit 72px apart — the section's own rhythm from
   one labelled group to the next, between the 64px and 96px spacing steps. */
.nsi4-case--guided .nsi4-case__body {
	gap: 72px;
}

.nsi4-case__intro {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	color: var(--nsi4-text-2);
}

/* The group index: one anchor link per group, a wrapped row of links under the
   intro at every width. Without case-guide.js these are plain anchors and
   nothing is marked; with it the group being read carries aria-current and the
   world accent. Scrolling stays the browser's — theme.min.css holds the smooth
   scroll and its reduced-motion guard — so nothing here animates a jump. */
.nsi4-case__index {
	margin: var(--nsi4-space-4) 0 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: var(--nsi4-space-2) var(--nsi4-space-4);
}

.nsi4-case__index a {
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-3);
	text-decoration: none;
}

.nsi4-case__index a:hover {
	color: var(--nsi4-text-2);
}

.nsi4-case__index a[aria-current="true"] {
	color: var(--nsi4-accent);
}

/* A group heading over a guided section's block: 20px/600 in the main text
   level, so it leads its group in the same white as the body under it and the
   hierarchy reads downward rather than upward. The size sits between the
   heading-3 and body steps and has no token. */
.nsi4-case__block > h3 {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: 20px;
	line-height: 28px;
	font-weight: 600;
	color: var(--nsi4-text);
}

/* 04 Evidence: the section guide in the left column (PO-02's, reused) and
   three blocks in the right — the screen, one component card, the numbers.
   Every override of a shared rule below is scoped by the section modifier,
   so the heroes' and the styleguide's crops keep the shared note tile and
   single-column notes, and the home page's and RETRY's stats keep theirs.
   The card classes themselves (.nsi4-evidence-card, .nsi4-trace,
   .nsi4-tasks, .nsi4-change, .nsi4-cascade) are new names that 04's markup
   opts into, like every component in this sheet, and appear on no other page
   today. */

/* 04's blocks sit 40px apart — the guided section's 72px group rhythm is 03's.
   This rule follows .nsi4-case--guided's in the sheet, so at equal
   specificity it wins. */
.nsi4-case--evidence .nsi4-case__body {
	gap: 40px;
}

/* The screen: the figure spans the right column once the side-by-side
   modifier is off it, and its three notes run three across from 900px (24px
   gap), one column below. The 04 tile is 22px with an accent fill instead of
   the shared outlined counter, and the note text drops to 14/22. The 5px tile
   radius and the 14px text have no token (the system's steps are 4/8/12 and
   the small size is 15) — literals, like PO-02's type sizes. */
@media (min-width: 900px) {
	.nsi4-case--evidence .nsi4-crop__notes {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: var(--nsi4-space-6);
	}
}

.nsi4-case--evidence .nsi4-crop__notes li {
	padding-left: calc(22px + var(--nsi4-space-3));
	font-size: 14px;
	line-height: var(--nsi4-lh-small);
}

.nsi4-case--evidence .nsi4-crop__notes li::before {
	width: 22px;
	height: 22px;
	border-radius: 5px;
	border: 0;
	background: var(--nsi4-accent);
	color: var(--nsi4-ground);
}

/* The frame's hairline is the shared crop's 1px border, which would leave the
   <img> two pixels short of its column. In 04 the same line is an outward
   ring, so frame, figure and image all measure the column's width. */
.nsi4-case--evidence .nsi4-crop__frame {
	border: 0;
	box-shadow: var(--nsi4-highlight-top), 0 0 0 1px var(--nsi4-hairline);
}

/* The component card: the chat's shell (surface, hairline, 12px radius, the
   system's one inset highlight) with a header row — the label in the world
   eyebrow on the left, the capture marker in 12px mono on the right. The
   cards are pictures of an interface, so their lines run the card's width
   rather than the body measure. */
.nsi4-evidence-card {
	display: grid;
	min-width: 0;
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
	color: var(--nsi4-text);
}

.nsi4-evidence-card p {
	max-width: none;
}

.nsi4-evidence-card__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--nsi4-space-3);
}

.nsi4-evidence-card__marker {
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: var(--nsi4-lh-eyebrow);
	color: var(--nsi4-text-3);
}

/* The mechanism diagram — a drawing of how something works rather than proof
   that it did. It never stands in Evidence: the cards there are proof the
   system ran; this is the shape of the system. The figure itself carries no
   frame, so the card a reader already knows — surface, hairline, radius-md,
   the system's one inset highlight — moves onto each stage. The five stack at
   every width: five across gave each about 100px of a 752px column. The figure
   is the query container, because it is the figure, not the viewport, that has
   to hold 24 + 16 + 112 + 16px of a box's chrome. Nothing here animates. */
.nsi4-diagram {
	margin: 0;
	display: grid;
	gap: var(--nsi4-space-4);
	color: var(--nsi4-text);
	container-type: inline-size;
}

.nsi4-diagram__stages {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--nsi4-space-6);
	counter-reset: stage;
}

.nsi4-diagram__stage {
	position: relative;
	display: grid;
	row-gap: var(--nsi4-space-1);
	padding: var(--nsi4-space-4) 20px;
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-md);
	box-shadow: var(--nsi4-highlight-top);
}

/* The marker is .nsi4-crop__notes li::before's, in flow rather than absolute:
   as a grid item of the box it takes a row of its own below the figure's 640px
   and the first of three columns over it. The empty alt text keeps the digit
   out of what a screen reader reads ahead of the name — the <ol> already
   counts. */
.nsi4-diagram__stage::before {
	counter-increment: stage;
	content: counter(stage) / "";
	width: 24px;
	height: 24px;
	display: grid;
	place-items: center;
	border: 1px solid var(--nsi4-accent);
	border-radius: var(--nsi4-radius-sm);
	color: var(--nsi4-accent);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	font-weight: 500;
	line-height: 1;
}

/* The connector: one 1px rule in the world accent down the marker's axis, from
   this box's bottom edge to the next box's top, ending in a 7x4 head whose
   point lands on that top border. Rule and head are one box drawn by a
   clip-path, because a stage's two pseudo-elements are the marker and this.
   Absolute offsets resolve against the padding box, one border pixel inside
   the box's own — hence the 1px on the calc — and the box is exactly as tall
   as the gap the stages stand apart by, so its point falls on the next stage's
   top. left: 29px is that axis: the marker's centre is 33px from the box edge
   (1 border + 20 padding + half the 24px marker) and the rule sits at x 3-4 of
   a 7px box, which keeps the whole-pixel rule crisp at 1x and leaves the head
   half a pixel right of the badge. The last stage has no successor and so no
   connector. */
.nsi4-diagram__stage:not(:last-child)::after {
	content: "";
	position: absolute;
	left: 29px;
	top: calc(100% + 1px);
	width: 7px;
	height: var(--nsi4-space-6);
	background: var(--nsi4-accent);
	clip-path: polygon(3px 0, 4px 0, 4px 20px, 7px 20px, 3.5px 24px, 0 20px, 3px 20px);
}

/* A box takes its three columns — the marker, the name's 112px, the line in
   what is left — once the figure itself is 640px wide, centred on one line and
   16px between the columns. Below that the box stacks: the marker, then the
   name, then the line under it, 4px apart. The figure is the container, so
   this is a container query and not a media query on the viewport: a 640px
   viewport leaves the figure far narrower than 640px and nothing to lay out. */
@container (min-width: 640px) {
	.nsi4-diagram__stage {
		grid-template-columns: 24px 112px minmax(0, 1fr);
		align-items: center;
		column-gap: var(--nsi4-space-4);
	}
}

/* The stage name is read, not shouted: the body's IBM Plex Sans at the small
   size and a semibold, keeping the case the copy writes it in. The line under
   it is the small body text. */
.nsi4-diagram__name {
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-diagram__line {
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

.nsi4-diagram__caption {
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-3);
}

/* Trinity · decision trace — a log table whose rows carry their own 24px
   gutters, so the card is unpadded and its rules run edge to edge. The .06
   row rule is the mockup's (the system hairline is .08) — a literal; the
   redaction fill reads --nsi4-redaction. Below 640px the table scrolls
   inside its focusable wrapper instead of moving the page. */
.nsi4-trace .nsi4-evidence-card__head {
	padding: var(--nsi4-space-4) var(--nsi4-space-6);
}

.nsi4-trace__scroll {
	overflow-x: auto;
}

.nsi4-trace__table {
	width: 100%;
	border-collapse: collapse;
	text-align: left;
	white-space: nowrap;
}

.nsi4-trace__table th {
	padding: 10px var(--nsi4-space-6);
	font-family: var(--nsi4-font-mono);
	/* 12px is the floor the design review sets for any text — a literal, and the
	   same size the ledger's label and value carry. */
	font-size: 12px;
	line-height: 16px;
	font-weight: 500;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-trace__table td {
	padding: 10px var(--nsi4-space-6);
	border-top: 1px solid rgba(255, 255, 255, 0.06);
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	line-height: 20px;
}

.nsi4-trace__bar {
	display: inline-block;
	width: 34px;
	height: 12px;
	/* 2px radius sits below the system's 4px step — a literal. */
	border-radius: 2px;
	background: var(--nsi4-redaction);
	vertical-align: middle;
}

.nsi4-trace__pass {
	color: var(--nsi4-accent);
}

.nsi4-trace__muted {
	color: var(--nsi4-text-2);
}

.nsi4-trace__foot {
	margin: 0;
	padding: 14px var(--nsi4-space-6) 18px;
	font-size: 13px;
	line-height: 20px;
	color: var(--nsi4-text-2);
}

/* Tycho · one task, start to finish — a six-row tool-call log on the same
   full-bleed row rule. Below 640px the detail wraps under its action, so the
   row re-flows instead of scrolling sideways. */
.nsi4-tasks .nsi4-evidence-card__head {
	padding: var(--nsi4-space-4) var(--nsi4-space-6);
}

.nsi4-tasks__row {
	display: grid;
	grid-template-columns: 16px 200px minmax(0, 1fr) 64px;
	gap: var(--nsi4-space-3);
	padding: 9px var(--nsi4-space-6);
	border-top: 1px solid rgba(255, 255, 255, 0.06);
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	line-height: 20px;
}

.nsi4-tasks__mark {
	color: var(--nsi4-accent);
}

.nsi4-tasks__action {
	color: var(--nsi4-text);
}

.nsi4-tasks__detail {
	color: var(--nsi4-text-2);
	min-width: 0;
}

.nsi4-tasks__result {
	color: var(--nsi4-accent);
	text-align: right;
}

@media (max-width: 639.98px) {
	.nsi4-tasks__row {
		grid-template-columns: 16px minmax(0, 1fr) auto;
	}

	.nsi4-tasks__detail {
		grid-row: 2;
		grid-column: 2 / -1;
	}

	.nsi4-tasks__result {
		grid-row: 1;
		grid-column: 3;
	}
}

/* fromScratch · Oracle · a proposed change — the card a person accepts or
   rejects. Accept and Reject are pictures of controls: spans, never buttons,
   never focusable. The card takes the spec'd 24px padding and 16px rhythm;
   the diff's two tints are the mockup's rgba and have no token (a literal),
   and the removed sign reads --nsi4-diff-minus. */
.nsi4-change {
	padding: var(--nsi4-space-6);
	gap: var(--nsi4-space-4);
}

.nsi4-change__title {
	font-family: var(--nsi4-font-sans);
	font-size: 18px;
	line-height: 26px;
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-change__diff {
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-md);
	overflow: hidden;
}

.nsi4-change__row {
	display: grid;
	grid-template-columns: 24px 120px minmax(0, 1fr);
	padding: 10px var(--nsi4-space-4);
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	line-height: 20px;
	color: var(--nsi4-text);
}

.nsi4-change__row--minus {
	background: rgba(240, 96, 96, 0.08);
}

.nsi4-change__row--minus .nsi4-change__sign {
	color: var(--nsi4-diff-minus);
}

.nsi4-change__row--plus {
	background: rgba(77, 124, 254, 0.10);
}

.nsi4-change__row--plus .nsi4-change__sign {
	color: var(--nsi4-accent);
}

.nsi4-change__field {
	color: var(--nsi4-text-3);
}

.nsi4-change__why {
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

.nsi4-change__why b {
	color: var(--nsi4-text);
	font-weight: 600;
}

.nsi4-change__footer {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--nsi4-space-3);
}

.nsi4-change__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	height: 40px;
	padding: 0 20px;
	border-radius: var(--nsi4-radius-md);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	font-weight: 600;
	line-height: 20px;
}

.nsi4-change__accept {
	background: var(--nsi4-accent);
	color: var(--nsi4-on-world);
}

.nsi4-change__reject {
	border: 1px solid rgba(255, 255, 255, 0.16);
	color: var(--nsi4-text);
}

.nsi4-change__note {
	margin-left: auto;
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: var(--nsi4-lh-eyebrow);
	color: var(--nsi4-text-3);
	text-align: right;
}

/* AliveMD · status cascade — two task rows and the line between them. The
   card takes the spec'd 24px padding and a 12px rhythm; the completed chip's
   tint is 14% of the world accent and the blocked chip's outline the
   mockup's .16 — both literals, neither has a token. */
.nsi4-cascade {
	padding: var(--nsi4-space-6);
	gap: var(--nsi4-space-3);
}

.nsi4-cascade__task {
	display: grid;
	grid-template-columns: 72px minmax(0, 1fr) auto;
	gap: var(--nsi4-space-4);
	align-items: center;
	padding: 14px var(--nsi4-space-4);
	background: var(--nsi4-ground);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-md);
}

.nsi4-cascade__id {
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	color: var(--nsi4-text-3);
}

.nsi4-cascade__title {
	font-size: var(--nsi4-fs-small);
}

.nsi4-cascade__chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 10px;
	border-radius: var(--nsi4-radius-sm);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: 16px;
}

.nsi4-cascade__chip--done {
	background: rgba(240, 96, 158, 0.14);
	color: var(--nsi4-accent);
}

.nsi4-cascade__chip--moved {
	border: 1px solid rgba(255, 255, 255, 0.16);
}

.nsi4-cascade__dot {
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--nsi4-accent);
}

.nsi4-cascade__was {
	color: var(--nsi4-text-3);
	text-decoration: line-through;
}

.nsi4-cascade__now {
	color: var(--nsi4-text);
}

.nsi4-cascade__unblocks {
	margin: 0;
	padding-left: 104px;
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: var(--nsi4-lh-eyebrow);
	color: var(--nsi4-text-3);
}

.nsi4-cascade__caption {
	margin: 0;
	font-size: 13px;
	line-height: 20px;
	color: var(--nsi4-text-2);
}

/* The numbers: two stats side by side from 640px, 24px apart, ruled and
   spaced by the shared stat above. 04 re-specifies the value as the world
   accent at 600, the unit in full text, and the label and source one step
   smaller — only 04's stats; the home page's, RETRY's, the Partnerships' and
   the styleguide's keep theirs. The scoped gap out-ranks the shared
   .nsi4-stats gap (the shared pair rule below sets columns only), so the
   24px holds from 640px up. The phone block at the end of the page-layout
   layer takes it to 16px below 640px. */
.nsi4-case--evidence .nsi4-stats {
	gap: var(--nsi4-space-6);
}

.nsi4-case--evidence .nsi4-stat__value {
	font-weight: 600;
	color: var(--nsi4-accent);
}

.nsi4-case--evidence .nsi4-stat__unit {
	color: var(--nsi4-text);
}

.nsi4-case--evidence .nsi4-stat__label {
	font-size: 14px;
}

.nsi4-case--evidence .nsi4-stat__source {
	font-size: 12px;
}

@media (min-width: 640px) {
	.nsi4-case--evidence .nsi4-stats {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}
/* Product card */
.nsi4-product {
	display: grid;
	/* The cards in a row are all stretched to the tallest one's height, and the
	   default `align-content` on a grid hands that surplus height straight back
	   to the auto rows: a card whose claim runs one line shorter than its
	   neighbour's then reads 4-5px taller on every row, which pushed its title
	   out of line with the others. `start` keeps each row at its own content
	   height and leaves the slack at the foot of the card, where it belongs. */
	align-content: start;
	gap: var(--nsi4-space-4);
	padding: var(--nsi4-space-6);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
	color: var(--nsi4-text);
	text-decoration: none;
	transition: background-color var(--nsi4-dur) var(--nsi4-ease), border-color var(--nsi4-dur) var(--nsi4-ease);
}

/* Phoenix underlines every link it hovers, and `a:hover` outranks a class, so
   the card's own hover state — the raised surface, the accent border, the
   coloured 'Read the case study' — arrives with an underline under its name,
   claim and proof line as well. Same declaration, one pseudo-class up: the
   card is the hover, not the words in it. The border and the link colour are
   separate properties and stay. */
.nsi4-product:hover,
.nsi4-product:focus-visible {
	text-decoration: none;
}

/* The card answers in its own product's colour, the way the Products hub's
   rows answer with the image edge and the arrow. Keyboard focus raises it on
   every device — one that reports `hover: none` still has a link to tab to. */
.nsi4-product:focus-visible {
	border-color: var(--nsi4-accent);
}

.nsi4-product:focus-visible .nsi4-product__more {
	color: var(--nsi4-accent);
}

/* Pointer hover, on the devices that have a pointer. */
@media (hover: hover) {
	.nsi4-product:hover {
		background: var(--nsi4-raised);
		border-color: var(--nsi4-accent);
	}

	.nsi4-product:hover .nsi4-product__more {
		color: var(--nsi4-accent);
	}
}

.nsi4-product__name {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-3);
	line-height: var(--nsi4-lh-heading-3);
	font-weight: var(--nsi4-fw-heading-3);
}

.nsi4-product__claim {
	margin: 0;
	color: var(--nsi4-text-2);
}

.nsi4-product__proof {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: 400;
	color: var(--nsi4-text-3);
}

.nsi4-product__more {
	font-weight: 600;
	font-size: var(--nsi4-fs-small);
	color: var(--nsi4-text);
	transition: color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-product__media {
	border-radius: var(--nsi4-radius-md);
	overflow: hidden;
	border: 1px solid var(--nsi4-hairline);
	/* Every card shot is cut to 16:10 and the box draws that ratio, so swapping
	   one card's image later cannot move the titles below it. */
	aspect-ratio: 16 / 10;
}

.nsi4-product__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* World texture — product hero band only */
.nsi4-texture {
	position: relative;
	isolation: isolate;
}

.nsi4-texture::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	opacity: var(--nsi4-texture-opacity);
	background-image: var(--nsi4-texture-image);
	background-size: var(--nsi4-texture-size);
	mask-image: linear-gradient(to bottom, black, transparent);
	-webkit-mask-image: linear-gradient(to bottom, black, transparent);
}

/* On a hero band the texture stays out of the hero copy. By default it
   fades to transparent exactly at the copy's top edge (--nsi4-hero-pad-top
   is what .nsi4-hero pads above the copy), so only the strip above the
   copy paints. From 1000px the hero splits into a copy column and a crop,
   and on those heroes the texture paints only in the half of the band away
   from the copy column, faded out before it gets there: the copy column's
   right edge never comes closer than half the band's width from the right
   edge (524px of 1280px), so it always lands in the transparent side. A
   hero with no crop writes its copy as the only child spanning both
   columns — no half is free beside it, so it keeps the strip above the
   copy. */
.nsi4-hero-band.nsi4-texture::before {
	mask-image: linear-gradient(to bottom, black, transparent var(--nsi4-hero-pad-top));
	-webkit-mask-image: linear-gradient(to bottom, black, transparent var(--nsi4-hero-pad-top));
}

@media (min-width: 1000px) {
	.nsi4-hero-band.nsi4-texture:not(:has(.nsi4-hero__copy:only-child))::before {
		mask-image: linear-gradient(to left, black 40%, transparent 50%);
		-webkit-mask-image: linear-gradient(to left, black 40%, transparent 50%);
	}
}

/* On a phone the strip is too thin to read: 64px of a texture drawn at the size
   it keeps on a desktop is a band of dust above the copy. Below 640px the band
   takes a taller pad and every world draws its texture 1.5x larger — dots on a
   9px pitch rather than 6px, a rule every 40px rather than every 27px — so the
   pattern reads as a pattern at the size a thumb holds it.

   The pad is set on the band and not on :root. That is what keeps the hub
   pages' own band at 64px: .nsi4-guides sits on a hero that has no band above
   it and reads the token from :root. Here it is inherited instead, so the hero's
   own top padding, the texture's fade point and the Trinity ticker strip all
   read 96px together, and 640px up is untouched. */
@media (max-width: 639.98px) {
	.nsi4-hero-band {
		--nsi4-hero-pad-top: 96px;
	}

	/* Every pitch in --nsi4-texture-size times 1.5, and every dot radius with
	   it, so the pattern is drawn at the same size relative to the eye as it is
	   on a desktop. The line textures keep their 1px lines — a line is a line at
	   either pitch — and no opacity moves. */
	.world-wrs {
		--nsi4-texture-image: radial-gradient(circle, var(--nsi4-accent) 1.5px, transparent 2.25px);
		--nsi4-texture-size: 9px 9px;
	}

	.world-trinity {
		--nsi4-texture-size: 100% 12px;
	}

	.world-fromscratch {
		--nsi4-texture-image: radial-gradient(circle, var(--nsi4-accent) 1.8px, transparent 2.4px);
		--nsi4-texture-size: 24px 24px;
	}

	.world-tycho {
		--nsi4-texture-size: 36px 36px, 36px 36px;
	}

	.world-alivemd {
		--nsi4-texture-image: linear-gradient(to bottom, transparent 39px, var(--nsi4-accent) 39px, var(--nsi4-accent) 40px);
		--nsi4-texture-size: 100% 40px;
	}
}

/* Skip link — the first focusable element in the shell. It sits at the top of
   the document on a surface, moved out of view until it takes focus, so a
   keyboard user reaches the content without walking the navigation. */
.nsi4-skip-link {
	position: absolute;
	top: var(--nsi4-space-4);
	left: var(--nsi4-space-4);
	z-index: 1080;
	display: inline-flex;
	align-items: center;
	min-height: var(--nsi4-touch-min);
	padding: 0 var(--nsi4-space-4);
	background: var(--nsi4-surface);
	color: var(--nsi4-text);
	border: 1px solid var(--nsi4-control-border);
	border-radius: var(--nsi4-radius-md);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	font-weight: 600;
	text-decoration: none;
	transform: translateY(calc(-100% - var(--nsi4-space-8)));
	transition: transform var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-skip-link:focus {
	transform: translateY(0);
}

/* Touch targets — every chrome control reaches the 44px minimum in both
   directions. The hit area grows; the type size does not. */
.nsi4 .navbar-toggler {
	min-width: var(--nsi4-touch-min);
	min-height: var(--nsi4-touch-min);
}

/* An open menu draws the way back out: the toggler keeps the mark's own stroke
   colour and weight and crosses the three lines into two. Bootstrap writes
   aria-expanded on the button, and the button itself is hidden from 992px, so
   the rule needs no media query of its own. */
.nsi4-nav .navbar-toggler[aria-expanded="true"] .navbar-toggler-icon {
	background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='%239da9bb' stroke-width='2' stroke-linecap='round' d='M6 6l18 18M24 6L6 24'/%3E%3C/svg%3E");
}

.nsi4 .navbar .dropdown-item {
	display: flex;
	align-items: center;
	min-height: var(--nsi4-touch-min);
}

.nsi4 footer a {
	display: inline-flex;
	align-items: center;
	min-width: var(--nsi4-touch-min);
	min-height: var(--nsi4-touch-min);
}

.nsi4 footer a[aria-label] {
	justify-content: center;
}

/* Chrome — the nav and the footer frame every page, so their rules live here
   rather than in any one page's layout. The nav is the mockup's: 64px tall,
   the ground held at 92% so the page reads faintly through it, over a
   hairline. The transparency is mixed, not a second colour. */
.nsi4-nav {
	position: sticky;
	top: 0;
	z-index: 1020;
	background: color-mix(in srgb, var(--nsi4-ground) 92%, transparent);
	border-bottom: 1px solid var(--nsi4-hairline);
}

.nsi4-nav .navbar {
	min-height: 64px;
}

/* Contact rides the right edge of the bar, clear of the menu: the list's auto
   margin pushes it out to the shell's content edge and the collapse centres
   it on the bar. Secondary, so a page's own primary button is the only
   gradient on the first screen; 36px is the bar's button height — a scoped
   override, the 44px floor stays on every other control. */
.nsi4-nav .navbar .nsi4-btn {
	min-height: 36px;
	margin-inline-start: var(--nsi4-space-4);
}

/* Below lg the collapse is a stacked menu, and a button sized to its own label
   sits left under full-width items. It takes the menu's own width instead, and
   drops the bar's inline-start gap, which would otherwise carry it 16px past
   the collapse's edge. */
@media (max-width: 991.98px) {
	.nsi4-nav .navbar .navbar-collapse .nsi4-btn {
		width: 100%;
		margin-inline-start: 0;
	}
}

.nsi4-nav .navbar-nav .nav-link {
	display: flex;
	align-items: center;
	min-height: var(--nsi4-touch-min);
	padding-inline: var(--nsi4-space-3);
	border-radius: var(--nsi4-radius-md);
	font-size: var(--nsi4-fs-small);
	font-weight: 500;
}

.nsi4-nav .navbar-nav .nav-link:hover,
.nsi4-nav .navbar-nav .nav-link[aria-current] {
	background: var(--nsi4-raised);
}

/* A menu already sits on the raised step, so the item the reader is on steps
   down to the ground rather than up another one. */
.nsi4-nav .dropdown-item[aria-current] {
	color: var(--nsi4-text);
	background: var(--nsi4-ground);
}

/* The footer is the mockup's four columns — brand, Products, Company,
   Elsewhere — over a base row. One column until 900px. */
.nsi4-footer {
	border-top: 1px solid var(--nsi4-hairline);
	padding-block: var(--nsi4-section-pad-compact);
}

.nsi4-footer__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-8);
}

/* Rows start at the top, so the logo's top edge meets the column headings'.
   The first gap is 0 because the link box is the 44px touch target with the
   mark at its top: the tagline then lands exactly 12px under the logo. */
.nsi4-footer__brand {
	display: grid;
	justify-items: start;
	align-content: start;
	gap: 0;
}

.nsi4 footer .nsi4-footer__brand a {
	align-items: flex-start;
}

.nsi4-footer__contact {
	margin: var(--nsi4-space-2) 0 0;
	color: var(--nsi4-text-3);
}

.nsi4-footer__contact + .nsi4-footer__contact {
	margin-top: 0;
}

.nsi4-footer__contact a {
	color: inherit;
	text-decoration: none;
}

.nsi4-footer__contact a:hover {
	text-decoration: underline;
}

.nsi4-footer__blurb {
	color: var(--nsi4-text-2);
	max-width: 34ch;
}

.nsi4-footer__col {
	display: grid;
	align-content: start;
	gap: var(--nsi4-space-2);
}

.nsi4-footer__col .nsi4-eyebrow {
	margin: 0;
}

.nsi4-footer__list {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--nsi4-space-2);
	line-height: var(--nsi4-lh-small);
}

.nsi4-footer__list a {
	color: var(--nsi4-text-2);
	text-decoration: none;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
}

.nsi4-footer__list a:hover {
	color: var(--nsi4-text);
}

/* The footer's links are text, not controls: inline at their line's height.
   The 44px box stays on the logo mark and on every control in the nav. */
.nsi4 footer .nsi4-footer__list a,
.nsi4 footer .nsi4-footer__contact a {
	display: inline;
}

.nsi4-footer__base {
	margin: 0;
	margin-top: var(--nsi4-space-12);
	padding-top: var(--nsi4-space-8);
	border-top: 1px solid var(--nsi4-hairline);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

/* On a phone the footer is two link columns: brand full width, Products and
   Company side by side, Elsewhere one inline row beneath them, over a base
   row on the tighter spacing pair — the whole block stays inside its 560px
   budget. */
@media (max-width: 899px) {
	.nsi4-footer__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--nsi4-space-6);
	}

	.nsi4-footer__brand,
	.nsi4-footer__col:last-child {
		grid-column: 1 / -1;
	}

	.nsi4-footer__col:last-child .nsi4-footer__list {
		display: flex;
		flex-wrap: wrap;
		gap: var(--nsi4-space-2) var(--nsi4-space-6);
	}

	.nsi4-footer__base {
		margin-top: var(--nsi4-space-6);
		padding-top: var(--nsi4-space-6);
	}
}

@media (min-width: 900px) {
	.nsi4-footer__grid {
		grid-template-columns: 4fr 2fr 2fr 2fr;
	}
}

/* ==========================================================================
   7. Page layout
   The shells the rebuilt pages compose from: the homepage and every page that
   follows it. The values are the signed-off mockups'. Everything the token
   block names is written as the token; the two the mockup fixes as raw values
   — the section head's 720px measure and the hero's 120px desktop padding —
   are written as raw values and do not move with the spacing scale.
   ========================================================================== */
.nsi4-hero {
	display: grid;
	gap: var(--nsi4-space-12);
	padding-block: var(--nsi4-hero-pad-top) var(--nsi4-space-12);
	/* The hero is the containing block for the band over its copy, and the
	   stacking context that band paints in. A negative z-index with neither
	   would put the band behind the page's own background. */
	position: relative;
	isolation: isolate;
}

/* The column-guides band — the hub pages' stand-in for the product pages'
   world texture: the page's own grid drawn as hairlines in the strip between
   the nav and the hero copy. It fills --nsi4-hero-pad-top, the hero's own top
   padding, and fades to nothing at its foot, so it never paints behind running
   text. It is out of flow, so it takes no part in the hero's own geometry, and
   its insets are the shell gutter, so it starts and ends on the content column
   at every width. Each guide is an empty child carrying a border on each edge
   rather than a background: Chromium snaps element borders to device pixels
   and does not snap gradient backgrounds, and a background drawn at a
   twelve-column pitch would blur into alternating 1px and 2px lines. */
.nsi4-guides {
	position: absolute;
	left: var(--nsi4-gutter);
	right: var(--nsi4-gutter);
	top: 0;
	height: var(--nsi4-hero-pad-top);
	z-index: -1;
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	column-gap: var(--nsi4-gutter);
	pointer-events: none;
	mask-image: linear-gradient(to bottom, black, transparent);
	-webkit-mask-image: linear-gradient(to bottom, black, transparent);
}

.nsi4-guides > span {
	border-left: 1px solid var(--nsi4-hairline);
	border-right: 1px solid var(--nsi4-hairline);
}

/* Below 1000px the grid is four columns; the other eight guides are out of it. */
@media (max-width: 999.98px) {
	.nsi4-guides > span:nth-child(n + 5) {
		display: none;
	}
}

@media (min-width: 1000px) {
	.nsi4-guides {
		grid-template-columns: repeat(12, minmax(0, 1fr));
	}
}

.nsi4-hero__copy {
	display: grid;
	gap: var(--nsi4-space-6);
	align-content: start;
}

/* A proof object that is more than one piece — the press kit's logo tile
   with its download and usage line — stacks the pieces on the copy's gap. */
.nsi4-hero__proof {
	display: grid;
	gap: var(--nsi4-space-6);
	align-content: start;
}

.nsi4-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--nsi4-space-3);
}

/* The address belongs under its button, left-aligned with it — never beside. */
.nsi4-actions:has(.nsi4-mail) {
	flex-direction: column;
	align-items: flex-start;
	gap: var(--nsi4-space-2);
}

/* Below 420px the hero's buttons stack, and a stack that sizes each button
   to its own label reads ragged: every hero button takes the full content
   width there, so the stack lines up on both edges. The closing bands stack
   the same way at the same width, so they take the same width here — declared
   in the one media query rather than in a second one at the same threshold. */
@media (max-width: 419.98px) {
	.nsi4-hero__copy .nsi4-actions .nsi4-btn,
	.nsi4-closing .nsi4-actions .nsi4-btn {
		width: 100%;
	}
}

/* A rule spans exactly the content column: it is drawn by an inset edge on
   the ruled element rather than by its border, which would run to the padded
   box and hang past the content. The inset is the shell gutter only on the
   element carrying the shell itself — a ruled section inside a shell already
   starts at the content edge. */
.nsi4-case,
.nsi4-rule {
	position: relative;
	border-top: 0;
}

.nsi4-case::before,
.nsi4-rule::before {
	content: "";
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	border-top: 1px solid var(--nsi4-hairline);
}

.nsi4-shell.nsi4-case::before,
.nsi4-shell.nsi4-rule::before {
	left: var(--nsi4-gutter);
	right: var(--nsi4-gutter);
}

/* A ruled band stacks its head, its content grid and any crop on one gap, the
   same one the case-study shell uses. Scoped to .nsi4-rule, which only the
   rebuilt pages carry. */
.nsi4-section.nsi4-rule {
	display: grid;
	gap: var(--nsi4-space-8);
}

.nsi4-section__head {
	display: grid;
	gap: var(--nsi4-space-3);
	max-width: 720px;
}

.nsi4-stats {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-8);
}

.nsi4-products {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-gutter);
}

.nsi4-steps {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-6);
}

.nsi4-steps li {
	display: grid;
	gap: var(--nsi4-space-3);
	padding-top: var(--nsi4-space-4);
	border-top: 1px solid var(--nsi4-control-border);
}

/* The quick links — a row of surface cards, each a title and one line of
   text-2. The card is the link, so the whole card is the target. */
.nsi4-links {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-gutter);
}

.nsi4-link-card {
	display: grid;
	/* The title and the arrow share the first row: the arrow sits in the
	   auto column at the content's right edge, so a long title wraps before
	   it instead of running under it. */
	grid-template-columns: minmax(0, 1fr) auto;
	gap: var(--nsi4-space-2) var(--nsi4-space-4);
	align-content: start;
	padding: var(--nsi4-space-6);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
	color: var(--nsi4-text);
	text-decoration: none;
	transition: background-color var(--nsi4-dur) var(--nsi4-ease), border-color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-link-card:hover {
	background: var(--nsi4-raised);
	border-color: var(--nsi4-control-border);
}

.nsi4-link-card__title {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-3);
	line-height: var(--nsi4-lh-heading-3);
	font-weight: var(--nsi4-fw-heading-3);
}

/* The affordance: the arrow at the card's top right, 16px, quiet until the
   card is hovered. */
.nsi4-link-card__arrow {
	grid-column: 2;
	grid-row: 1;
	align-self: start;
	width: 16px;
	height: 16px;
	color: var(--nsi4-text-3);
	transition: color var(--nsi4-dur) var(--nsi4-ease), transform var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-link-card:hover .nsi4-link-card__arrow {
	color: var(--nsi4-text);
}

.nsi4-link-card__body {
	grid-column: 1 / -1;
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

/* The arrow's 2px step runs only where motion is welcome; the reduced-motion
   guard kills every transition, and this keeps the transform away as well. */
@media (prefers-reduced-motion: no-preference) {
	.nsi4-link-card:hover .nsi4-link-card__arrow {
		transform: translateX(2px);
	}
}

/* ==========================================================================
   The press kit — the spec sheet: four logo tiles, ten colour swatches and
   two descriptions, each one click to download or copy. Every tile and card
   is the surface step .nsi4-link-card already sets: the same background, the
   same hairline, the same radius, the same highlight.
   ========================================================================== */

/* The head of those three sections: the heading at the left, its note on the
   same baseline at the right edge of the content column. Under 900px the note
   drops under the heading, 8px below it, and reads as ordinary body copy.
   Scoped to --split, so the single-heading heads on every other page keep the
   one-column head and its 720px measure. */
.nsi4-section__head--split {
	grid-template-columns: minmax(0, auto) minmax(0, 1fr);
	column-gap: var(--nsi4-gutter);
	align-items: baseline;
	max-width: none;
}

.nsi4-section__note {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-3);
	text-align: right;
}

/* The logo tiles. One column below 640px, two across from there — four tiles
   make a two-by-two, and two across is as wide as the horizontal logo reads
   well at. Below 640px the preview loses 40px, because the tile is one column
   of a phone and the logo is not the reason to scroll. */
.nsi4-logos {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-6);
}

@media (min-width: 640px) {
	.nsi4-logos {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

.nsi4-logo {
	margin: 0;
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
}

/* The artwork on its own ground, at a height the four tiles can share. The
   preview rounds only its top corners, so the tile's own radius and its focus
   ring are both left alone. */
.nsi4-logo__preview {
	display: grid;
	place-items: center;
	height: 160px;
	border-bottom: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg) var(--nsi4-radius-lg) 0 0;
}

@media (min-width: 640px) {
	.nsi4-logo__preview {
		height: 200px;
	}
}

.nsi4-logo__preview--dark {
	background: var(--nsi4-ground);
}

.nsi4-logo__preview--light {
	background: var(--nsi4-paper);
	border-bottom-color: var(--nsi4-paper-line);
}

.nsi4-logo__preview img {
	display: block;
	width: auto;
	height: 64px;
}

/* The vertical lockup, a step down on a phone: 128px beside the 64px horizontal
   logo from 640px, 112px below it. The preview centres what it holds. */
.nsi4-logo__preview--tall img {
	height: 112px;
}

@media (min-width: 640px) {
	.nsi4-logo__preview--tall img {
		height: 128px;
	}
}

.nsi4-logo__caption {
	display: grid;
	/* The name and its line take the row the button does not, so a long line
	   wraps before the button rather than running under it. */
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	gap: var(--nsi4-space-2) var(--nsi4-space-4);
	padding: var(--nsi4-space-4) 20px;
}

.nsi4-logo__text {
	display: grid;
	gap: var(--nsi4-space-1);
}

.nsi4-logo__name {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	font-weight: 600;
	letter-spacing: var(--nsi4-ls-small);
	color: var(--nsi4-text);
}

/* The touch minimum below 640px, the mockup's 36px from there. Every other
   control on a phone keeps the 44px floor; these two were the exception, and
   the nav's 36px button is the same call it overrides the same way. */
.nsi4-logo__download {
	min-height: var(--nsi4-touch-min);
}

@media (min-width: 640px) {
	.nsi4-logo__download {
		min-height: 36px;
	}
}

/* The swatches — the chip across the column's full width, and on the row under
   it its name and hex at the left and the copy button at the right. Each
   swatch's colour arrives as --nsi4-swatch from the markup, so the page names
   the token and the sheet never names a hex. */
.nsi4-swatches {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--nsi4-space-6);
}

@media (min-width: 640px) {
	.nsi4-swatches {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (min-width: 1000px) {
	.nsi4-swatches {
		grid-template-columns: repeat(5, minmax(0, 1fr));
	}
}

.nsi4-swatch {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: center;
	/* The mockup's 10px, a quarter-step off the spacing scale. The name takes
	   the column the button does not, so a long name wraps before the button
	   rather than running under it. */
	gap: 10px;
	min-width: 0;
}

.nsi4-swatch__chip {
	/* The chip is the first row of the two the markup already describes: it
	   spans both columns and its own height carries the row. */
	grid-column: 1 / -1;
	height: 72px;
	border-radius: var(--nsi4-radius-md);
	background: var(--nsi4-swatch);
}

/* The two chips that sit on the page's own ground need an edge to be seen at
   all — the hairline is the same one every tile draws. */
.nsi4-swatch__chip--line {
	border: 1px solid var(--nsi4-hairline);
}

.nsi4-swatch__text {
	display: grid;
	gap: var(--nsi4-space-1);
	min-width: 0;
}

.nsi4-swatch__name {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	font-weight: 600;
	letter-spacing: var(--nsi4-ls-small);
	color: var(--nsi4-text);
}

.nsi4-swatch__hex {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	/* The mockup's 18px, one step off the scale's 16px eyebrow line. */
	font-size: var(--nsi4-fs-eyebrow);
	line-height: 18px;
	color: var(--nsi4-text-2);
}

/* The copy controls — the icon button on a swatch and the labelled button on a
   description card. Both are written into the page hidden and press-kit.js
   reveals them, so a rule that sets display here would give a reader without
   scripts a button that does nothing: the [hidden] rule below outranks it. */
.nsi4-copy {
	border-radius: var(--nsi4-radius-md);
	color: var(--nsi4-text-2);
	cursor: pointer;
	background: transparent;
	border: 1px solid var(--nsi4-hairline);
	transition: border-color var(--nsi4-dur) var(--nsi4-ease), color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-copy:hover {
	border-color: var(--nsi4-control-border);
	color: var(--nsi4-text);
}

.nsi4-copy[hidden] {
	display: none;
}

/* The icon button: 44px square, the shell's own touch minimum. */
.nsi4-swatch__copy {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	font-size: 20px;
}

/* The labelled button: the touch minimum below 640px, the tile's 36px from
   there, with the label's own baseline. */
.nsi4-copy--label {
	min-height: var(--nsi4-touch-min);
	padding: 0 20px;
	font-family: var(--nsi4-font-sans);
	font-size: 16px;
	font-weight: 600;
	line-height: 20px;
	color: var(--nsi4-text);
	border-color: var(--nsi4-control-border);
}

@media (min-width: 640px) {
	.nsi4-copy--label {
		min-height: 36px;
	}
}

/* The two description cards. The short text is a quarter longer than nothing
   and a quarter shorter than the long one; 5fr / 7fr is the reading the
   mockup sets, so neither card gets a scrollbar it does not need. */
.nsi4-copies {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-6);
}

@media (min-width: 900px) {
	.nsi4-copies {
		grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
	}
}

.nsi4-copy-card {
	display: grid;
	align-content: start;
	gap: 20px;
	padding: var(--nsi4-space-6);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
}

.nsi4-copy-card__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--nsi4-space-4);
	min-height: 36px;
}

.nsi4-copy-card__text {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	color: var(--nsi4-text);
}

/* The region every copy on the page is announced in: on the page for the
   browser to read, off it for the reader to see. */
.nsi4-live {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

@media (max-width: 899.98px) {
	.nsi4-section__head--split {
		grid-template-columns: minmax(0, 1fr);
		row-gap: var(--nsi4-space-2);
	}

	.nsi4-section__note {
		text-align: left;
	}
}

/* The FAQ — native disclosure rows. A summary is a control, so it takes the
   44px minimum; its focus ring is the shell's own, drawn on everything inside
   body.nsi4. The chevron is a chevron-right turned on its side: the sprite
   carries no chevron-down. It turns with no transition, because the system's
   motion reach is hover and press, and a disclosure is neither. */
.nsi4-faq {
	display: grid;
	gap: var(--nsi4-space-3);
}

.nsi4-faq__row {
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
}

.nsi4-faq__question {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--nsi4-space-4);
	min-height: var(--nsi4-touch-min);
	padding: var(--nsi4-space-3) var(--nsi4-space-6);
	border-radius: var(--nsi4-radius-lg);
	list-style: none;
	color: var(--nsi4-text);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	font-weight: 600;
	cursor: pointer;
}

.nsi4-faq__question::-webkit-details-marker {
	display: none;
}

.nsi4-faq__question .nsi4-icon {
	color: var(--nsi4-text-2);
	transform: rotate(90deg);
}

.nsi4-faq__row[open] .nsi4-faq__question .nsi4-icon {
	transform: rotate(270deg);
}

.nsi4-faq__answer {
	padding: 0 var(--nsi4-space-6) var(--nsi4-space-6);
	max-width: var(--nsi4-measure);
}

.nsi4-faq__answer p {
	margin: 0;
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	color: var(--nsi4-text-2);
}

/* The closing band carries the section rhythm itself: the page writes it as
   the closing class and the rule, without the plain section class. */
.nsi4-closing {
	display: grid;
	gap: var(--nsi4-space-6);
	align-items: start;
	padding-block: var(--nsi4-section-pad);
}

.nsi4-mail {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data);
	line-height: var(--nsi4-lh-data);
	font-weight: 400;
	color: var(--nsi4-text-2);
}

/* A hero's mail line sits below its action group rather than in it, so the
   copy grid's own gap is pulled back to the 8px the closing bands pair
   button and address on. */
.nsi4-hero__copy > .nsi4-mail {
	margin-top: calc(var(--nsi4-space-2) - var(--nsi4-space-6));
}

@media (min-width: 700px) {
	.nsi4-stats {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: var(--nsi4-gutter);
	}

	/* A two-stat row would leave the third column empty, so it takes two equal
	   columns instead. Keyed off the stat count, the same shape as the two-card
	   links row further down: the last child must also be the second, or a
	   three-stat row would match on its second stat and be laid out two across. */
	.nsi4-stats:has(> .nsi4-stat:nth-child(2):last-child) {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 760px) {
	.nsi4-links {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.nsi4-products {
		grid-template-columns: repeat(6, minmax(0, 1fr));
	}

	.nsi4-products__wide,
	.nsi4-products__third {
		grid-column: span 3;
	}
}

@media (min-width: 900px) {
	.nsi4-steps {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		/* Three shared rows: each li subgrids them, so the eyebrow, the title
		   and the body hold one top edge across the three columns however many
		   lines a title takes — as separate grids the lis each absorbed their
		   own free space and the three levels drifted apart. The row gap is the
		   li's own 12px step: shared tracks take the parent's gutters. */
		grid-template-rows: repeat(3, auto);
		gap: var(--nsi4-space-3) var(--nsi4-gutter);
	}

	.nsi4-steps li {
		grid-row: span 3;
		grid-template-rows: subgrid;
		gap: var(--nsi4-space-3);
	}
}

/* The closing band stacks: the button sits under the copy, left-aligned with
   the heading, at every width. */

/* A section whose body measures under 240px tall at 1280px takes the compact
   half of the spacing pair. Declared after .nsi4-case and .nsi4-closing set
   their own padding so one rule wins over both; the modifier itself is
   applied per page, section by section. */
.nsi4-section--compact,
.nsi4-case--compact,
.nsi4-closing--compact {
	padding-block: var(--nsi4-section-pad-compact);
}

@media (min-width: 1000px) {
	.nsi4-hero {
		grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
		padding-block: var(--nsi4-hero-pad-top) var(--nsi4-space-24);
		align-items: center;
	}

	/* A hero with no crop writes one child; it spans the band rather than
	   sitting in the copy column with an empty track beside it. The guides are
	   out of flow and are not one of the two shapes this rule is about, so a
	   hero carrying one still writes one child — the copy last, the guides
	   directly before it. */
	.nsi4-hero__copy:only-child,
	.nsi4-hero > .nsi4-guides + .nsi4-hero__copy:last-child {
		grid-column: 1 / -1;
	}

	/* The 96px under a hero balances a seven-column image against the copy
	   beside it. With one child there is nothing to balance, and the band
	   reads as 48px of nothing, so a copy-only hero drops back to space-12. */
	.nsi4-hero:has(> .nsi4-hero__copy:only-child),
	.nsi4-hero:has(> .nsi4-guides + .nsi4-hero__copy:last-child) {
		padding-block-end: var(--nsi4-space-12);
	}
}

/* A hero with a proof object reverses the split — the copy takes the wider
   track and the object sits wholly past the page's horizontal centre (at
   1280 the stock 5fr track starts at x=572 against a centre of 640). Both
   items start at the row top, and the object is pushed down the copy's own
   run up to the headline — the eyebrow line, its bottom margin and the copy
   grid's gap — so its top is level with the h1 rather than centred against
   the copy. Below 1000px there is no second track: the object follows the
   copy in the one column the hero still has. The guides over the strip are
   neither of the two items and keep their own top. */
@media (min-width: 1000px) {
	.nsi4-hero.nsi4-hero--proof {
		grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
		align-items: start;
	}

	.nsi4-hero--proof > :not(.nsi4-hero__copy):not(.nsi4-guides) {
		margin-top: calc(var(--nsi4-lh-eyebrow) + var(--nsi4-space-4) + var(--nsi4-space-6));
	}
}

/* ==========================================================================
   The home hero — the task field.
   The copy at the left, one square per task in the studio's engineering record
   at the right, and the five products under both. The geometry is the board's
   (board 7 at 1280×800, board 8 at 390×844); the field is canvas, laid and
   drawn by assets/js/task-field.js, which reads the data the page carries.

   The section is not .nsi4-hero: that grid is shared with the five product
   heroes and its 5fr/7fr split is not this one. Nothing here is Bootstrap's
   either — the class names are all nsi4's own, so no Phoenix rule is kept alive
   by them and the served theme sheet cannot move.

   At 1100px up the field's box is the board's fixed 640×536 and its left edge
   sits 64px left of the shell's centre line. The grid's own column starts on
   that line, so a 640 box pulled 64px back across it ends on the products
   header's right edge — the Contact us line — from a 1200px viewport up
   (feature Decisions `field-edge`). Below the width where the shell's 1200 stops
   binding, the box runs past that edge by 600px less half the page, and rather
   than let it sit further and further into the copy it takes that overhang as
   margin of its own: it slides right by exactly what it overshoots, so the copy
   keeps the gap between its last word and the field's nearest square at every
   width from 1100px up. The legend and the caption follow it out, so the
   products header's right edge is what they keep and not the box's. A box
   cannot be both fixed and narrower than what contains it, so the band clips
   its own horizontal overflow instead of handing the page a sideways scrollbar.
   clip, not hidden, so the narrow layout below still lays out normally inside
   the same element.

   From 1440px the canvas runs past the box: the squares spill past the header's
   edge and thin to nothing, so the field's edge reads as a scatter rather than
   as a geometric cut. The box itself does not move for it — the legend, the
   caption and the tooltip keep the box as their origin at every width.
   ========================================================================== */
.nsi4-field-hero {
	position: relative;
	/* The band's own width as a unit. The grid inside it is capped at the
	   shell's 1200px, so a rule that needs half the page's own width has nothing
	   else to measure it in. Two rules below 1200px need it and nothing else
	   does: the box's margin, which carries the overhang past the shell's edge,
	   and the panel's right offset, which carries it twice. They are the only
	   50cqw readers left in the sheet, and the only reason this containment
	   stays. */
	container-type: inline-size;
	overflow-x: clip;
	/* theme.min.css pads every bare <section> top and bottom — 3.5rem from
	   992px — and this band carries its own rhythm: the field's box fixes the
	   first screen, so the vendor padding comes off. Nothing is padded on the
	   inline axis, so the band runs the page's own width. */
	padding-block: 0;
}

.nsi4-field-hero__grid {
	display: grid;
	row-gap: var(--nsi4-space-12);
}

.nsi4-field-hero__copy {
	display: grid;
	gap: var(--nsi4-space-6);
	align-content: start;
	max-width: var(--nsi4-content-max);
	/* Not `auto`. An auto inline margin on a grid item suppresses the default
	   stretch, so the copy would shrink to its own width and the auto margins
	   would centre it — the gutter would then start the copy somewhere the rest
	   of the hero does not, and at 768 the copy's own fit-content width is
	   narrower than the column, so it drifts right. Filling the column puts
	   the gutter on one edge for the whole hero. The max-width above does not
	   bind below 1100px, where the column is the page's own width. */
	margin-inline: 0;
	padding-block: var(--nsi4-space-6) 0;
	padding-inline: var(--nsi4-gutter);
}

/* The copy is one grid run at the copy's own gap, so its three lines carry no
   margin of their own — the eyebrow's and the lede's paragraph margins would
   otherwise stack on top of that gap. */
.nsi4-field-hero__copy > * {
	margin: 0;
}

/* The field. Its box is reserved here and not by the script: a canvas lays out
   at its default 300×150 until something sizes it, and the field sits above
   the products header and the five tiles on the first screen — a box arriving
   late would move all of them. Narrow: the band's own 390×156 ratio, full
   bleed. Wide: the board's 640×536, fixed. */
.nsi4-field {
	position: relative;
	margin: 0;
	width: 100%;
}

/* The canvas fills the figure exactly at both layouts below 1440px, and runs
   past it from 1440. Either way its left edge is the figure's own, so the
   figure is still what positions the tooltip: the script places the tip in the
   canvas's own coordinates and flips it against the canvas's width, and the
   figure's left and top are where that canvas starts. */
.nsi4-field__canvas {
	display: block;
	width: 100%;
	aspect-ratio: 390 / 156;
	height: auto;
}

/* The tip's origin is fixed here and never written from the script, so moving
   it costs a transform and not a layout: only a change of left or top is a
   layout shift. */
.nsi4-field__tip {
	position: absolute;
	left: 0;
	top: 0;
	display: none;
	width: 336px;
	padding: 10px 12px;
	background: var(--nsi4-raised);
	border: 1px solid var(--nsi4-field-line);
	border-radius: var(--nsi4-radius-md);
	box-shadow: var(--nsi4-highlight-top);
	pointer-events: none;
}

/* The tip's three lines, 6px apart: the square in words, the title or the
   sentence that stands in for one, and the record's own id and status. */
.nsi4-field__tip--on {
	display: grid;
	gap: 6px;
}

.nsi4-field__what,
.nsi4-field__title,
.nsi4-field__meta {
	margin: 0;
}

/* The tooltip's first line: the square itself, and the sentence that says what
   a square is — 'A closed task in Tycho, our AI-native IDE.' The swatch is the
   legend's own key, carried here in the square's colour rather than the field's
   key colour, so the line is about the square the pointer is on. */
.nsi4-field__what {
	display: flex;
	align-items: center;
	gap: 8px;
	font-family: var(--nsi4-font-sans);
	font-size: 14px;
	line-height: 20px;
	color: var(--nsi4-text-2);
}

/* The tooltip's second line: the task's own title where the studio publishes
   one. 'Its title stays internal.' says so rather than leaving a line out. Its
   height is the title's own — the card grows when a long title wraps, and the
   script writes each square's three lines as new elements so that growth moves
   nothing the reader can see shift. */
.nsi4-field__title {
	font-family: var(--nsi4-font-sans);
	font-size: 15px;
	line-height: 22px;
	color: var(--nsi4-text);
}

.nsi4-field__title--internal {
	color: var(--nsi4-text-3);
}

/* The tooltip's third line: the record's own id and status, 'CT-02 · COMPLETED'
   — or the status alone, and no separator, where a task carries no id. */
.nsi4-field__meta {
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: 16px;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-field__panel {
	padding: var(--nsi4-space-4) var(--nsi4-gutter) 0;
}

.nsi4-field__legend,
.nsi4-field__idle {
	margin: 0;
}

.nsi4-field__legend {
	display: flex;
	align-items: center;
	gap: var(--nsi4-space-3);
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	line-height: 16px;
	font-weight: 500;
	color: var(--nsi4-text-2);
}

.nsi4-field__key {
	width: 9px;
	height: 9px;
	flex: none;
	transform: rotate(27deg);
	background: var(--nsi4-field-key);
}

.nsi4-field__key--open {
	background: none;
	border: 1px solid var(--nsi4-field-key);
}

.nsi4-field__idle {
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	line-height: 16px;
	color: var(--nsi4-text-3);
}

/* The two captions: the pointer's on a mouse, the tap's on a phone. */
.nsi4-field__idle-wide {
	display: none;
}

/* A tapped square puts its three lines in the caption's place, which is board
   8's state — the ring and the enlarged squares are the field's, the words are
   here. Nothing is shown until the tap lands. */
.nsi4-field__pick {
	display: none;
	margin-block-start: var(--nsi4-space-2);
}

@media (max-width: 1099.98px) {
	/* Below the desktop layout the products keep their distance from the
	   field's caption: the narrow band carries no rule above the header, and
	   the header stands a finger's width clear of the field's last line. */
	.nsi4-field-products {
		padding-block-start: var(--nsi4-space-6);
	}

	/* The caption's space holds both of its states, so a tap — and a clear —
	   leave the products below it exactly where they were. The two captions
	   share one cell, so the cell is as tall as the taller of them whatever a
	   task title wraps to, and only their visibility changes: `display: none`
	   would take one of them out of the cell's height and the other would hand
	   the slack back. A hidden caption is out of the accessibility tree, so
	   neither state is read while it is not the one shown. */
	.nsi4-field__panel {
		display: grid;
		grid-template-areas:
			"legend"
			"caption";
	}

	.nsi4-field__legend {
		grid-area: legend;
	}

	.nsi4-field__idle,
	.nsi4-field__pick {
		grid-area: caption;
	}

	/* The cell holds all three lines and one title line — 22px, 24px and 18px,
	   the line-heights below, with the 6px gap twice — so the products under it
	   stay where they are when a square is picked. 'Its title stays internal.'
	   and a one-line title are both one line and leave it still; a title that
	   wraps takes exactly its extra lines and no more. */
	.nsi4-field__pick {
		display: grid;
		visibility: hidden;
		gap: 6px;
		min-height: 76px;
	}

	.nsi4-field--picked .nsi4-field__idle {
		visibility: hidden;
	}

	.nsi4-field--picked .nsi4-field__pick {
		visibility: visible;
	}

	/* Under the band there is room for the lines the tooltip cannot carry. */
	.nsi4-field__pick .nsi4-field__what {
		font-size: 15px;
		line-height: 22px;
	}

	.nsi4-field__pick .nsi4-field__title {
		font-size: 17px;
		line-height: 24px;
	}

	.nsi4-field__pick .nsi4-field__meta {
		font-size: 13px;
		line-height: 18px;
	}
}

@media (min-width: 1100px) {
	.nsi4-field-hero__grid {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
		align-items: start;
		max-width: var(--nsi4-content-max);
		margin-inline: auto;
		padding-block-start: var(--nsi4-space-4);
		padding-inline: var(--nsi4-gutter);
	}

	.nsi4-field-hero__copy {
		max-width: none;
		margin-inline: 0;
		padding-block-start: var(--nsi4-space-16);
		padding-inline: 0 var(--nsi4-space-12);
	}

	/* The box's column starts on the shell's centre line; the margin pulls the
	   box 64px back across it, so its left edge sits 64px left of the centre
	   line and its right edge lands on the products header's right end — under
	   Contact us — from a 1200px viewport up.

	   Below a 1200px viewport the shell's 1200 stops binding, the header's own
	   edge is the page's own gutter in, and the box runs past it by 600px less
	   half the page. The margin carries that overhang as well: the box slides
	   right by exactly as much as it overshoots, so its left edge lands on the
	   same x at every width and the copy keeps the clearance it has at 1280. The
	   two terms are one sum, and the max() is the difference between a page wide
	   enough and one that is not — from a 1200px viewport it is 0px and the rule
	   is the 64px pull on its own. */
	.nsi4-field {
		width: 640px;
		height: 536px;
		justify-self: start;
		margin-inline-start: calc(-64px + max(0px, calc(var(--nsi4-content-max) / 2 - 50cqw)));
	}

	.nsi4-field__canvas {
		aspect-ratio: auto;
		height: 100%;
	}

	/* The legend and the pointer's caption sit at the bottom of the band, in
	   the field's empty corner — the rectangle the generator leaves clear of
	   squares, the right three-quarters of the box's last 60px — and never over
	   the band. The figure is what positions them, and the right edge they take
	   is the products header's own. The figure carries the box's overhang — it is
	   the box — so the offset has to carry it too, and it carries it twice: once
	   for the figure sitting past the header's end, and once for the figure
	   itself having been slid right by that same overhang. 1200px - 100cqw is
	   twice 600px - 50cqw, so one `max()` holds both bands — at a 1100 window it
	   draws the panel 115px in, where without the second term the legend would
	   sit 57.5px right of the header's end; from a 1200px viewport it is 0px.
	   The corner is 480 units wide and the panel is 335 of them, so the header's
	   end sits inside it at every width by construction. */
	.nsi4-field__panel {
		position: absolute;
		right: max(0px, calc(var(--nsi4-content-max) - 100cqw));
		bottom: var(--nsi4-space-3);
		padding: 0;
		text-align: right;
	}

	.nsi4-field__legend {
		justify-content: flex-end;
	}

	.nsi4-field__idle {
		margin-block-start: var(--nsi4-space-2);
	}

	.nsi4-field__idle-wide {
		display: inline;
	}

	.nsi4-field__idle-narrow {
		display: none;
	}
}

/* From 1440px the canvas runs past the box's own right edge: the spill set's
   752 units with the canvas's left edge on the box's, so 107 squares float up to
   64px past the header's edge and thin to none. The box stays 640 wide, which
   is what keeps the legend, the caption and the tooltip measured from the box
   here as at every other width. Crossing it rebuilds the field, as crossing
   1100 does — the two layouts lay the same squares inside different bounds, so
   they are two settings and not one reflowed. */
@media (min-width: 1440px) {
	.nsi4-field__canvas {
		width: 752px;
	}
}

/* The products under the field: the header, then the five tiles. */
.nsi4-field-products {
	max-width: var(--nsi4-content-max);
	margin-inline: auto;
	padding-block-end: var(--nsi4-space-12);
	padding-inline: var(--nsi4-gutter);
}

.nsi4-field-products__head {
	display: flex;
	align-items: baseline;
	gap: var(--nsi4-space-2);
	padding-block-start: var(--nsi4-space-3);
}

.nsi4-field-products__head > * {
	margin: 0;
}

.nsi4-field-products__tiles {
	display: grid;
	gap: var(--nsi4-space-6);
	margin-block-start: var(--nsi4-space-3);
}

/* A tile. On a phone it is a row: the mark, the name over the proof line, the
   arrow at the right, and a hairline above each. From 1100px it is the board's
   card: the mark, name and arrow on the first line, the proof across the
   second, in the surface step the other cards use. */
/* A tile's content starts at its top, and the rows are packed rather than
   stretched. A tile is as tall as its tallest neighbour in the row, and a proof
   line that wraps must not hand the slack back to the row above it — with the
   grid's default stretch the free space is split between two auto rows, so the
   taller the proof, the lower the name sits in every tile beside it. */
.nsi4-tile {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto;
	grid-template-areas:
		"mark name arrow"
		"mark proof arrow";
	column-gap: var(--nsi4-space-3);
	row-gap: var(--nsi4-space-1);
	align-content: start;
	align-items: start;
	min-height: 64px;
	padding-block: var(--nsi4-space-3);
	border-block-start: 1px solid var(--nsi4-hairline);
	color: var(--nsi4-text);
	text-decoration: none;
}

/* Phoenix underlines every link it hovers, and `a:hover` outranks a class, so
   the tile's own hover state arrives with an underline under its name and
   proof line as well. Same declaration, one pseudo-class up: the tile is the
   hover, not the words in it. */
.nsi4-tile:hover,
.nsi4-tile:focus-visible {
	text-decoration: none;
}

.nsi4-tile__mark {
	grid-area: mark;
	display: block;
	width: 20px;
	height: 20px;
}

.nsi4-tile__name {
	grid-area: name;
	font-family: var(--nsi4-font-sans);
	font-size: 17px;
	line-height: 24px;
	font-weight: 600;
}

.nsi4-tile__proof {
	grid-area: proof;
	font-family: var(--nsi4-font-sans);
	font-size: 15px;
	line-height: 22px;
	color: var(--nsi4-text-2);
}

.nsi4-tile__arrow {
	grid-area: arrow;
	font-size: 18px;
	color: var(--nsi4-text-3);
}

/* The tile answers in its own product's colour. Keyboard focus raises it on
   every device — one that reports `hover: none` still has a link to tab to. */
.nsi4-tile:focus-visible {
	border-color: var(--nsi4-accent);
	background: var(--nsi4-raised);
}

.nsi4-tile:focus-visible .nsi4-tile__arrow {
	color: var(--nsi4-accent);
}

/* Pointer hover, on the devices that have a pointer. */
@media (hover: hover) {
	.nsi4-tile:hover {
		border-color: var(--nsi4-accent);
		background: var(--nsi4-raised);
	}

	.nsi4-tile:hover .nsi4-tile__arrow {
		color: var(--nsi4-accent);
	}
}

@media (min-width: 640px) {
	.nsi4-field-products__tiles {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (min-width: 1100px) {
	.nsi4-field-products__head {
		justify-content: space-between;
		/* One rule at the top of the products, from 1100px up: below it the
		   narrow band separates the products from the field by its own height,
		   and a rule there crowds the caption. */
		border-block-start: 1px solid var(--nsi4-hairline);
	}

	/* One line at each end of the shell needs no separator between them. */
	.nsi4-field-products__sep {
		display: none;
	}

	.nsi4-field-products__tiles {
		grid-template-columns: repeat(5, minmax(0, 1fr));
		/* space-4, not the space-6 the other two grids carry: five cards
		   share one shell, and at 1100px a space-6 gutter is 24px of every
		   tile's width that its name does not have. The rule is here, inside
		   the desktop breakpoint, so the tablet's three columns keep theirs —
		   and it is the state the tiles keep up to the switch width below,
		   where the board's spacing returns. */
		gap: var(--nsi4-space-4);
		margin-block-start: var(--nsi4-space-2);
	}

	.nsi4-tile {
		grid-template-areas:
			"mark name arrow"
			"proof proof proof";
		row-gap: var(--nsi4-space-2);
		/* The board's tile: a 124px floor, the surface step and the one
		   highlight the system has. Its 20px padding is the board's too, but
		   20 is not one of the system's steps, and five tiles side by side
		   from 1100px cannot hold 'fromScratch' clear of its arrow with it —
		   the tightest tile is 9px short. So from here the padding takes the
		   step below: space-4, paired with the space-4 gutter above, which
		   buys the name 17px and leaves the floor and everything else as the
		   board drew them. From the switch width below, both go back to the
		   board's values. */
		min-height: 124px;
		padding: var(--nsi4-space-4);
		border: 1px solid var(--nsi4-hairline);
		border-radius: var(--nsi4-radius-lg);
		background: var(--nsi4-surface);
		box-shadow: var(--nsi4-highlight-top);
		transition: background-color var(--nsi4-dur) var(--nsi4-ease), border-color var(--nsi4-dur) var(--nsi4-ease);
	}
}

/* The tiles' switch width: 1146px, read off the publish.
   Above 1100px the five tiles have to choose between the board's spacing and
   RD-104's, and they only have to choose below this width. The board's is the
   space-6 gutter and the 20px padding; the step below is space-4 for both.
   Reading the tightest tile's name-to-arrow gap off the publish across the
   widths between, the board's spacing is 7.94px clear of the arrow at 1145px
   and 8.13px at 1146px, so this is the narrowest width it fits:
   'fromScratch' is the tightest name, and its column grows 0.2px for every
   pixel of window width until the content max holds the tile at 211px from
   1220px. Below the switch nothing is lost but the spacing, which is why
   only the narrow desktop widths carry the tighter pair. */
@media (min-width: 1146px) {
	.nsi4-field-products__tiles {
		gap: var(--nsi4-space-6);
	}

	.nsi4-tile {
		padding: 20px;
	}
}

@media (min-width: 1100px) {
	.nsi4-products__third {
		grid-column: span 2;
	}
}

/* Product-page blocks. The hero band is the full-bleed strip a product page
   wraps its .nsi4-hero in; the platforms grid and the chat are the two content
   blocks the case sections are built from. */
.nsi4-hero-band {
	position: relative;
}

/* The band is full-bleed but its rule is not: inset from both edges by the
   shell's own offset, so the line starts and ends on the content column at
   every width. */
.nsi4-hero-band::after {
	content: "";
	position: absolute;
	bottom: 0;
	left: max(var(--nsi4-gutter), calc((100% - var(--nsi4-content-max)) / 2 + var(--nsi4-gutter)));
	right: max(var(--nsi4-gutter), calc((100% - var(--nsi4-content-max)) / 2 + var(--nsi4-gutter)));
	border-top: 1px solid var(--nsi4-hairline);
}

/* The band's own rule is the first case section's divider, so the two do not
   draw one above the other. */
.nsi4-hero-band + .nsi4-case::before {
	content: none;
}

/* The Trinity ticker — the page's one orchestrated motion. An ambient strip of
   pair and latency entries that runs across the top of the hero band, above the
   copy rather than under it, and carries no prices. The six items flow as one
   row inside the band's top padding — --nsi4-hero-pad-top, the copy's own top
   edge — so no label can land behind the copy at any width; where the six do
   not fit, the strip clips them at the band's edge. The stagger (durations and
   delays) is the live page's own. Its resting opacity is the texture level, so
   the copy below keeps its contrast with every item at full fade. It paints
   under the copy through the band's own stacking context (`.nsi4-texture`). */
.nsi4-ticker {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: var(--nsi4-hero-pad-top);
	z-index: -1;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--nsi4-space-6);
	overflow: hidden;
	pointer-events: none;
	padding-inline: max(var(--nsi4-gutter), calc((100% - var(--nsi4-content-max)) / 2 + var(--nsi4-gutter)));
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	opacity: var(--nsi4-texture-opacity);
	color: var(--nsi4-text-3);
}

.nsi4-ticker__item {
	flex: none;
	white-space: nowrap;
	animation: nsi4-ticker-fade 4s ease-in-out infinite;
}

.nsi4-ticker__item--up {
	color: var(--nsi4-accent);
}

.nsi4-ticker__item:nth-child(2) {
	animation-duration: 5s;
	animation-delay: 1s;
}

.nsi4-ticker__item:nth-child(3) {
	animation-duration: 6s;
	animation-delay: 2s;
}

.nsi4-ticker__item:nth-child(4) {
	animation-duration: 4.5s;
	animation-delay: 1.5s;
}

.nsi4-ticker__item:nth-child(5) {
	animation-duration: 5.5s;
	animation-delay: 0.5s;
}

.nsi4-ticker__item:nth-child(6) {
	animation-duration: 6s;
	animation-delay: 2.5s;
}

@keyframes nsi4-ticker-fade {
	0%,
	100% {
		opacity: 0;
	}

	50% {
		opacity: 1;
	}
}

.nsi4-platforms {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-4);
}

.nsi4-platform {
	display: grid;
	gap: var(--nsi4-space-2);
	/* Cards in a row are equal height; the slack belongs at the card's foot.
	   Spread over the rows instead (the default), a stretched name row pushes
	   the body down and the bodies stop lining up across the row. */
	align-content: start;
	padding: var(--nsi4-space-6);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
}

.nsi4-platform__name {
	margin: 0;
	display: flex;
	/* Top-aligned to the name's first line: a centred icon drifts down a
	   wrapped or stretched name and no longer starts level with the text. */
	align-items: flex-start;
	gap: var(--nsi4-space-2);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	font-weight: 600;
}

.nsi4-platform__name .nsi4-icon {
	color: var(--nsi4-accent);
}

.nsi4-platform__body {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

/* Definition list — the compact alternative to a row of cards, for a group's
   later parts (WRS's What it does and The assistant, Trinity's three parts).
   Name left, description right, a hairline above every row: no card, no icon.
   The name is the item's own heading (h4 under a group h3, h3 under the
   page's h2), not .nsi4-spec's mono key. The name column is about 12rem;
   below 640px the name sits over its description. */
.nsi4-definitions {
	display: grid;
}

.nsi4-definition {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-1);
	padding-block: var(--nsi4-space-4);
	border-top: 1px solid var(--nsi4-hairline);
}

.nsi4-definition__name {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: 16px;
	line-height: var(--nsi4-lh-small);
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-definition__body {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

@media (min-width: 640px) {
	.nsi4-definition {
		grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
		gap: var(--nsi4-space-4);
	}
}

/* The people list — the team page's six ruled rows, one per person. A square
   portrait opens each row, left of the name and role: 96px below 1200px and
   120px from it, 16px from the text beside it. The name and role stay left,
   what they work on and their profile links right, 4/8 from 900px and stacked
   below it; 24px of padding on the row and a hairline between rows. The side
   is one flex row so no cell is left empty when a person has no profile
   links. */
.nsi4-people {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

.nsi4-person {
	display: grid;
	gap: var(--nsi4-space-4);
	align-content: start;
	padding-block: var(--nsi4-space-6);
}

.nsi4-person + .nsi4-person {
	border-top: 1px solid var(--nsi4-hairline);
}

/* The id half of a row. Flex rather than the stacked grid it was, so the
   portrait can sit beside the name and role; flex-start is what puts the
   name's line box top on the portrait's top edge. */
.nsi4-person__id {
	display: flex;
	align-items: flex-start;
	gap: var(--nsi4-space-4);
}

/* Name over role, 12px apart as before, in a column that can shrink so a long
   role wraps inside the row instead of widening it. */
.nsi4-person__text {
	display: grid;
	gap: var(--nsi4-space-3);
	align-content: start;
	min-width: 0;
}

/* The portrait. A fixed square rather than a flexible one: a long role wraps
   in the text beside it and never shrinks the face. `cover` is belt and braces
   on a crop the script already made square. */
.nsi4-person__portrait {
	flex: none;
	width: 96px;
	height: 96px;
	border-radius: var(--nsi4-radius-md);
	object-fit: cover;
}

/* Where the photograph has not arrived yet, the same frame is filled with the
   person's initials. It carries no meaning of its own — the name beside it is
   the name — so it is hidden from assistive technology. */
.nsi4-person__portrait--initials {
	display: grid;
	place-items: center;
	background: var(--nsi4-raised);
	font-family: var(--nsi4-font-mono);
	font-size: 26px;
	font-weight: 500;
	line-height: 1;
	color: var(--nsi4-text-3);
}

.nsi4-person__side {
	display: grid;
	gap: var(--nsi4-space-3);
	align-content: start;
	justify-items: start;
}

/* The name is the row's heading: 19/28 and 600 sit between the system's
   heading-3 (24/32) and body (17/27) steps, as a raw value. */
.nsi4-person__name {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: 19px;
	line-height: 28px;
	font-weight: 600;
}

.nsi4-person__role {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

/* The products line beside the 'Works on' label, sharing its baseline. */
.nsi4-person__work {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--nsi4-space-4);
}

.nsi4-person__work .nsi4-eyebrow {
	margin: 0;
}

.nsi4-person__products {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

/* The profile links at the row's right end. Icon-only, so the accessible
   name is the anchor's aria-label and the glyph carries none. The anchor is
   the target, not the 20px icon, so it takes the touch minimum in both
   directions. */
.nsi4-person__links {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--nsi4-space-2);
}

.nsi4-person__links a {
	display: grid;
	place-items: center;
	min-width: var(--nsi4-touch-min);
	min-height: var(--nsi4-touch-min);
	border-radius: var(--nsi4-radius-md);
	color: var(--nsi4-text-2);
	font-size: 20px;
	text-decoration: none;
	transition: color var(--nsi4-dur) var(--nsi4-ease), background-color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-person__links a:hover {
	color: var(--nsi4-text);
	background: var(--nsi4-raised);
}

@media (min-width: 900px) {
	.nsi4-person {
		grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
		gap: var(--nsi4-gutter);
		align-items: start;
	}

	.nsi4-person__side {
		display: flex;
		justify-content: space-between;
		gap: var(--nsi4-space-6);
		align-items: start;
	}
}

/* The portrait grows a step at the width the page's own column does, so the
   face at 1200px up is the size the text beside it has become. Below it the
   96px frame is what every name fits beside. */
@media (min-width: 1200px) {
	.nsi4-person__portrait {
		width: 120px;
		height: 120px;
	}

	.nsi4-person__portrait--initials {
		font-size: 32px;
	}
}

/* The AI conversation. It is a static transcript: no typing effect, no reveal.
   The column is sized explicitly so the bubbles' 88% cap resolves against the
   panel's own width rather than against an auto track's content. */
.nsi4-chat {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-3);
	padding: var(--nsi4-space-6);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
}

.nsi4-chat__labels {
	display: flex;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: var(--nsi4-space-3);
}

/* Scoped to the chat: a bubble is a <p> inside .nsi4-case__body, so it is a
   body paragraph and takes the measure under its own 88% cap — a transcript
   line may not run wider than any other body line. */
.nsi4-chat .nsi4-bubble {
	max-width: min(88%, var(--nsi4-measure));
	margin: 0;
	padding: var(--nsi4-space-3) var(--nsi4-space-4);
	border-radius: var(--nsi4-radius-lg);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
}

.nsi4-bubble--you {
	justify-self: end;
	background: var(--nsi4-raised);
	color: var(--nsi4-text);
	border-bottom-right-radius: var(--nsi4-radius-sm);
}

.nsi4-bubble--ai {
	justify-self: start;
	background: var(--nsi4-ground);
	border: 1px solid var(--nsi4-hairline);
	border-bottom-left-radius: var(--nsi4-radius-sm);
}

.nsi4-bubble--ai b {
	color: var(--nsi4-accent);
	font-weight: 600;
}

/* The program breakdown — a label over a list, the two abreast from 900px.
   The partnerships page composes the same block into its columns below. */
.nsi4-lists {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-8);
}

.nsi4-list-block {
	display: grid;
	gap: var(--nsi4-space-3);
	align-content: start;
}

.nsi4-list {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--nsi4-space-2);
}

.nsi4-list li {
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

@media (min-width: 900px) {
	.nsi4-lists {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--nsi4-gutter);
	}
}

/* The ruled variant — the fromScratch Codex's six groups as a grid of ruled
   cells, three across from 900px, two from 640px, one below. Only the
   container gains a class: the cells and the lists are the shared
   program-breakdown ones above, which the partnerships columns use too. */
.nsi4-lists--ruled {
	gap: var(--nsi4-space-8) var(--nsi4-gutter);
}

.nsi4-lists--ruled .nsi4-list-block {
	border-top: 1px solid var(--nsi4-hairline);
	padding-top: var(--nsi4-space-4);
}

.nsi4-lists--ruled .nsi4-list li {
	color: var(--nsi4-text);
}

@media (min-width: 640px) {
	.nsi4-lists--ruled {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 900px) {
	.nsi4-lists--ruled {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* The partnerships programs — three columns from 900px, stacked below. Each
   column subgrids four shared rows (name, offer, look-for, action) over the
   parent's explicit grid, so the second list label and the three buttons
   hold one line across the three columns. The gap is written on both grids
   so the shared tracks read the same gutters either way it is resolved. */
.nsi4-programs {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-8);
}

.nsi4-program {
	display: grid;
	gap: var(--nsi4-space-8);
	align-content: start;
}

@media (min-width: 900px) {
	.nsi4-programs {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		grid-template-rows: repeat(4, auto);
		gap: var(--nsi4-space-8) var(--nsi4-gutter);
	}

	.nsi4-program {
		grid-row: span 4;
		grid-template-rows: subgrid;
		gap: var(--nsi4-space-8) var(--nsi4-gutter);
	}
}

/* A platform tile that is a link — the hub's three program tiles. The surface
   step and the hover are .nsi4-product's, which is also an anchor. */
a.nsi4-platform {
	color: var(--nsi4-text);
	text-decoration: none;
	transition: background-color var(--nsi4-dur) var(--nsi4-ease), border-color var(--nsi4-dur) var(--nsi4-ease);
}

a.nsi4-platform:hover {
	background: var(--nsi4-raised);
	border-color: var(--nsi4-control-border);
}

@media (min-width: 640px) {
	.nsi4-platforms {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* A row of exactly three tiles — the Community hub's partner programs, Team's
   principles. Two columns would leave the third tile orphaned
   under a pair, so a three-tile row skips that step: one column, then three
   equal columns at 900px. Keyed off the tile count, so no page carries a class
   for it. The third tile must also be the last, or a four-tile row would match
   on its third tile and be laid out three across. Every other tile count keeps
   the 640px step above. */
.nsi4-platforms:has(> .nsi4-platform:nth-child(3):last-child) {
	grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 900px) {
	.nsi4-platforms:has(> .nsi4-platform:nth-child(3):last-child) {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* The article page — the one shell the six posts and papers share. One 4/8
   grid from 900px, the tracks .nsi4-case takes: the rail holds the line and
   the facts about the piece, and the title and the body share the right
   column's left edge. Below 900px it is one column and order runs line,
   title, meta, body. */
.nsi4-article__grid {
	display: grid;
	/* One column that can shrink below its content: an implicit auto track
	   takes the min-content of what is inside it, so an unwrappable item — a
	   wide table cell, a long unbreakable run — would widen the whole page to
	   fit. The track is clamped at zero instead. */
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-6);
	/* The band the header used to be: the hero's top edge over a section's
	   foot, so the body meets the next band on the normal section rhythm. */
	padding-block: var(--nsi4-hero-pad-top) var(--nsi4-section-pad);
}

/* The rail writes no box of its own below 900px: display: contents hands its
   two children to the grid, and order puts the title between them. */
.nsi4-article__rail {
	display: contents;
}

.nsi4-article__rail > nav {
	order: 1;
}

.nsi4-article__head {
	order: 2;
}

.nsi4-article__rail dl {
	order: 3;
}

.nsi4-prose {
	order: 5;
}

/* The one line above the title: the eyebrow's metrics in the casing it is
   written in, because it names a place rather than a section — .nsi4-keep-case
   in the markup says that out loud, so no uppercase is declared here. The hub
   is the only link in it and it sits inside a sentence, beside the category,
   so it is underlined rather than left to colour alone: the two greys are
   1.42:1 apart, which is not a difference of its own. */
.nsi4-article__line {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	color: var(--nsi4-text-3);
}

.nsi4-article__line a {
	color: var(--nsi4-text-2);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	transition: color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-article__line a:hover {
	color: var(--nsi4-text);
}

/* The facts about the piece. On a phone they read as one wrapped line with no
   labels, every non-last pair closed by a middot; from 900px they are a
   labelled stack 24px under the line. The dl is flex at both widths because a
   whitespace-only text node between the pairs is not rendered as a flex item,
   and would otherwise open a line box of its own. The 24px leading has no step
   at the 15px small size, so it is a literal. */
.nsi4-article__rail dl {
	display: flex;
	flex-wrap: wrap;
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

.nsi4-article__rail dl > div {
	display: flex;
	flex-direction: column;
	gap: var(--nsi4-space-1);
}

.nsi4-article__rail dt {
	display: none;
}

.nsi4-article__rail dd {
	margin: 0;
}

.nsi4-article__rail dl > div:not(:last-child) dd::after {
	content: "·";
	margin-inline: var(--nsi4-space-2);
}

/* "Nicolas Siatras" over "CEO & Founder" on the rail; ", CEO & Founder"
   beside the name in the phone's one line. */
.nsi4-article__role::before {
	content: ", ";
}

/* The contents, below 900px: a disclosure under the meta, closed, whose summary
   names the list and how many sections it holds. It is the rail's own markup at
   both widths - one details, one nav, one list of links, on the same hairline
   track. The phone's list carries no reading marks: which section is in view
   is the rail's business, and a folded list has no business saying so. */
.nsi4-article__contents {
	order: 4;
}

.nsi4-article__contents-head {
	display: none;
}

.nsi4-article__contents > summary {
	min-height: 44px;
	padding-block: 14px;
	color: var(--nsi4-text-2);
	transition: color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-article__contents > summary:hover {
	color: var(--nsi4-text);
}

/* 14px/20px is the mockup's own step and the file's smallest named step is
   15px, so both are literals. */
.nsi4-article__contents-list {
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: 14px;
	line-height: 20px;
	border-left: 1px solid var(--nsi4-hairline);
}

/* Below 900px each link is a row on that track, not a run of the browser's
   link colour: 12px above and below the 20px line is exactly the 44px touch
   minimum, 16px off the track is the rail's own inset, and no underline.
   From 900px the same selector is restated at 6px, which is what keeps the
   rail's rows at the rail's density. */
.nsi4-article__contents-list a {
	display: block;
	padding: 12px 0 12px 16px;
	color: var(--nsi4-text-2);
	text-decoration: none;
	transition: color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-article__contents-list a:hover {
	color: var(--nsi4-text);
}

.nsi4-article__contents-top {
	display: inline-block;
	margin-top: var(--nsi4-space-3);
	color: var(--nsi4-text-2);
	transition: color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-article__contents-top:hover {
	color: var(--nsi4-text);
}

@media (min-width: 900px) {
	.nsi4-article__grid {
		grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
		column-gap: var(--nsi4-gutter);
		grid-template-areas:
			"rail head"
			"rail body";
	}

	/* The rail sticks for as long as the article runs: a sticky grid item is
	   held inside its own area, so it stops when the body does. The offset is
	   the one PO-02's case guide already reads. */
	.nsi4-article__rail {
		display: grid;
		gap: var(--nsi4-space-6);
		grid-area: rail;
		position: sticky;
		top: var(--nsi4-sticky-top);
		align-self: start;
	}

	.nsi4-article__head {
		grid-area: head;
	}

	.nsi4-prose {
		grid-area: body;
	}

	.nsi4-article__rail dl {
		flex-direction: column;
		flex-wrap: nowrap;
		gap: var(--nsi4-space-4);
		color: var(--nsi4-text);
	}

	.nsi4-article__rail dt {
		display: block;
		font-family: var(--nsi4-font-mono);
		font-size: var(--nsi4-fs-eyebrow);
		line-height: var(--nsi4-lh-eyebrow);
		font-weight: var(--nsi4-fw-eyebrow);
		letter-spacing: var(--nsi4-ls-eyebrow);
		text-transform: uppercase;
		color: var(--nsi4-text-3);
	}

	.nsi4-article__rail dd {
		color: var(--nsi4-text);
	}

	.nsi4-article__rail dl > div:not(:last-child) dd::after {
		content: none;
	}

	/* "10 min" on the rail; the phone's line reads "10 min read". */
	.nsi4-article__read {
		display: none;
	}

	.nsi4-article__role {
		display: block;
		color: var(--nsi4-text-2);
	}

	.nsi4-article__role::before {
		content: none;
	}

	/* The rail's contents from 900px: a hairline rule, the label and its
	   counter, the sections on a track, and a way back to the top. The details is
	   the same element the phone folds, opened here for good - the open attribute
	   cannot be set from a stylesheet, so the two ways a browser hides a closed
	   details are both overruled: the display of the children, and the
	   content-visibility of ::details-content. A detail left folded at 1280 would
	   be an empty rail, which is what this pair is here to prevent. */
	.nsi4-article__contents > summary {
		display: none;
	}

	.nsi4-article__contents > nav {
		display: block;
	}

	.nsi4-article__contents::details-content {
		content-visibility: visible;
	}

	.nsi4-article__contents > nav {
		padding-top: 20px;
		border-top: 1px solid var(--nsi4-hairline);
	}

	.nsi4-article__contents-head {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		margin: 0 0 var(--nsi4-space-2);
	}

	.nsi4-article__contents-count {
		font-family: var(--nsi4-font-mono);
		font-size: var(--nsi4-fs-eyebrow);
		line-height: var(--nsi4-lh-eyebrow);
		font-weight: var(--nsi4-fw-eyebrow);
		letter-spacing: var(--nsi4-ls-eyebrow);
		color: var(--nsi4-text-3);
	}

	.nsi4-article__contents-list {
		border-left: 1px solid var(--nsi4-hairline);
	}

	.nsi4-article__contents-list a {
		position: relative;
		display: block;
		padding: 6px 0 6px 16px;
		color: var(--nsi4-text-2);
		text-decoration: none;
		transition: color var(--nsi4-dur) var(--nsi4-ease);
	}

	.nsi4-article__contents-list a:hover {
		color: var(--nsi4-text);
	}

	/* Read and current are the script's marks. Without it every link keeps the
	   base grey and the list is still a list. */
	.nsi4-article__contents-list a[data-read] {
		color: var(--nsi4-text-3);
	}

	.nsi4-article__contents-list a[aria-current="location"] {
		color: var(--nsi4-text);
		font-weight: 500;
	}

	/* The 2px bar sits over the track's 1px rule, at the link's left edge. */
	.nsi4-article__contents-list a[aria-current="location"]::before {
		content: "";
		position: absolute;
		left: -1px;
		top: 0;
		bottom: 0;
		width: 2px;
		background: var(--nsi4-text);
	}
}

/* The featured image is the page's largest element, so it loads eagerly. Only
   the block margins are declared: the figure now sits inside
   .nsi4-article__head in the grid's right-hand column, where it carries no
   shell and the theme's `figure { margin: 0 0 1rem }` has already left the
   inline half at zero. The title above it sets `margin: 0`, so this top margin
   is the whole gap between the headline's box and the image's. */
.nsi4-article__figure {
	margin-block: var(--nsi4-space-8) var(--nsi4-space-12);
}

.nsi4-article__figure img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: var(--nsi4-radius-lg);
}

/* The generated-art label, in the papers' caption style (.nsi4-figure figcaption)
   so a lead image and a figure caption read the same way. A label, never a sentence. */
.nsi4-article__figure figcaption {
	margin-block-start: var(--nsi4-space-3);
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

/* The article body. The pages write plain HTML — p, h2, h3, ul, ol, dl, table,
   figure, strong, a, code, sup — so the rhythm lives here rather than on every
   element, and the first and last children sit flush inside the shell. */
.nsi4-prose {
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	color: var(--nsi4-text);
	/* The article's body measure. --nsi4-measure (50ch, 510px here) reads 59
	   characters on the second body paragraph of /blog/trinity-philosophy at
	   1280px, under the 62-72 target, so the article sets its own width: at
	   530px the longest first full line across the six bodies is 71
	   characters. A literal with no step, like the section head's 720px. From
	   900px the measure moves to the rule's direct children (see the 900px
	   block). */
	max-width: 530px;
	/* min-width: 0 drops the automatic minimum a grid item would otherwise
	   take from its widest unwrappable line, which is what keeps the measure
	   on a phone instead of pushing the track out to it. */
	min-width: 0;
}

@media (min-width: 900px) {
	/* From 900px the body column outgrows the text's measure, so the measure
	   moves to the children: the text keeps 530px, and an essay figure takes
	   the whole column, the lead image's width. */
	.nsi4-prose {
		grid-area: body;
		max-width: none;
	}

	.nsi4-prose > :not(.nsi4-essay-figure) {
		max-width: 530px;
	}
}

.nsi4-prose > :first-child {
	margin-block-start: 0;
}

.nsi4-prose > :last-child {
	margin-block-end: 0;
}

.nsi4-prose p {
	margin-block: 0 var(--nsi4-space-6);
}

/* The summary, scoped to the article: .nsi4-type-lede on a hub hero keeps its
   own step and colour. 19/29 on a phone and 22/34 from 900px — neither leading
   has a step in the scale, so both are literals — at full text brightness,
   with 40px under it before the first heading. */
.nsi4-prose .nsi4-type-lede {
	font-size: 19px;
	line-height: 29px;
	color: var(--nsi4-text);
	margin-block-end: 40px;
}

@media (min-width: 900px) {
	.nsi4-prose .nsi4-type-lede {
		font-size: 22px;
		line-height: 34px;
	}
}

/* The abstract: the eyebrow, then the summary the prose styles above, then in
   prompt-complexity's case the abstract paragraph itself at body size. 40px of
   clear space under the last paragraph, a hairline, then 40px before the body
   — 40 has no step (the scale runs 32 → 48), so both are literals. The padding
   reset is because the theme's bare `section` rule opens with 40px of its own. */
.nsi4-abstract {
	padding-block: 0 40px;
	margin-block-end: 40px;
	border-bottom: 1px solid var(--nsi4-hairline);
}

.nsi4-abstract > :last-child {
	margin-block-end: 0;
}

.nsi4-abstract > .nsi4-eyebrow {
	margin-block: 0 var(--nsi4-space-2);
}

/* Article headings sit a step under the sheet's: 24/32 is the heading-3 step,
   which keeps an h2 over its own section instead of over the page. The 56px
   above it has no step (the scale runs 48 → 64), so it is a literal. */
.nsi4-prose h2 {
	margin-block: 56px var(--nsi4-space-3);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-3);
	line-height: var(--nsi4-lh-heading-3);
	font-weight: var(--nsi4-fw-heading-3);
	letter-spacing: var(--nsi4-ls-heading-3);
}

/* 19/28 is a step under the heading-3 step and the scale names no token for
   it, so both values are literals. */
.nsi4-prose h3 {
	margin-block: var(--nsi4-space-8) var(--nsi4-space-2);
	font-family: var(--nsi4-font-sans);
	font-size: 19px;
	line-height: 28px;
	font-weight: 600;
	letter-spacing: 0;
}

.nsi4-prose ul,
.nsi4-prose ol {
	margin-block: 0 var(--nsi4-space-6);
	padding-inline-start: var(--nsi4-space-6);
}

.nsi4-prose li {
	margin-block-start: var(--nsi4-space-2);
}

.nsi4-prose li::marker {
	color: var(--nsi4-text-3);
}

.nsi4-prose strong {
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-prose a {
	color: var(--nsi4-accent);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	text-decoration-color: var(--nsi4-control-border);
}

.nsi4-prose a:hover {
	text-decoration-color: var(--nsi4-accent);
}

.nsi4-prose code {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data);
	line-height: var(--nsi4-lh-data);
}

/* Three box looks on one box element. Every box takes 40px clear above and
   below — the clearance target is 40 and the scale jumps 32 → 48, so it is a
   literal like the section head's 720px — and sibling margins collapse, which
   is what makes 40px hold under an h2's 12px and a paragraph's 24px alike.
   The note itself is the surface step with a hairline edge. */
.nsi4-note {
	margin-block: 40px;
	padding: var(--nsi4-space-6);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-md);
	box-shadow: var(--nsi4-highlight-top);
	/* Notes set their own text step: 16/26, one below the body. */
	font-size: 16px;
	line-height: 26px;
	color: var(--nsi4-text);
}

.nsi4-note > :first-child {
	margin-block-start: 0;
}

.nsi4-note > :last-child {
	margin-block-end: 0;
}

/* A box's title, whether it is written as a paragraph or as the box's own h3:
   the eyebrow's metrics, 8px above its text. The h3 rule is declared after
   .nsi4-prose h3 — equal specificity, so source order decides. */
.nsi4-note__title {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-note h3 {
	margin-block: 0 var(--nsi4-space-2);
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

/* The prose paragraph rhythm is a class plus a type selector, so the title needs
   a class on both sides of it to take the margin. */
.nsi4-note > .nsi4-note__title {
	margin-block: 0 var(--nsi4-space-2);
}

/* A finding is a ruled pull-out: no fill, no frame, a 2px rule down its left
   edge and text set a step above the body. */
.nsi4-note.nsi4-note--finding {
	padding: 4px 0 4px var(--nsi4-space-6);
	background: none;
	border: 0;
	border-left: 2px solid var(--nsi4-text);
	border-radius: 0;
	box-shadow: none;
	font-size: 21px;
	line-height: 32px;
	font-weight: 500;
	color: var(--nsi4-text);
}

/* A call to action is a raised panel and the article's last child: title, text,
   then the link as a button 24px under the text — the paragraph's own bottom
   margin is what opens that gap, and the button is left-aligned as any inline
   box in the flow would be. */
.nsi4-note.nsi4-note--cta {
	padding: var(--nsi4-space-8);
	background: var(--nsi4-raised);
	border-radius: var(--nsi4-radius-lg);
}

/* The prose link's underline and accent are for words inside a sentence; a
   button carries neither. */
.nsi4-note.nsi4-note--cta a.nsi4-btn {
	color: var(--nsi4-text);
	text-decoration: none;
}

/* Two boxes that stack below 768px need the 40px clearance between their
   borders, not the 16px gutter; side by side from 768px, the gutter returns. */
.nsi4-note-pair {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 40px;
	margin-block: 40px;
}

.nsi4-note-pair > .nsi4-note {
	margin-block: 0;
}

@media (min-width: 768px) {
	.nsi4-note-pair {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--nsi4-gutter);
	}
}

/* The unified formula as a display equation: the body's family at the lede's
   step, 32px clear above and below (a token) and indented 24px (a token). It
   wraps between terms — the phone's 343px column holds it with no scrolling
   and no clipping. The variables are italic, the exponent a real <sup>. */
.nsi4-equation {
	margin-block: var(--nsi4-space-8);
	padding-inline-start: var(--nsi4-space-6);
	font-family: var(--nsi4-font-sans);
	font-size: 20px;
	line-height: 32px;
	color: var(--nsi4-text);
	overflow-wrap: break-word;
}

/* Its three terms are unbreakable: the line can only break in the spaces
   between them, so no term splits across lines at either width. The phone's
   343px column holds the widest term on its own, so nothing overflows. */
.nsi4-equation__term {
	white-space: nowrap;
}

/* Figure 1 sits directly under the equation, in two generated geometries. The
   wide one fills the 530px column from 562px up, so its plot spans 69% of the
   figure; below that the compact one is capped at 400px, which is what keeps
   its labels between 11 and 14px at the phone's 343px column — one
   full-width figure would scale 1.55:1 between the two widths and no single
   label size lands inside the band. */
.nsi4-figure {
	margin-block: 0 40px;
}

.nsi4-figure svg {
	display: block;
	width: 400px;
	max-width: 100%;
	height: auto;
}

.nsi4-figure svg.nsi4-figure__wide {
	display: none;
	width: 100%;
}

@media (min-width: 562px) {
	.nsi4-figure svg.nsi4-figure__wide {
		display: block;
	}

	.nsi4-figure svg.nsi4-figure__compact {
		display: none;
	}
}

.nsi4-figure figcaption {
	margin-block-start: var(--nsi4-space-3);
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

/* Essay figures: Nicolas's two Uncomplicated diagrams, rebuilt (feature
   Decisions `essay-figures`). The pictures are cropped from his images with
   their lettering removed, by Scripts/make-essay-figures.py; every word is
   text here. The figure is its own query container, because from 900px it
   takes the article column while the text keeps its 530px measure: its own
   width, not the viewport's, decides one column or two. */
.nsi4-essay-figure {
	container-type: inline-size;
	margin: var(--nsi4-space-6) 0 40px;
}

.nsi4-essay-figure__panel {
	padding: var(--nsi4-space-4);
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
}

/* The generated-art label, in the lead image's caption style. */
.nsi4-essay-figure figcaption {
	margin-block-start: var(--nsi4-space-3);
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

.nsi4-essay-figure img {
	display: block;
	max-width: 100%;
	height: auto;
}

/* Each tone is the colour its name had in Nicolas's image; all clear 4.5:1 on
   --nsi4-surface. A paragraph in a figure sets its own margin: the selectors
   carry .nsi4-prose so that the prose's 24px under every paragraph loses. */
.nsi4-prose .nsi4-essay-figure__name {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: 19px;
	line-height: 28px;
	font-weight: 600;
	color: var(--tone);
}

.nsi4-prose .nsi4-essay-figure__line {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

/* Evaporation, not disruption: the two sides stack, then sit side by side
   from a 500px figure with a hairline between them. */
.nsi4-shift__sides {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
}

.nsi4-shift__side--disruption {
	--tone: #93B3E8;
}

.nsi4-shift__side--evaporation {
	--tone: #FEA325;
}

.nsi4-shift__side + .nsi4-shift__side {
	margin-block-start: var(--nsi4-space-6);
	padding-block-start: var(--nsi4-space-6);
	border-top: 1px solid var(--nsi4-hairline);
}

.nsi4-shift__art {
	position: relative;
	margin-block: var(--nsi4-space-3);
}

.nsi4-shift__art img {
	width: 100%;
}

/* A ring's name sits 24px under the ring, centred on it: the positions are the
   rings' centres in the source, over each picture's crop (580 and 600 wide,
   380 high). The shadow keeps a name readable over the drifting dots. */
.nsi4-shift__label {
	position: absolute;
	top: 84.7%;
	transform: translate(-50%, -50%);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: 16px;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	white-space: nowrap;
	color: #CFE0F2;
	text-shadow: 0 0 6px var(--nsi4-surface), 0 0 2px var(--nsi4-surface);
}

.nsi4-shift__label--you {
	left: 24.1%;
}

.nsi4-shift__side--evaporation .nsi4-shift__label--you {
	left: 20.2%;
}

.nsi4-shift__label--rival {
	left: 77.1%;
	color: #F7A58F;
}

.nsi4-prose .nsi4-shift__verdict {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	font-weight: 500;
	color: var(--nsi4-text);
}

/* The four fates: a list on a phone, orb beside the words; a 2x2 grid from a
   500px figure, orb over the words, in the essay's order. The hairlines are
   the list's own background showing through a 1px gap. */
.nsi4-prose .nsi4-fates__head {
	display: flex;
	align-items: center;
	gap: var(--nsi4-space-3);
	margin: 0 0 var(--nsi4-space-3);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: 16px;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--nsi4-text-2);
}

.nsi4-fates__head img {
	width: 32px;
	height: 32px;
}

.nsi4-prose .nsi4-fates__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 1px;
	margin: 0;
	padding: 0;
	list-style: none;
	background: var(--nsi4-hairline);
}

.nsi4-prose .nsi4-fates__fate {
	display: grid;
	grid-template-columns: 88px minmax(0, 1fr);
	column-gap: var(--nsi4-space-4);
	align-items: start;
	margin: 0;
	padding: var(--nsi4-space-4) 0;
	background: var(--nsi4-surface);
}

.nsi4-fates__fate img {
	grid-row: 1 / span 4;
	width: 88px;
	height: 88px;
}

.nsi4-fates__fate--die {
	--tone: #E96949;
}

.nsi4-fates__fate--shrink {
	--tone: #AAB9D2;
}

.nsi4-fates__fate--pivot {
	--tone: #F69E23;
}

.nsi4-fates__fate--acquire {
	--tone: #E6C443;
}

.nsi4-prose .nsi4-fates__num {
	margin: var(--nsi4-space-2) 0 0;
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: 16px;
	font-weight: 500;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-prose .nsi4-fates__line {
	margin-block-start: var(--nsi4-space-1);
}

/* Each of the image's sentences is a line of its own wherever it fits. */
.nsi4-fates__line span {
	display: block;
}

.nsi4-prose .nsi4-fates__cases {
	margin: var(--nsi4-space-2) 0 0;
	font-family: var(--nsi4-font-mono);
	font-size: 13px;
	line-height: 20px;
	font-weight: 500;
	color: var(--tone);
}

/* A case never breaks inside its name; the dot rides at the end of the line
   it closes. */
.nsi4-fates__cases span {
	white-space: nowrap;
}

.nsi4-fates__cases span:not(:last-child)::after {
	content: "\00a0·";
	color: var(--nsi4-text-3);
}

/* The narrowest phones: a smaller orb keeps 'Constellation Brands' whole. */
@container (max-width: 339px) {
	.nsi4-prose .nsi4-fates__fate {
		grid-template-columns: 64px minmax(0, 1fr);
		column-gap: var(--nsi4-space-3);
	}

	.nsi4-fates__fate img {
		width: 64px;
		height: 64px;
	}
}

@container (min-width: 500px) {
	.nsi4-essay-figure__panel {
		padding: var(--nsi4-space-6);
	}

	.nsi4-shift__sides {
		grid-template-columns: minmax(0, 580fr) minmax(0, 600fr);
	}

	.nsi4-shift__side:first-child {
		padding-inline-end: var(--nsi4-space-6);
	}

	.nsi4-shift__side + .nsi4-shift__side {
		margin-block-start: 0;
		padding-block-start: 0;
		padding-inline-start: var(--nsi4-space-6);
		border-top: 0;
		border-left: 1px solid var(--nsi4-hairline);
	}

	.nsi4-shift__label {
		font-size: 11px;
		line-height: 14px;
	}

	.nsi4-prose .nsi4-fates__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.nsi4-prose .nsi4-fates__fate {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: var(--nsi4-space-2) var(--nsi4-space-4) var(--nsi4-space-6);
		text-align: center;
	}

	.nsi4-fates__fate img {
		width: 112px;
		height: 112px;
	}

	.nsi4-prose .nsi4-fates__num {
		margin-block-start: 0;
	}
}

@container (min-width: 700px) {
	.nsi4-shift__label {
		font-size: 12px;
		line-height: 16px;
	}

	.nsi4-fates__fate img {
		width: 136px;
		height: 136px;
	}
}

/* One table component for the papers' data lists, scoped to the prose — the
   blog posts carry no table. Full column width; header cells on the eyebrow's
   metrics; body cells the small step at a 24px leading (the scale names no
   24 for 15px, so it is a literal); a hairline under every row; 12px of cell
   padding with 16px between columns and none outside the first and last. */
.nsi4-prose .nsi4-table {
	width: 100%;
	margin-block: 0 var(--nsi4-space-6);
	border-collapse: collapse;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text);
}

.nsi4-prose .nsi4-table th {
	padding: var(--nsi4-space-3) var(--nsi4-space-4);
	border-bottom: 1px solid var(--nsi4-hairline);
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	text-align: left;
	color: var(--nsi4-text-3);
	vertical-align: bottom;
}

.nsi4-prose .nsi4-table td {
	padding: var(--nsi4-space-3) var(--nsi4-space-4);
	border-bottom: 1px solid var(--nsi4-hairline);
	vertical-align: top;
}

.nsi4-prose .nsi4-table th:first-child,
.nsi4-prose .nsi4-table td:first-child {
	padding-inline-start: 0;
}

.nsi4-prose .nsi4-table th:last-child,
.nsi4-prose .nsi4-table td:last-child {
	padding-inline-end: 0;
}

/* Numeric columns read right, in mono with tabular figures so the digits
   stack. The header takes only its column's alignment, so it keeps the
   eyebrow's metrics above it. */
.nsi4-prose .nsi4-table td.nsi4-table__num {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data);
	font-variant-numeric: tabular-nums;
	text-align: right;
}

.nsi4-prose .nsi4-table th.nsi4-table__num {
	text-align: right;
}

/* A table that cannot fit scrolls inside its own wrapper rather than moving
   the page. The wrapper is focusable so the scroll region is reachable from
   the keyboard, and overflow-x: auto only ever scrolls when it has to. */
.nsi4-prose .nsi4-table__scroll {
	overflow-x: auto;
}

/* Core Dimensions' heading became its table's caption, in the eyebrow style a
   box title takes. */
.nsi4-prose .nsi4-table caption {
	caption-side: top;
	padding: 0 0 var(--nsi4-space-3);
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	text-align: left;
	color: var(--nsi4-text-3);
}

/* The papers' "Term: description" bullets as definition pairs: the term at
   600 16/24, its definition at the note's 16/26 step with no indent, and 16px
   between pairs — the step the rest of the shell stacks pairs on. */
.nsi4-terms {
	margin-block: 0 var(--nsi4-space-6);
}

.nsi4-terms > div + div {
	margin-block-start: var(--nsi4-space-4);
}

.nsi4-terms dt {
	font-family: var(--nsi4-font-sans);
	font-size: 16px;
	line-height: 24px;
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-terms dd {
	margin: 0;
	font-size: 16px;
	line-height: 26px;
	color: var(--nsi4-text-2);
}

/* Status and Published in a call to action's closing panel: the eyebrow labels
   over their values, stacked on a phone and side by side from 600px. */
.nsi4-note__meta {
	display: grid;
	gap: var(--nsi4-space-3);
	margin-block: var(--nsi4-space-3) var(--nsi4-space-6);
}

.nsi4-note__meta > div {
	display: grid;
	gap: var(--nsi4-space-1);
}

.nsi4-note__meta dt {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-note__meta dd {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text);
}

@media (min-width: 600px) {
	.nsi4-note__meta {
		display: flex;
		gap: var(--nsi4-space-12);
	}
}

/* Two related cards would leave the third column of the links row empty. Keyed
   off the card count, the same shape as the three-tile platform row above. */
@media (min-width: 760px) {
	.nsi4-links:has(> .nsi4-link-card:nth-child(2):last-child) {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* The article index — the blog and research hubs' table of contents. One row
   per article, newest first: the article's lead image, date, category, title,
   excerpt, then read time on the blog and the status chip on research. Rows are
   separated by hairlines and the row's hover raises it one surface step. The
   title link carries the row: its ::after covers the whole item, so the image,
   the date and that last line are part of the same click target. */
.nsi4-index {
	margin: 0;
	padding: 0;
	list-style: none;
}

.nsi4-index__item {
	position: relative;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--nsi4-space-2);
	padding-block: var(--nsi4-space-6);
	border-top: 1px solid var(--nsi4-hairline);
	transition: background-color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-index__item:hover {
	background: var(--nsi4-raised);
}

/* The article's own lead image, cut down to 480px by
   Scripts/make-article-thumbnails.py. It is drawn at its own aspect ratio and
   never cropped, so what the hub shows is the picture the article page shows,
   smaller. Below 900px it is the row's first child and spans the row: the step
   from its bottom edge to the date is the row's 8px gap plus the margin below,
   which takes that gap to the 16px step the shell stacks on elsewhere. Nothing
   here positions the thumbnail against the title link's ::after overlay — that
   overlay is above it already, so a click on the picture opens the row. The
   hairline is the ledger row image's: several of these pictures run near-black
   at their own edges, so on the near-black ground the frame dissolves and the
   thumbnail reads as loose light rather than a picture in a row. The border sits
   inside the box the reset already draws, so the 240px column is still the
   thumbnail's outer width. */
.nsi4-index__thumb {
	display: block;
	width: 100%;
	height: auto;
	margin-block-end: calc(var(--nsi4-space-4) - var(--nsi4-space-2));
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
}

/* The row's copy column: eyebrow, title, excerpt, then read time or the status
   chip, on the 8px half-step the rest of the shell stacks on. */
.nsi4-index__item > div {
	display: grid;
	gap: var(--nsi4-space-2);
	align-content: start;
}

.nsi4-index__item > div > .nsi4-eyebrow,
.nsi4-index__meta,
.nsi4-index__status {
	margin: 0;
}

.nsi4-index__date {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-data);
	line-height: var(--nsi4-lh-data);
	color: var(--nsi4-text-2);
}

.nsi4-index__title {
	margin: 0;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-heading-3);
	line-height: var(--nsi4-lh-heading-3);
	font-weight: var(--nsi4-fw-heading-3);
	letter-spacing: var(--nsi4-ls-heading-3);
}

.nsi4-index__title a {
	color: var(--nsi4-text);
	text-decoration: none;
}

.nsi4-index__title a::after {
	content: "";
	position: absolute;
	inset: 0;
}

/* The excerpt, the same sentence the article serves as its meta description.
   The 24px leading has no step at the 15px small size, so it is a literal. */
.nsi4-index__summary {
	margin: 0;
	max-width: 64ch;
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

.nsi4-index__meta {
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

/* The date takes a fixed first column from 768px and sits above the title
   below it. The row is two columns in that range, so the thumbnail is placed
   across both of them on the first row instead of being left to auto-placement,
   which would put the date down in column one beside it. The gutter is 24px
   from 768px and the thumbnail asks for the 16px step, so the difference is
   taken back here. */
@media (min-width: 768px) {
	.nsi4-index__item {
		grid-template-columns: 12rem minmax(0, 1fr);
		align-items: start;
		gap: var(--nsi4-gutter);
	}

	.nsi4-index__thumb {
		grid-column: 1 / -1;
		grid-row: 1;
		margin-block-end: calc(var(--nsi4-space-4) - var(--nsi4-gutter));
	}
}

/* From 900px the row is three columns and the thumbnail takes the third: a
   fixed 240px at the row's right edge, its top edge on the category eyebrow's,
   which is why the placement is an explicit `grid-row: 1` and not a `-1` span —
   this grid declares columns and no rows, so a `-1` resolves back onto line 1.
   A row whose article has no image keeps the same three columns and an empty
   third one, so its copy is exactly as wide as the rows beside it rather than
   running out under a thumbnail that is not there. */
@media (min-width: 900px) {
	.nsi4-index__item {
		grid-template-columns: 12rem minmax(0, 1fr) 240px;
	}

	.nsi4-index__thumb {
		grid-column: 3;
		grid-row: 1;
		width: 240px;
		margin-block-end: 0;
	}

	/* A picture wider than 1344x768 is letterboxed into the tile's own shape
	   rather than cropped to it, so a row keeps the whole picture and every
	   thumbnail in the list stays one size. Below 900px the rule does not
	   apply and the thumbnail keeps its own ratio. */
	.nsi4-index__thumb--wide {
		aspect-ratio: 1344 / 768;
		object-fit: contain;
		background-color: var(--nsi4-surface);
	}
}

/* 03 and 04 on a phone.
   Both sections carry a guide in the head and a column of blocks in the body,
   and both are far taller at 375px than the same content at 1280px: the head
   stacks above the body instead of beside it, the 72px group rhythm and 40px
   block gap were set for a wide column, and every card, row and note carries
   the padding it has on a desktop. Nothing here changes the content, only the
   space around it, and the whole block is below 640px so the desktop is
   untouched by construction.

   Every selector is scoped by a section modifier rather than by the component,
   because the components are shared: .nsi4-platform also styles fromScratch
   01's three cards, .nsi4-lists the Partnerships page's program breakdown, and
   .nsi4-crop__notes every hero's crop. 03 and 04 both carry
   .nsi4-case--guided, and 04 carries .nsi4-case--evidence as well, so the two
   are not a partition — the rules for the head and the body are written
   against both on purpose, to survive a section that takes only one. The
   component rules below are keyed on .nsi4-case--guided alone because 03 is
   the only one of the two that renders cards, definition rows, a Codex grid or
   a group block; 04 holds a crop, a card and a stat row instead. No page
   outside the five product pages carries either modifier.

   Type never drops below the 15px small step: only the leading tightens, to
   20px, the step the sheet already uses for 13px mono. The sizes with no
   token — the 28px phone h2 and the 6px gaps — are literals, like the 18px
   group heading and the 13px mono above. */
@media (max-width: 639.98px) {
	/* The section takes the compact block padding the short sections already
	   use, and the head sits 24px from the body rather than 32. */
	.nsi4-case--guided,
	.nsi4-case--evidence {
		padding-block: var(--nsi4-section-pad-compact);
		gap: var(--nsi4-space-6);
	}

	/* The guide head: the index 12px under the intro and 6px above the head's
	   other parts, so a two-line intro and a one-row index cost less. The h2
	   keeps the heading-2 size, and the intro keeps the body leading — the two
	   research links in fromScratch's sit on adjacent lines inside that
	   paragraph, and tightening its leading pulls their centres 3px closer for
	   a few pixels of height. The h2 was briefly taken to 28/34 here and
	   reverted: it is a type change rather than one of this block's spacing
	   levers, and WRS 03 clears the target without it. */
	.nsi4-case--guided .nsi4-case__head,
	.nsi4-case--evidence .nsi4-case__head {
		gap: 6px;
	}

	.nsi4-case--guided .nsi4-case__index,
	.nsi4-case--evidence .nsi4-case__index {
		margin-top: var(--nsi4-space-3);
		gap: 6px var(--nsi4-space-3);
	}

	/* The group rhythm is the biggest single lever on a phone: 72px reads as a
	   broken page when three groups stack under a stacked head. 24px, against
	   12px from a group heading to its own content, keeps the two apart. */
	.nsi4-case--guided .nsi4-case__body {
		gap: var(--nsi4-space-6);
	}

	.nsi4-case--evidence .nsi4-case__body {
		gap: var(--nsi4-space-6);
	}

	.nsi4-case--guided .nsi4-case__block,
	.nsi4-case--guided .nsi4-platforms {
		gap: var(--nsi4-space-3);
	}

	/* A tile at 16px padding, 24/20 leading. */
	.nsi4-case--guided .nsi4-platform {
		padding: var(--nsi4-space-4);
		gap: 6px;
	}

	.nsi4-case--guided .nsi4-platform__name {
		line-height: 24px;
	}

	.nsi4-case--guided .nsi4-platform__body {
		line-height: 20px;
	}

	/* A definition row is a ruled line of text, not a card: 8px either side of
	   the name and the description, with the hairline doing the separating. */
	.nsi4-case--guided .nsi4-definition {
		padding-block: var(--nsi4-space-2);
	}

	.nsi4-case--guided .nsi4-definition__name,
	.nsi4-case--guided .nsi4-definition__body {
		line-height: 20px;
	}

	/* The Codex's six groups two across, so they read as three rows of two
	   instead of six rows of one. Each cell is about 160px, which wraps the
	   longer items to two lines — the price of halving the block. */
	.nsi4-case--guided .nsi4-lists--ruled {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--nsi4-space-4) var(--nsi4-space-3);
	}

	.nsi4-case--guided .nsi4-lists--ruled .nsi4-list-block {
		gap: var(--nsi4-space-2);
		padding-top: var(--nsi4-space-3);
	}

	.nsi4-case--guided .nsi4-lists--ruled .nsi4-list {
		gap: var(--nsi4-space-1);
	}

	/* 04: the screen's notes, the component cards and the numbers take the same
	   step down. The trace and the task log keep their full-bleed rows and
	   lose only their row padding. */
	.nsi4-case--evidence .nsi4-crop__notes {
		gap: var(--nsi4-space-2);
	}

	.nsi4-case--evidence .nsi4-chat {
		padding: var(--nsi4-space-4);
		gap: var(--nsi4-space-2);
	}

	.nsi4-case--evidence .nsi4-chat .nsi4-bubble {
		padding: var(--nsi4-space-2) var(--nsi4-space-3);
		line-height: 20px;
	}

	.nsi4-case--evidence .nsi4-change,
	.nsi4-case--evidence .nsi4-cascade {
		padding: var(--nsi4-space-4);
	}

	.nsi4-case--evidence .nsi4-trace .nsi4-evidence-card__head,
	.nsi4-case--evidence .nsi4-tasks .nsi4-evidence-card__head {
		padding: var(--nsi4-space-3) var(--nsi4-space-4);
	}

	.nsi4-case--evidence .nsi4-trace__table th,
	.nsi4-case--evidence .nsi4-trace__table td,
	.nsi4-case--evidence .nsi4-tasks__row,
	.nsi4-case--evidence .nsi4-change__row {
		padding-block: 6px;
	}

	.nsi4-case--evidence .nsi4-trace__foot {
		padding: var(--nsi4-space-3) var(--nsi4-space-4);
	}

	.nsi4-case--evidence .nsi4-cascade__task {
		padding: var(--nsi4-space-3);
	}

	/* A task row is a line of title, not three columns: the id and the chip
	   share row 1 and the title runs the full content width beneath them, so a
	   real title reads as text instead of a column of single words. The chip's
	   auto track keeps it a chip at the right edge rather than a stretched bar. */
	.nsi4-cascade__task {
		grid-template-columns: minmax(0, 1fr) auto;
		row-gap: var(--nsi4-space-2);
	}

	.nsi4-cascade__id {
		grid-row: 1;
		grid-column: 1;
	}

	.nsi4-cascade__chip {
		grid-row: 1;
		grid-column: 2;
	}

	.nsi4-cascade__title {
		grid-row: 2;
		grid-column: 1 / -1;
	}

	/* The 104px indent that lined the arrow up with the title column is dead on a
	   phone, where the title now starts on the id's own left edge — so the arrow
	   takes just the row's own inset: its one-pixel border plus its 12px padding. */
	.nsi4-cascade__unblocks {
		padding-left: calc(1px + var(--nsi4-space-3));
	}

	.nsi4-case--evidence .nsi4-stats {
		gap: var(--nsi4-space-4);
	}

	.nsi4-case--evidence .nsi4-stat {
		gap: 6px;
		padding-top: var(--nsi4-space-3);
	}
}

/* The product mark: the artwork itself, one class for the 16px slots that sit
   in a flex row (the Products ledger's rows, the 404's list), plus an inline
   modifier for the nav menu, the footer and the press kit list, where the mark
   runs inside a text line. Replaces the 8px world dot AS-07 removed. Nothing
   here fixes a size: the img's width/height attributes carry the 16px, so the
   same class never fights the 48px hero or 32px card marks, which carry no
   class at all. */
.nsi4-product-mark {
	flex: 0 0 auto;
}

.nsi4-product-mark--inline {
	vertical-align: -3px;
	margin-inline-end: var(--nsi4-space-2);
}

/* The mark beside its status chip: the product hero's badge row (48px mark)
   and the product card's (32px mark), so both centre the pair on one line. */
.nsi4-mark-row {
	display: flex;
	align-items: center;
	gap: var(--nsi4-space-3);
}

/* The Products ledger — one row per product, the home cards' replacement on
   /products. Each row carries the product's own shot beside the mark and name,
   the job, the status chip with its year, Proof and Stack as terms of their
   own, and an arrow. The six-column table and its header row are gone: a row
   now names everything it holds, so the header had nothing left to say.

   The whole row is the product link — the name's ::after covers it, the way
   the article index carries its title — and a hover raises the row one surface
   step, with the image's border and the arrow in the product's colour.

   Mobile-first, as the block was before: below 640px a row is one column with
   the shot across its full width; from 640px the shot takes a 176px column
   beside the text; from 1200px the row is the mockup's five tracks.

   The raised surface is a pseudo-element rather than the row's own background.
   It reaches 20px past the content column on each side, which a background on
   the row could only do by padding the row on hover — and padding the row on
   hover moves its content, so the row is in two places at once. As a pseudo it
   reaches without touching the row's box: every child keeps its rect, and the
   hairline between rows stays at the column's width. */
.nsi4-ledger {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* `isolation` belongs to the raised surface below: it makes the row a stacking
   context, so the pseudo can sit behind the content on a negative z-index
   without escaping past the row and behind the page. */
.nsi4-ledger__row {
	position: relative;
	isolation: isolate;
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	align-items: center;
	gap: var(--nsi4-space-3);
	padding-block: var(--nsi4-space-6);
	border-top: 1px solid var(--nsi4-hairline);
}

/* The raised surface, transparent until the row is hovered or its link takes
   keyboard focus. Only colours transition, on the system's duration; the
   motion guard in section 11 takes the transition away under reduced motion,
   so both switch instantly there.

   The overhang is the mockup's 20px, never more than the room the page has:
   `(100vw - 100%) / 2` is that room — the row is the shell's content column and
   the shell is centred — with the gutter as the floor. A centred row at 1200px
   and up takes the full 20; a row running to the edge of a phone takes its
   overhang from the shell's padding instead, which is what stops the surface
   reaching past the viewport and the page growing a sideways scrollbar. */
.nsi4-ledger__row::before {
	content: "";
	position: absolute;
	z-index: -1;
	inset: 0 calc(-1 * min(20px, max(var(--nsi4-gutter), (100vw - 100%) / 2)));
	border-radius: var(--nsi4-radius-lg);
	background-color: transparent;
	transition: background-color var(--nsi4-dur) var(--nsi4-ease);
}

/* The shot. The ratio is the ledger's 16:10 rather than the img's width and
   height attributes, which stay the intrinsic size for a reader the CSS never
   reaches — three of the five files are a hair off 16:10, and the row wants
   one ratio rather than each file's own. */
.nsi4-ledger__image {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 16 / 10;
	object-fit: cover;
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-md);
	transition: border-color var(--nsi4-dur) var(--nsi4-ease);
}

/* The pair is the name over its job, or the chip over the year. It is a box
   only from 1200px, where each of the two is centred beside the shot as one
   thing; a grid cannot centre two siblings as a unit. Below that it is
   `display: contents` — no box, so its children are laid out by the row's own
   grid exactly as if the wrapper were not in the markup. */
.nsi4-ledger__pair {
	display: contents;
}

/* Mark and name on one line, the chip at the line's right end. The 10px gap
   has no step — a literal. */
.nsi4-ledger__product {
	display: flex;
	align-items: center;
	gap: 10px;
	color: var(--nsi4-text);
	text-decoration: none;
}

/* Phoenix underlines every link it hovers, and `a:hover` outranks a class, so
   the row's own hover state — the raised surface, the outlined shot, the
   coloured arrow — arrives with an underline under the name as well. Same
   declaration, one pseudo-class up: the row is the hover, not the word. The
   focus ring is a separate property and stays. */
.nsi4-ledger__product:hover,
.nsi4-ledger__product:focus-visible {
	text-decoration: none;
}

/* The name's link covers the row: the ::after's containing block is the row,
   the nearest positioned box. */
.nsi4-ledger__product::after {
	content: "";
	position: absolute;
	inset: 0;
}

.nsi4-ledger__name {
	font-family: var(--nsi4-font-sans);
	/* 20px is the lede step; the 28px leading has none — a literal, as the
	   article index's 24px excerpt line carries. */
	font-size: var(--nsi4-fs-lede);
	line-height: 28px;
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-ledger__job {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text-2);
}

/* 'Since 2024' under the chip: the eyebrow step's size and leading in the mono
   face, a step down in colour. */
.nsi4-ledger__since {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	color: var(--nsi4-text-3);
}

/* Proof and Stack as terms of their own: the term takes a 56px column, the
   value the rest, 8px between the pairs. The 56px column and the 12px mono
   term have no step — the mockup's own measures, as literals. */
.nsi4-ledger__data {
	display: grid;
	grid-template-columns: 56px minmax(0, 1fr);
	gap: var(--nsi4-space-2) var(--nsi4-space-3);
	margin: 0;
}

.nsi4-ledger__label,
.nsi4-ledger__value {
	margin: 0;
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
}

.nsi4-ledger__label {
	font-family: var(--nsi4-font-mono);
	/* Phoenix bolds every dt; a term here is a quiet label beside its value,
	   not the value itself. */
	font-weight: 400;
	font-size: 12px;
	line-height: 22px;
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-ledger__value {
	color: var(--nsi4-text);
}

/* The stack is the list behind the claim, not the claim itself: it steps down
   from the proof above it. */
.nsi4-ledger__stack {
	color: var(--nsi4-text-2);
}

/* The arrow belongs to the wide row alone: a 176px shot beside a column of
   text leaves it nowhere to sit below 1200px, and a phone has no hover to
   spend it on. */
.nsi4-ledger__arrow {
	display: none;
	font-size: 20px;
	color: var(--nsi4-text-3);
	transition: color var(--nsi4-dur) var(--nsi4-ease);
}

/* The phone: the shot across the row's full width, then the same head, job,
   year and terms under it in the same order. Placed rather than auto-flowed,
   so the chip can share the name's line at the line's right end. */
.nsi4-ledger__image {
	grid-column: 1;
	grid-row: 1;
}

.nsi4-ledger__product,
.nsi4-ledger__status {
	grid-column: 1;
	grid-row: 2;
}

.nsi4-ledger__status {
	justify-self: end;
}

.nsi4-ledger__job {
	grid-column: 1;
	grid-row: 3;
}

.nsi4-ledger__since {
	grid-column: 1;
	grid-row: 4;
}

.nsi4-ledger__data {
	grid-column: 1;
	grid-row: 5;
}

@media (min-width: 640px) {
	/* The shot takes its own column and spans the text beside it; the text
	   keeps the head it has on a phone, with the chip at the line's end.

	   Four rows, declared, because the span below is `1 / -1` and `-1` is the
	   last line of the *explicit* grid: with no explicit track it is line 1, so
	   the span collapses onto the first row and the job, the year and the terms
	   start under the shot. Every one is `auto`, so the text alone sets the
	   row's height and a job that wraps takes the row with it. */
	.nsi4-ledger__row {
		grid-template-columns: 176px minmax(0, 1fr);
		grid-template-rows: repeat(4, auto);
		column-gap: var(--nsi4-space-6);
		row-gap: var(--nsi4-space-3);
	}

	/* The shot hangs from the name line's top rather than the row's centre: in
	   a stacked column the name is what anchors the row, and `align-items:
	   center` would drop the shot below it instead. The 1200px block takes the
	   alignment back, so the wide row's shot centres on its own track. */
	.nsi4-ledger__image {
		width: 176px;
		grid-column: 1;
		grid-row: 1 / -1;
		align-self: start;
	}

	.nsi4-ledger__product,
	.nsi4-ledger__status {
		grid-column: 2;
		grid-row: 1;
	}

	.nsi4-ledger__job {
		grid-column: 2;
		grid-row: 2;
	}

	.nsi4-ledger__since {
		grid-column: 2;
		grid-row: 3;
	}

	.nsi4-ledger__data {
		grid-column: 2;
		grid-row: 4;
	}
}

@media (min-width: 1200px) {
	/* The mockup's five tracks: shot, name over job, chip over year, terms,
	   arrow. 28px has no step — the mockup's own gap, a literal.

	   One row, declared, because everything here is placed `grid-row: 1 / -1`
	   and `-1` is the last line of the *explicit* grid: with no explicit track it
	   is line 1 and every span collapses onto the first. One track also means one
	   centre line — the row's own `align-items: center` puts the pairs, the terms
	   and the arrow on it together; the shot takes its alignment back below. */
	.nsi4-ledger__row {
		grid-template-columns: 176px 340px 180px minmax(0, 1fr) 24px;
		grid-template-rows: auto;
		column-gap: 28px;
		row-gap: 0;
	}

	/* The shot's alignment, given back here, because the 640px block's
	   `align-self: start` is still in force at this width and nothing overrides
	   it. The single track is as tall as the row's tallest cell, so terms longer
	   than the shot would hang the shot from the row's top while every other
	   cell stayed on the centre line. */
	.nsi4-ledger__image {
		align-self: center;
	}

	/* The pair stacks and centres itself inside its column: the name 8px over
	   its job, the chip 10px over the year. Two measures, so each pair carries
	   its own — `align-content` is what centres the pair on the row, and it
	   still centres a pair whose job has wrapped onto three lines. */
	.nsi4-ledger__pair {
		display: grid;
		align-content: center;
		grid-column: 2;
		grid-row: 1 / -1;
		row-gap: var(--nsi4-space-2);
	}

	.nsi4-ledger__pair--status {
		grid-column: 3;
		/* 10px has no step — a literal, as the pair's own gap above. */
		row-gap: 10px;
	}

	/* The pair's own children reflow into its two rows. Below 1200px they are
	   placed on the row's grid instead, and those placements are still in force
	   here — `grid-column: 2` inside a one-column pair would open a second,
	   empty column and push the name into it. */
	.nsi4-ledger__product,
	.nsi4-ledger__job,
	.nsi4-ledger__status,
	.nsi4-ledger__since {
		grid-column: 1;
		grid-row: auto;
	}

	/* The chip takes the head of its column here, not the end it takes on a
	   phone, where it shares the name's line. */
	.nsi4-ledger__status {
		justify-self: start;
	}

	.nsi4-ledger__data {
		grid-column: 4;
		grid-row: 1 / -1;
	}

	.nsi4-ledger__arrow {
		display: block;
		grid-column: 5;
		grid-row: 1 / -1;
	}
}

/* Keyboard focus raises the same surface a pointer hover does, on every device
   — one that reports `hover: none` still has a link to tab to. `:has()` rather
   than `:focus-within`, which would leave the row raised after a mouse click
   on it. */
.nsi4-ledger__row:has(.nsi4-ledger__product:focus-visible)::before {
	background-color: var(--nsi4-raised);
	box-shadow: var(--nsi4-highlight-top);
}

.nsi4-ledger__row:has(.nsi4-ledger__product:focus-visible) .nsi4-ledger__image {
	border-color: var(--nsi4-accent);
}

.nsi4-ledger__row:has(.nsi4-ledger__product:focus-visible) .nsi4-ledger__arrow {
	color: var(--nsi4-accent);
}

/* Pointer hover, on the devices that have a pointer. */
@media (hover: hover) {
	.nsi4-ledger__row:hover::before {
		background-color: var(--nsi4-raised);
		box-shadow: var(--nsi4-highlight-top);
	}

	.nsi4-ledger__row:hover .nsi4-ledger__image {
		border-color: var(--nsi4-accent);
	}

	.nsi4-ledger__row:hover .nsi4-ledger__arrow {
		color: var(--nsi4-accent);
	}
}

/* ==========================================================================
   The progression ledger — /community/retry's Mythic kill record

   A real table from 900px up, where its own layout is the wide one and is
   never written at all; below that each row leaves the table layout entirely
   and becomes a stack. The stacking is the only place a `display` is set on a
   table element, so the wide table needs nothing switched back on.

   Every value cell carries a label span naming its column. Above 900px the
   `<th scope="col">` does the naming and the span is dropped; below it the
   header row is gone and the span is the name. Nothing above 900px sets a
   cell's display, which is what keeps the span from being outranked — the
   Products ledger's mobile labels shipped dead once because `.nsi4-ledger td
   { display: block }` outranked the class that was meant to lay them out.
   ========================================================================== */

/* The mockup's 40px between the head, the ledger and the source line. Three
   classes, because .nsi4-section.nsi4-rule already sets 32px and this has to
   win on specificity rather than on where it lands in the sheet. */
.nsi4-section.nsi4-rule.nsi4-progression {
	gap: 40px;
}

/* The head: eyebrow and heading on the left, the two figures at the right
   end of the content column. The shared head caps at 720px, which would clip
   the figures, so the measure is taken back inside the section. */
.nsi4-progression .nsi4-section__head {
	max-width: none;
}

.nsi4-progression__head {
	grid-template-columns: minmax(0, 1fr);
	row-gap: var(--nsi4-space-4);
}

@media (min-width: 900px) {
	.nsi4-progression__head {
		grid-template-columns: minmax(0, 1fr) auto;
		column-gap: var(--nsi4-gutter);
		/* End line, not centre: the two figures sit on the heading's last
		   line, where a stat block sits on every other page. */
		align-items: end;
	}
}

/* The figures, 32px apart, each a name over its value. One row at every
   width: the two names and their figures are shorter than the content
   column on the narrowest phone measured, so below 900px they read across
   rather than down. */
.nsi4-progression__facts {
	display: grid;
	grid-auto-flow: column;
	gap: var(--nsi4-space-8);
	margin: 0;
}

.nsi4-progression__fact dt {
	font-family: var(--nsi4-font-mono);
	/* 12px has no step on the scale — a literal, as .nsi4-ledger__label above. */
	font-size: 12px;
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	color: var(--nsi4-text-3);
}

.nsi4-progression__fact dd {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	/* 22px/28px is the mockup's own measure; the scale names no 22. */
	font-size: 22px;
	line-height: 28px;
	font-weight: var(--nsi4-fw-data-xl);
	color: var(--nsi4-text);
	font-variant-numeric: tabular-nums;
}

/* The ledger's box: the system's one card shape — surface, hairline, 12px
   radius, the top highlight. It sits on the wrapper and not on the table, so
   a collapsed-border table never has to paint a rounded border of its own.
   The hidden overflow clips the header and last row to the radius. */
.nsi4-progression__ledger {
	background: var(--nsi4-surface);
	border: 1px solid var(--nsi4-hairline);
	border-radius: var(--nsi4-radius-lg);
	box-shadow: var(--nsi4-highlight-top);
	overflow: hidden;
}

.nsi4-progression__table {
	width: 100%;
	border-collapse: collapse;
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-small);
	line-height: var(--nsi4-lh-small);
	color: var(--nsi4-text);
}

.nsi4-progression__table th {
	padding: var(--nsi4-space-3) var(--nsi4-space-6);
	border-bottom: 1px solid var(--nsi4-hairline);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
	text-align: left;
	color: var(--nsi4-text-3);
	vertical-align: bottom;
}

.nsi4-progression__table td {
	padding: var(--nsi4-space-4) var(--nsi4-space-6);
	vertical-align: top;
}

/* The year reads as a figure rather than a word, and its figures tabulate
   against the dates further down the column. */
.nsi4-progression__year {
	font-family: var(--nsi4-font-mono);
	font-weight: var(--nsi4-fw-data-xl);
}

/* The raid names a place and the Mythic column a fraction of it; both step
   down from the boss and the date beside them. */
.nsi4-progression__raid,
.nsi4-progression__mythic {
	color: var(--nsi4-text-2);
}

.nsi4-progression__mythic {
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/* The boss and its chip, on one line while the column holds both and wrapped
   under the name when it does not. The wrap is what keeps the End boss
   column from setting the table's width in the band just above 900px, where
   six columns are tightest. The name is an element of its own and not the
   cell's bare text, so the chip has a box to sit beside rather than to be
   measured inside. */
.nsi4-progression__name {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--nsi4-space-2);
}

/* The two feats that took the guild into the Hall of Fame. Filled in the
   world's accent on the ground colour, as the world button is — so on this
   page the chip is WRS orange. */
.nsi4-progression__chip {
	white-space: nowrap;
	padding: var(--nsi4-space-1) var(--nsi4-space-2);
	border-radius: var(--nsi4-radius-sm);
	background: var(--nsi4-accent);
	color: var(--nsi4-ground);
	font-family: var(--nsi4-font-mono);
	font-size: 12px;
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: var(--nsi4-fw-eyebrow);
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
}

/* The rank column: three Warcraft Logs progress ranks on fixed tracks, wide
   enough for the widest figure each scope can print and no wider — so the
   column is the same width on every row and the values sit under the header's
   three names. Mono at the data weight, because they are figures. */
.nsi4-progression__trio {
	display: grid;
	grid-template-columns: 44px 36px 44px;
	column-gap: var(--nsi4-space-2);
	font-family: var(--nsi4-font-mono);
	font-weight: var(--nsi4-fw-data-xl);
	font-variant-numeric: tabular-nums;
}

/* The header's trio is the header's own type, at the header's weight: three
   column names, not three sets of figures. */
.nsi4-progression__table th .nsi4-progression__trio {
	font-weight: inherit;
}

/* The rank is a link out to that tier's Warcraft Logs rankings, so it takes
   the rank's colour from its quality class below and underlines dotted in the
   quiet rule colour; on hover the underline goes solid in the rank's own
   colour. The rule comes before the five classes on purpose — they are the
   same specificity, and a later `color: inherit` would win over all of them. */
.nsi4-progression__rank-link {
	color: inherit;
	text-decoration: underline dotted;
	text-decoration-thickness: 1px;
	text-underline-offset: 4px;
	text-decoration-color: var(--nsi4-control-border);
}

.nsi4-progression__rank-link:hover {
	text-decoration-style: solid;
	text-decoration-color: currentColor;
}

/* Warcraft Logs' quality colours. Rare and epic are lifted from #0070FF and
   #A335EE to the first value at or above 4.5:1 on the surface (#11151E) at
   the same hue and lightness; the other three are Warcraft Logs' unchanged,
   which measure 13.36, 7.25 and 5.90 there. */
.nsi4-rank--uncommon {
	color: #1EFF00;
}

.nsi4-rank--rare {
	color: #0F79FF;
}

.nsi4-rank--epic {
	color: #AF4FF0;
}

.nsi4-rank--legendary {
	color: #FF8000;
}

.nsi4-rank--astounding {
	color: #E268A8;
}

/* A tier Warcraft Logs has no progress data for reads as a dash, not as a
   rank of nothing. */
.nsi4-progression__none {
	color: var(--nsi4-text-3);
}

/* The rank cells and their header are the table's tightest: the three tracks
   carry the column's width, so their padding only has to clear the text. Only
   from 900px, where the column is a column: stacked below that, the cell is
   the row's full width and every other stacked cell has no padding of its own. */
@media (min-width: 900px) {
	.nsi4-progression__table td.nsi4-progression__ranks,
	.nsi4-progression__table th.nsi4-progression__ranks {
		padding-left: var(--nsi4-space-3);
		padding-right: var(--nsi4-space-3);
	}
}

/* Cutting Edge — an end boss killed on Mythic before the next raid opened.
   The world's accent, as the Hall of Fame chip is, so on this page both are
   WRS orange. Under 900px it rides the date's own line, 8px after it; from
   900px it takes a line of its own under the date. */
.nsi4-progression__ce {
	display: inline-block;
	margin-left: var(--nsi4-space-2);
	vertical-align: 1px;
	white-space: nowrap;
	padding: 1px 6px;
	border: 1px solid var(--nsi4-accent);
	border-radius: var(--nsi4-radius-sm);
	color: var(--nsi4-accent);
	font-family: var(--nsi4-font-mono);
	/* 11px and 16px have no step on the scale — literals, as the label's 12px. */
	font-size: 11px;
	line-height: 16px;
	letter-spacing: var(--nsi4-ls-eyebrow);
	text-transform: uppercase;
}

.nsi4-progression__source {
	text-align: right;
}

.nsi4-progression__link {
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	line-height: var(--nsi4-lh-eyebrow);
	font-weight: 400;
	/* The arrow belongs to the link, so the two never part: nowrap keeps the
	   arrow on the line of the word before it. */
	white-space: nowrap;
	color: var(--nsi4-text-3);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	text-decoration-color: var(--nsi4-control-border);
}

.nsi4-progression__link:hover {
	color: var(--nsi4-text-2);
	text-decoration-color: var(--nsi4-text-3);
}

.nsi4-progression__sources {
	margin: 0;
	font-family: var(--nsi4-font-mono);
	/* 18px leading has no step — a literal, as the papers' table's 24px. */
	font-size: var(--nsi4-fs-eyebrow);
	line-height: 18px;
	color: var(--nsi4-text-3);
}

@media (min-width: 900px) {
	/* The label is the header row's job above 900px, and showing it there as
	   well would read every column twice. One class, nothing else competing
	   for it. */
	.nsi4-progression__label {
		display: none;
	}

	/* The scope name is the header row's job for the same reason: it already
	   sits over the column it names. */
	.nsi4-progression__scope {
		display: none;
	}

	/* A row of its own height gives the tag its own line, 4px under the date,
	   rather than the 8px beside it the stacked row has room for. */
	.nsi4-progression__ce {
		display: block;
		width: max-content;
		margin: var(--nsi4-space-1) 0 0;
	}

	.nsi4-progression__table tbody tr + tr td {
		border-top: 1px solid var(--nsi4-hairline);
	}
}

/* Seven columns are tightest in the band above 1100px, where the content
   column has stopped growing but the table is still a table: every cell
   gives up half its side padding, and the rank cells, whose width their own
   tracks already carry, give up another 4px. That is what keeps the table
   inside the ledger at 900px, where its content alone is wider than the box.
   The ledger's hidden overflow would clip the Source column, not scroll it. */
@media (min-width: 900px) and (max-width: 1099.98px) {
	.nsi4-progression__table th,
	.nsi4-progression__table td {
		padding-left: var(--nsi4-space-3);
		padding-right: var(--nsi4-space-3);
	}

	.nsi4-progression__table td.nsi4-progression__ranks,
	.nsi4-progression__table th.nsi4-progression__ranks {
		padding-left: var(--nsi4-space-2);
		padding-right: var(--nsi4-space-2);
	}
}

/* Below 900px each row is a stack: the year and the raid share the first
   line, the boss and its chip the second, Mythic the third, Killed the
   fourth, the three ranks the fifth, the source link the sixth. Mythic
   and Killed each take the row's full width because a pair on one line would
   size the row's first column by whichever of the two is wider — a Mythic
   ratio reads narrower than a date, and `[PROGRESS]` reads wider than either —
   and the raid label beside it would start at a different x on every row.
   Every one of those values keeps its label, and every label and the value it
   names sit in grid items, which never overlap. The rank line takes the same
   full width but drops its label: each of its three values is named by its own
   World, EU and Realm scope, so a 'Rank' label would only spend the width the
   widest trio needs to hold on one line. The three ranks wrap onto a second
   line on a row too narrow for them rather than overflow the ledger, which
   clips. The year and the raid are two-column grids, label `max-content` and
   value the rest. The source link, the milestone note and the rank line are
   one column each, their own text being the whole cell. The boss, Mythic and
   Killed cells take the row's two columns instead, so those three values in a
   row start at one x — the raid label's. */
@media (max-width: 899.98px) {
	.nsi4-progression__table,
	.nsi4-progression__table tbody {
		display: block;
	}

	.nsi4-progression__table thead {
		display: none;
	}

	/* The header row is gone, so every cell names its own value, and the name
	   reads in the header row's style: one quiet measure for the column names,
	   whatever colour the value beside it carries. */
	.nsi4-progression__label {
		font-family: var(--nsi4-font-mono);
		/* 12px has no step on the scale — a literal, as the header row's. */
		font-size: 12px;
		line-height: var(--nsi4-lh-eyebrow);
		font-weight: var(--nsi4-fw-eyebrow);
		letter-spacing: var(--nsi4-ls-eyebrow);
		text-transform: uppercase;
		color: var(--nsi4-text-3);
	}

	/* The row is the box from here, so it carries the row's padding and the
	   hairline under the one above; the cells inside keep none of either. */
	.nsi4-progression__table tr {
		display: grid;
		grid-template-columns: max-content minmax(0, 1fr);
		column-gap: var(--nsi4-space-3);
		row-gap: var(--nsi4-space-1);
		padding: var(--nsi4-space-4) var(--nsi4-space-6);
	}

	.nsi4-progression__table tbody tr + tr {
		border-top: 1px solid var(--nsi4-hairline);
	}

	.nsi4-progression__table td {
		display: grid;
		grid-template-columns: max-content minmax(0, 1fr);
		column-gap: var(--nsi4-space-3);
		align-items: baseline;
		padding: 0;
	}

	/* The three cells that take a line each ride the row's own two columns
	   instead of sizing a label column of their own: `max-content` for the
	   year on the first line, the rest of the width for every value under it,
	   with the row's own gap between them. So the End boss, the Mythic figure
	   and the kill date start where the raid label starts, and a label wider
	   than the year grows that one column — carrying the raid label and all
	   three values right together, rather than letting a value sit wherever its
	   own label happens to end. */
	.nsi4-progression__table td.nsi4-progression__boss,
	.nsi4-progression__table td.nsi4-progression__mythic,
	.nsi4-progression__table td.nsi4-progression__killed {
		grid-template-columns: subgrid;
	}

	/* Three classes, because the label column is the source link's and the rank
	   line's to keep empty — each one's own text is the whole cell. */
	.nsi4-progression__table td.nsi4-progression__source,
	.nsi4-progression__table td.nsi4-progression__note,
	.nsi4-progression__table td.nsi4-progression__ranks {
		grid-template-columns: minmax(0, 1fr);
	}

	.nsi4-progression__year {
		grid-column: 1;
		grid-row: 1;
	}

	.nsi4-progression__raid {
		grid-column: 2;
		grid-row: 1;
	}

	.nsi4-progression__boss {
		grid-column: 1 / -1;
		grid-row: 2;
	}

	.nsi4-progression__mythic {
		grid-column: 1 / -1;
		grid-row: 3;
	}

	.nsi4-progression__killed {
		grid-column: 1 / -1;
		grid-row: 4;
	}

	.nsi4-progression__ranks {
		grid-column: 1 / -1;
		grid-row: 5;
	}

	.nsi4-progression__source {
		grid-column: 1 / -1;
		grid-row: 6;
	}

	.nsi4-progression__table td.nsi4-progression__ranks .nsi4-progression__label {
		display: none;
	}

	/* In the stack each rank names its own scope, so the three are free to size
	   to what they hold instead of the fixed tracks the wide table needs, and to
	   wrap: a row narrower than the trio drops the Realm rank onto a second
	   line rather than pushing it past the ledger's edge, where the hidden
	   overflow would clip it with no scroll to recover it. */
	.nsi4-progression__trio {
		display: flex;
		flex-wrap: wrap;
		column-gap: var(--nsi4-space-4);
		row-gap: var(--nsi4-space-1);
	}

	/* A rank is a pair, so it lays out as one: its scope name and the value it
	   names share a baseline, 8px apart on the gap below. A flex container
	   renders no whitespace-only text, so the space between them in the markup
	   drops out rather than adding to the gap — as inline text it left each
	   value 17px from its own name and 16px from the next. */
	.nsi4-progression__rank {
		display: flex;
		align-items: baseline;
		column-gap: var(--nsi4-space-2);
	}

	/* The scope name reads in the header row's style — the same quiet measure
	   as the cell labels beside it. The 8px between it and the rank it names is
	   the flex gap on `.nsi4-progression__rank` above, not a margin here: a
	   margin plus the markup's own space would read as 17px again. */
	.nsi4-progression__scope {
		font-family: var(--nsi4-font-mono);
		/* 12px has no step on the scale — a literal, as the header row's. */
		font-size: 12px;
		line-height: var(--nsi4-lh-eyebrow);
		font-weight: var(--nsi4-fw-eyebrow);
		letter-spacing: var(--nsi4-ls-eyebrow);
		text-transform: uppercase;
		color: var(--nsi4-text-3);
	}

	/* The founding row fills one cell where a kill fills five, so it has no
	   boss, Mythic, Killed or Rank cell and its source link follows the note
	   instead of leaving a kill row's last two lines empty. */
	.nsi4-progression__note {
		grid-column: 1 / -1;
		grid-row: 2;
	}

	.nsi4-progression__row--founded .nsi4-progression__source {
		grid-row: 3;
	}
}

/* The 404 — a 4/8 split from 900px: the copy on the left, the five products
   on the right; the two stack below it. The min-height puts the footer's top
   edge at or below the viewport bottom: the chrome above main is the 64px bar
   plus its 1px hairline, measured at 65px. Both columns keep their content at
   the top — align-content: start — so the extra height the min-height creates
   is space at the bottom, not inflated gaps in the copy. */
.nsi4-notfound {
	display: grid;
	gap: var(--nsi4-space-8);
	/* 100svh is the smallest viewport height, so the footer clears the bottom
	   even where a phone browser's chrome is showing. */
	min-height: calc(100svh - 65px);
}

.nsi4-notfound__copy,
.nsi4-notfound__list {
	display: grid;
	gap: var(--nsi4-space-6);
	align-content: start;
}

.nsi4-notfound__items {
	margin: 0;
	padding: 0;
	list-style: none;
}

.nsi4-notfound__items li {
	position: relative;
	padding-block: var(--nsi4-space-6);
	border-top: 1px solid var(--nsi4-hairline);
	transition: background-color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-notfound__items li:hover {
	background: var(--nsi4-raised);
}

.nsi4-notfound__items a {
	display: grid;
	gap: var(--nsi4-space-2);
	color: var(--nsi4-text);
	text-decoration: none;
}

/* The row's link covers the row: the ::after's containing block is the list
   item, the nearest positioned box. */
.nsi4-notfound__items a::after {
	content: "";
	position: absolute;
	inset: 0;
}

/* The name line and the claim read as the ledger's product cell does: 17px 600
   in Sans over the claim at the small size's 24px leading (a literal there too). */
.nsi4-notfound__name {
	display: flex;
	align-items: center;
	gap: var(--nsi4-space-2);
	font-family: var(--nsi4-font-sans);
	font-size: var(--nsi4-fs-body);
	line-height: var(--nsi4-lh-body);
	font-weight: 600;
	color: var(--nsi4-text);
}

.nsi4-notfound__claim {
	font-size: var(--nsi4-fs-small);
	line-height: 24px;
	color: var(--nsi4-text-2);
}

@media (min-width: 900px) {
	.nsi4-notfound {
		grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
		gap: var(--nsi4-gutter);
	}
}

/* ==========================================================================
   8. Legacy class names
   Kept so every existing page keeps its hooks. Where a legacy class carried
   the brand gradient, it now renders in the signed-off language: the gradient
   appears only on the logo and the primary button.
   ========================================================================== */
.nsi4-product-title {
	color: var(--nsi4-accent);
}

.nsi4-border {
	border-color: var(--nsi4-hairline);
}

.nsi4-action-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--nsi4-touch-min);
	background: linear-gradient(90deg, var(--nsi4-cta-start), var(--nsi4-cta-end));
	color: var(--nsi4-on-cta);
	border: none;
	border-radius: var(--nsi4-radius-md);
	transition: filter var(--nsi4-dur) var(--nsi4-ease), transform var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-action-button:hover {
	filter: brightness(1.12);
	background: linear-gradient(90deg, var(--nsi4-cta-start), var(--nsi4-cta-end));
	color: var(--nsi4-on-cta);
}

.nsi4-action-button:active {
	transform: translateY(1px);
}

.nsi4-secondary-action-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: var(--nsi4-touch-min);
	background: transparent;
	color: var(--nsi4-text);
	border: 1px solid var(--nsi4-control-border);
	border-radius: var(--nsi4-radius-md);
	transition: border-color var(--nsi4-dur) var(--nsi4-ease), background-color var(--nsi4-dur) var(--nsi4-ease);
}

.nsi4-secondary-action-button:hover {
	background: var(--nsi4-raised);
	color: var(--nsi4-text);
	border-color: var(--nsi4-text);
}

.nsi4-color-A,
.nsi4-color-B,
.nsi4-color-C {
	color: var(--nsi4-accent);
	border-color: var(--nsi4-accent);
}

.nsi4-bg-gradient {
	background: var(--nsi4-raised);
}

.nsi4-text-gradient {
	color: var(--nsi4-accent);
}

/* ==========================================================================
   9. Icons
   One system: Lucide, drawn from wwwroot/assets/img/icons/lucide-sprite.svg.
   Size follows font-size, so the existing sizing utilities keep working.
   ========================================================================== */
/* :where() keeps this at zero specificity, so the sizing and colour
   utilities already on the swapped markup keep winning. font-size is 1em —
   the parent's size, as the FontAwesome spans this replaced were — so each
   icon keeps the size its surrounding type gives it. */
:where(.nsi4-icon) {
	display: inline-block;
	width: 1em;
	height: 1em;
	font-size: 1em;
	flex: none;
	vertical-align: -0.125em;
	fill: none;
	stroke-width: 1.5;
	color: inherit;
}

/* In a control an icon is its own size, not the control's type size. */
:where(.nsi4-btn) :where(.nsi4-icon),
:where(.nsi4-action-button) :where(.nsi4-icon),
:where(.nsi4-secondary-action-button) :where(.nsi4-icon) {
	font-size: 20px;
}

:where(.nsi4-icon--content) {
	font-size: 24px;
}

/* ==========================================================================
   10. Phoenix override layer
   Each block names the theme.min.css selector it beats. Phoenix components
   land on token values; nothing here edits theme.min.css itself.
   ========================================================================== */
:root,
[data-bs-theme=dark] {
	/* theme.min.css :root --phoenix-font-sans-serif — the stack the whole
	   theme reads, including body via --phoenix-body-font-family */
	--phoenix-font-sans-serif: var(--nsi4-font-sans);
	/* theme.min.css :root --phoenix-font-monospace */
	--phoenix-font-monospace: var(--nsi4-font-mono);
	/* theme.min.css :root --phoenix-body-font-family, which is what body reads */
	--phoenix-body-font-family: var(--nsi4-font-sans);
	--phoenix-body-font-size: var(--nsi4-fs-body);
	--phoenix-body-font-weight: var(--nsi4-fw-body);
	--phoenix-body-line-height: 1.588;
	/* theme.min.css [data-bs-theme=dark] --phoenix-body-color / --phoenix-body-bg */
	--phoenix-body-color: var(--nsi4-text);
	--phoenix-body-color-rgb: 237, 239, 243;
	--phoenix-body-bg: var(--nsi4-ground);
	--phoenix-body-bg-rgb: 10, 13, 20;
	--phoenix-emphasis-color: var(--nsi4-text);
	--phoenix-emphasis-color-rgb: 237, 239, 243;
	--phoenix-heading-color: var(--nsi4-text);
	--phoenix-secondary-color: var(--nsi4-text-2);
	--phoenix-secondary-color-rgb: 160, 166, 179;
	--phoenix-tertiary-color: var(--nsi4-text-3);
	--phoenix-tertiary-color-rgb: 131, 138, 152;
	/* theme.min.css [data-bs-theme=dark] --phoenix-border-color */
	--phoenix-border-color: var(--nsi4-hairline);
	--phoenix-border-color-rgb: 255, 255, 255;
	--phoenix-border-color-translucent: var(--nsi4-hairline);
	/* theme.min.css :root --phoenix-border-radius* ; the pill token is
	   redefined because the system has no pills */
	--phoenix-border-radius: var(--nsi4-radius-md);
	--phoenix-border-radius-sm: var(--nsi4-radius-sm);
	--phoenix-border-radius-lg: var(--nsi4-radius-lg);
	--phoenix-border-radius-xl: var(--nsi4-radius-lg);
	--phoenix-border-radius-xxl: var(--nsi4-radius-lg);
	--phoenix-border-radius-2xl: var(--nsi4-radius-lg);
	--phoenix-border-radius-pill: var(--nsi4-radius-md);
	/* theme.min.css :root --phoenix-focus-ring* */
	--phoenix-focus-ring-width: 2px;
	--phoenix-focus-ring-opacity: 1;
	--phoenix-focus-ring-color: var(--nsi4-focus);
	/* theme.min.css :root --phoenix-box-shadow* ; the system's only shadow */
	--phoenix-box-shadow: var(--nsi4-highlight-top);
	--phoenix-box-shadow-sm: var(--nsi4-highlight-top);
	--phoenix-box-shadow-lg: var(--nsi4-highlight-top);
	--phoenix-box-shadow-inset: none;
}

/* These follow the page's world, so they sit on the wrapper rather than on
   :root, where the accent would freeze at its corporate value.
   theme.min.css [data-bs-theme=dark] --phoenix-link-color / -hover-color */
.nsi4 {
	--phoenix-link-color: var(--nsi4-accent);
	--phoenix-link-hover-color: var(--nsi4-text);
	--phoenix-link-decoration: underline;
	--phoenix-link-hover-decoration: underline;
}

/* theme.min.css .bg-body-emphasis — a surface, not a second ground */
.nsi4 .bg-body-emphasis {
	background-color: var(--nsi4-surface) !important;
}

/* theme.min.css .card */
.nsi4 .card {
	--phoenix-card-bg: var(--nsi4-surface);
	--phoenix-card-border-color: var(--nsi4-hairline);
	--phoenix-card-box-shadow: var(--nsi4-highlight-top);
	color: var(--nsi4-text);
}

/* theme.min.css .dropdown-menu / .modal-content / .offcanvas — the raised step */
.nsi4 .dropdown-menu,
.nsi4 .modal-content,
.nsi4 .offcanvas {
	--phoenix-dropdown-bg: var(--nsi4-raised);
	--phoenix-dropdown-border-color: var(--nsi4-hairline);
	--phoenix-modal-bg: var(--nsi4-raised);
	--phoenix-modal-border-color: var(--nsi4-hairline);
	--phoenix-offcanvas-bg: var(--nsi4-raised);
	border-color: var(--nsi4-hairline);
}

/* theme.min.css .badge — the system's badge shape, on the Phoenix variants */
.nsi4 .badge {
	border-radius: var(--nsi4-radius-sm);
	font-family: var(--nsi4-font-mono);
	font-size: var(--nsi4-fs-eyebrow);
	letter-spacing: 0.06em;
}

/* theme.min.css .btn, .form-control, .form-select */
.nsi4 .btn,
.nsi4 .form-control,
.nsi4 .form-select {
	border-radius: var(--nsi4-radius-md);
}

.nsi4 .form-control,
.nsi4 .form-select {
	background-color: var(--nsi4-surface);
	border-color: var(--nsi4-control-border);
	color: var(--nsi4-text);
}

/* theme.min.css .navbar — the nav is chrome, not content */
.nsi4 .navbar .nav-link {
	color: var(--nsi4-text-2);
}

.nsi4 .navbar .nav-link:hover,
.nsi4 .navbar .nav-link[aria-current] {
	color: var(--nsi4-text);
}

/* ==========================================================================
   11. Motion guard
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
	.nsi4 *,
	.nsi4 *::before,
	.nsi4 *::after {
		transition: none !important;
		animation: none !important;
	}
}

/* ==========================================================================
   12. View transitions

   Chromium crossfades one page into the next on a same-origin navigation
   instead of cutting. Opted in only where the reader has not asked for
   reduced motion: with no @view-transition at-rule in force the navigation
   is a plain cut, which is how every other browser behaves anyway.

   The nav carries its own name, so it is captured as its own snapshot and
   holds still while the rest of the page crossfades. The fade runs on the
   system's duration and easing, so a navigation moves at the same speed as
   a hover, and adds no keyframes beyond the default crossfade.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
	@view-transition {
		navigation: auto;
	}

	.nsi4-nav {
		view-transition-name: nsi4-nav;
	}

	::view-transition-old(root),
	::view-transition-new(root) {
		animation-duration: var(--nsi4-dur);
		animation-timing-function: var(--nsi4-ease);
	}
}
