/* ==========================================================================
   Code-Inspect design system
   1.  Tokens
   2.  Reset & base
   3.  Layout primitives
   4.  Typography & prose
   5.  Buttons & links
   6.  Header, navigation, drawer
   7.  Hero & decoration
   8.  Sections & components
   9.  Forms & contact
   10. Footer
   11. Blog & utilities
   12. Page sections — glance band, panels, tabs, quote
   13. Figures, bands and the long-page kit
   14. Dark mode — structural adjustments
   15. Motion & reduced motion
   ========================================================================== */

/* 1. Tokens
   ========================================================================== */

:root {
	color-scheme: light;

	/* Brand — overridden inline by the Customizer, shared by both modes. */
	--ci-brand: #1d3fa0;
	--ci-accent: #22c7e8;
	--ci-ink: #0a1733;

	/* Derived brand ramp. */
	--ci-brand-700: color-mix(in srgb, var(--ci-brand) 82%, #000);
	--ci-brand-600: var(--ci-brand);
	/* Never flips. Use for brand text on a surface that is white in BOTH
	   modes (the white .ci-btn--light on a dark band), and for a brand FILL
	   on a surface that is dark in both modes. */
	--ci-brand-dark: color-mix(in srgb, var(--ci-brand) 82%, #000);
	/* Hover fill under white text. Darkens in light, LIGHTENS in dark, so the
	   hover always advances towards the viewer. Distinct from --ci-brand-700,
	   which is also used as a foreground and must lighten in dark. */
	--ci-btn-fill-hover: var(--ci-brand-700);
	--ci-brand-500: color-mix(in srgb, var(--ci-brand) 84%, #fff);
	--ci-brand-100: color-mix(in srgb, var(--ci-brand) 12%, #fff);
	--ci-brand-050: color-mix(in srgb, var(--ci-brand) 6%, #fff);

	/* Brand as a FOREGROUND (links, glyphs). Split from the fill above because
	   the raw brand is too dark to read on a dark surface. */
	--ci-brand-fg: var(--ci-brand-600);
	/* Text/glyphs sitting ON a brand fill. */
	--ci-on-brand: #ffffff;

	--ci-ink-900: color-mix(in srgb, var(--ci-ink) 92%, #000);
	--ci-ink-800: var(--ci-ink);
	--ci-ink-700: color-mix(in srgb, var(--ci-ink) 88%, var(--ci-brand));
	/* Hairline between two adjacent dark bands, and under the ink band. */
	--ci-band-edge: rgba(10, 23, 51, .14);

	/* Neutrals. */
	--ci-text: #10192c;
	--ci-text-2: #44536c;
	--ci-text-3: #596880;
	--ci-line: #e3e8f0;
	--ci-line-2: #eef2f7;
	--ci-paper: #ffffff;
	--ci-paper-2: #f6f8fc;
	--ci-paper-3: #eef3f9;
	/* Section bands. A separate role from --ci-paper-2, which also fills cards,
	   fields and drawer rows -- which is why the band could never be darkened
	   before without darkening every component with it. At #edf1f8 the band sits
	   1.13:1 against the page, roughly twice the step it had, and --ci-text-3
	   still clears AA on it at 5.07:1. The hairline does the rest of the work. */
	--ci-band: #edf1f8;
	--ci-band-line: #dfe5ef;
	/* Raised surfaces: cards, popovers, the drawer, the form card. */
	--ci-raised: #ffffff;
	/* Border on a surface that already has one in light mode. */
	--ci-elev-bd: var(--ci-line);
	/* Border on a surface that has none in light mode. */
	--ci-elev-bd-soft: transparent;
	--ci-hover-surface: var(--ci-raised);
	/* Border a clickable surface takes on hover, and the hairline on an
	   icon chip. One value each, so "this is clickable" looks the same on
	   every box on the site. */
	--ci-surface-bd-hover: color-mix(in srgb, var(--ci-brand) 34%, var(--ci-line));
	--ci-chip-bd: color-mix(in srgb, var(--ci-brand) 14%, transparent);
	/* The one horizontal nudge: an icon moving in the direction of travel.
	   There were four distances for this single gesture -- 3px, 4px (three
	   identical copies) and 5px. */
	--ci-nudge: 4px;
	--ci-quote-rule: var(--ci-brand-600);
	--ci-scrim: rgba(6, 14, 32, .55);
	--ci-drawer-bg: var(--ci-raised);
	--ci-drawer-bd: transparent;
	/* Persistent selected state (form chips). Must stay legible as a FILL. */
	--ci-chip-on: var(--ci-brand-600);
	--ci-chip-on-fg: var(--ci-on-brand);
	/* Switch knob + a 0/1 flag so the toggle's resting position is CSS-driven
	   and correct on the first paint, instead of being set by JS afterwards. */
	--ci-switch-thumb: #ffffff;

	--ci-hero-bg:
		radial-gradient(78% 52% at 78% -16%, color-mix(in srgb, var(--ci-accent) 26%, transparent), transparent 62%),
		radial-gradient(64% 46% at 8% 4%, color-mix(in srgb, var(--ci-brand) 62%, transparent), transparent 66%),
		linear-gradient(165deg, var(--ci-ink-700) 0%, var(--ci-ink-900) 100%);

	/* The closing CTA is a permanently dark band, so its fill must not follow
	   --ci-brand-700 (which lightens in dark for its foreground role). */
	--ci-closing-bg:
		radial-gradient(700px 340px at 85% 10%, color-mix(in srgb, var(--ci-accent) 32%, transparent), transparent 65%),
		linear-gradient(140deg, var(--ci-brand-dark), var(--ci-ink-900));

	/* Translucent chrome. */
	--ci-header-bg: rgba(255, 255, 255, .86);
	--ci-header-sh: 0 6px 24px -14px rgba(10, 23, 51, .3);
	--ci-drawer-sh: -20px 0 60px -20px rgba(10, 23, 51, .45);

	/* Form fields. */
	--ci-field-bg: var(--ci-paper-2);
	--ci-field-bg-focus: #ffffff;
	--ci-control-accent: var(--ci-brand-600);

	/* Semantic state. */
	--ci-danger: #c2353b;
	--ci-danger-bg: #fff6f6;
	--ci-success: #127049;
	--ci-success-fg: #0d5c3c;
	--ci-success-bg: color-mix(in srgb, #127049 8%, #fff);
	--ci-success-line: color-mix(in srgb, #127049 25%, #fff);

	/* On a permanently dark band (hero, ink section, footer, closing CTA).
	   These keep their values in both modes — the bands stay dark. */
	--ci-on-dark: #f3f6fb;
	--ci-on-dark-2: #b9c6dd;
	--ci-on-dark-3: #8e9dba;
	--ci-on-dark-line: rgba(255, 255, 255, .13);
	/* Fills for a surface ON a permanently dark band. Two rungs, so the five
	   hand-written white alphas section 13 started with have somewhere to go. */
	--ci-on-dark-fill: rgba(255, 255, 255, .06);
	--ci-on-dark-fill-2: rgba(255, 255, 255, .1);
	--ci-decor-grid: rgba(255, 255, 255, .045);
	--ci-decor-orb-o: .5;

	/* Media treatment. */
	--ci-media-filter: none;
	--ci-map-filter: grayscale(.25) contrast(1.05);

	/* ---- Dark palette values. Defined once here, switched on below. ------- */
	--ci-d-paper: #0c1016;
	--ci-d-paper-2: #12171f;
	/* Dark band lifts only 1.04:1 above the page, deliberately: --ci-raised
	   (#161c25) must still read as lifted ABOVE the band, and at #10151d it
	   clears it by 1.07:1. Any darker a band and cards would sink into it. */
	--ci-d-band: #10151d;
	--ci-d-paper-3: #1a212b;
	--ci-d-raised: #161c25;
	--ci-d-scrim: rgba(0, 0, 0, .66);
	--ci-d-drawer-bg: #1a212b;
	--ci-d-drawer-bd: rgba(255, 255, 255, .34);
	--ci-d-switch-thumb: #aab5c6;
	--ci-d-closing-bg:
		radial-gradient(700px 340px at 85% 10%, color-mix(in srgb, var(--ci-accent) 24%, transparent), transparent 65%),
		linear-gradient(140deg, #1b2d5a, #0f1931);
	--ci-d-elev-bd: rgba(255, 255, 255, .09);
	--ci-d-surface-bd-hover: color-mix(in srgb, var(--ci-brand) 46%, rgba(255, 255, 255, .22));
	--ci-d-chip-bd: rgba(255, 255, 255, .12);
	--ci-d-elev-bd-soft: rgba(255, 255, 255, .08);
	--ci-d-hover-surface: #1d242f;
	--ci-d-hero-bg:
		radial-gradient(78% 52% at 78% -16%, color-mix(in srgb, var(--ci-accent) 20%, transparent), transparent 62%),
		radial-gradient(64% 46% at 8% 4%, color-mix(in srgb, var(--ci-brand) 46%, transparent), transparent 66%),
		linear-gradient(165deg, #1b2d5a 0%, #0f1931 100%);

	--ci-d-text: #e8ecf3;
	--ci-d-text-2: #aab5c6;
	--ci-d-text-3: #8290a4;
	--ci-d-line: rgba(255, 255, 255, .11);
	--ci-d-line-2: rgba(255, 255, 255, .06);

	/* Bands lift ABOVE the page in dark mode so the hierarchy survives. */
	--ci-d-ink-700: #1b2d5a;
	--ci-d-ink-800: #15233f;
	--ci-d-ink-900: #111b33;
	--ci-d-band-edge: rgba(255, 255, 255, .09);

	--ci-d-brand-700: color-mix(in srgb, var(--ci-brand) 70%, #fff);
	--ci-d-brand-fg: color-mix(in srgb, var(--ci-brand) 52%, #fff);
	--ci-d-brand-100: color-mix(in srgb, var(--ci-brand) 32%, #202836);
	--ci-d-brand-050: color-mix(in srgb, var(--ci-brand) 20%, #141a24);

	--ci-d-header-bg: rgba(12, 16, 22, .82);
	--ci-d-header-sh: 0 6px 24px -14px rgba(0, 0, 0, .8);
	--ci-d-drawer-sh: -20px 0 60px -20px rgba(0, 0, 0, .75);

	--ci-d-field-bg: #131922;
	--ci-d-field-bg-focus: #1b2432;

	--ci-d-danger: #ff8f95;
	--ci-d-danger-bg: #2b1418;
	--ci-d-success: #4ade9b;
	--ci-d-success-fg: #7ff0bb;
	--ci-d-success-bg: #0f2a1f;
	--ci-d-success-line: rgba(74, 222, 155, .3);

	/* The wash is a brand tint, not a neutral, so it needs its own strength in
	   dark: 7% of the brand over a near-black page is invisible. */
	--ci-d-fig-wash: color-mix(in srgb, var(--ci-brand) 34%, transparent);
	--ci-d-fig-accent: var(--ci-brand-fg);
	--ci-d-fig-on-accent: #0c1016;
	--ci-d-tint: #141b25;
	--ci-d-tint-line: rgba(255, 255, 255, .1);
	--ci-d-focus: var(--ci-accent);

	--ci-d-sh-1: 0 1px 2px rgba(0, 0, 0, .5), 0 2px 8px rgba(0, 0, 0, .35);
	--ci-d-sh-2: 0 2px 4px rgba(0, 0, 0, .4), 0 12px 28px -8px rgba(0, 0, 0, .6);
	--ci-d-sh-3: 0 24px 60px -18px rgba(0, 0, 0, .8);
	--ci-d-sh-brand: 0 10px 28px -14px color-mix(in srgb, var(--ci-brand) 75%, transparent);

	/* Type. */
	--ci-font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
	--ci-font-display: "Inter Tight", "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

	--ci-fs-eyebrow: .75rem;
	--ci-fs-sm: .875rem;
	--ci-fs-base: 1rem;
	--ci-fs-lead: clamp(1.0625rem, .98rem + .36vw, 1.25rem);
	--ci-fs-h3: clamp(1.125rem, 1.06rem + .28vw, 1.3125rem);
	--ci-fs-h2: clamp(1.75rem, 1.42rem + 1.45vw, 2.75rem);
	--ci-fs-h1: clamp(2.125rem, 1.6rem + 2.3vw, 3.5rem);
	--ci-fs-hero: clamp(2.5rem, 1.6rem + 3.9vw, 4.5rem);

	/* Space. */
	--ci-gap: 1.5rem;
	--ci-gap-lg: clamp(1.5rem, 1rem + 3vw, 3.5rem);
	--ci-section-y: clamp(3.5rem, 2.2rem + 5.2vw, 7rem);
	--ci-wrap: 1240px;
	--ci-wrap-pad: clamp(1.125rem, .55rem + 2.2vw, 2.5rem);

	/* Inset. Four rungs, and every padded box takes one of them. Before this
	   there were forty-one distinct padding declarations and six container
	   clamps that each grew at a different rate, which is why nothing lined up
	   on a wide screen: two cards side by side were inset 1.6rem and 1.5rem,
	   and the four feature panels a visitor meets in sequence all widened at
	   different speeds. The two clamps share a growth rate on purpose. */
	--ci-pad-sm: 1rem 1.15rem;
	--ci-pad: 1.5rem;
	--ci-pad-lg: clamp(1.75rem, 1.2rem + 2.2vw, 2.75rem);
	--ci-pad-xl: clamp(2.25rem, 1.6rem + 3vw, 4rem);

	/* Shape. Four rungs and nothing else: every corner in the theme is one of
	   these, a pill (999px) or a circle. Before this there were sixteen
	   distinct radii, which is what made the system read as assembled. */
	--ci-r-xs: 6px;
	--ci-r-sm: 10px;
	--ci-r: 16px;
	--ci-r-lg: 24px;

	/* Icon chips. Three sizes, one radius rule: sm and md take --ci-r-sm,
	   lg takes --ci-r. Before this there were thirteen distinct sizes. */
	--ci-chip-sm: 36px;
	--ci-chip-md: 44px;
	--ci-chip-lg: 56px;

	/* Elevation. */
	--ci-sh-1: 0 1px 2px rgba(10, 23, 51, .05), 0 2px 8px rgba(10, 23, 51, .04);
	--ci-sh-2: 0 2px 4px rgba(10, 23, 51, .05), 0 12px 28px -8px rgba(10, 23, 51, .12);
	--ci-sh-3: 0 24px 60px -18px rgba(10, 23, 51, .28);
	--ci-sh-brand: 0 14px 32px -12px color-mix(in srgb, var(--ci-brand) 55%, transparent);

	/* Motion.
	   --ci-ease is the interface curve: a state flipping under the pointer.
	   --ci-ease-out is the arrival curve, fast then settling, for anything
	   entering the page. --ci-enter is an alias so entrance rules read as what
	   they do; it is deliberately the SAME curve rather than a second one,
	   because two arrival curves is how a site stops having one hand. */
	--ci-ease: cubic-bezier(.22, .68, .36, 1);
	--ci-ease-out: cubic-bezier(.16, 1, .3, 1);
	--ci-enter: var(--ci-ease-out);
	--ci-dur: .34s;

	/* The motion scale. Four steps, chosen by how far the thing travels and
	   how much of the page it is, so a chip and a whole band never share a
	   duration. Anything outside this set is a one-off that has to justify
	   itself in a comment. */
	/* There is no entrance step below --ci-m-md on purpose. A state changing
	   under the pointer is --ci-dur above, and adding a second token for the
	   same role is how a system ends up with two answers to one question. */
	--ci-m-md: .42s;   /* one element arriving */
	--ci-m-lg: .62s;   /* a band arriving */
	/* A diagram drawing itself. This one is a budget, not a taste: the whole
	   figure finishes at --fig-d + this, and the reveal fires when the band
	   crosses 88% of the viewport — so the diagram is at the very bottom of
	   the screen when it starts. Much past .9s and a reader scrolling at a
	   normal pace arrives at a half-built picture. */
	--ci-m-xl: .9s;

	/* The two entrance distances, and the one stagger step. Grids multiply the
	   step by a capped index (see ci_reveal()), so the tail of a twelve-card
	   grid cannot run past ~.4s however long the grid gets. */
	--ci-rise: 20px;
	--ci-glide: 30px;
	--ci-stagger: 55ms;
	/* The INTERACTION step, for groups that arrive inside a control the reader
	   has just opened -- a menu's rows, a drawer's three parts. Separate from
	   --ci-stagger because the budgets differ: a page band can afford 55ms
	   between items because the reader is watching the page assemble, while a
	   menu has to be finished before the pointer reaches the row it came for.
	   Every interaction delay in the file is an integer multiple of this. */
	--ci-stagger-tight: 30ms;

	/* Diagram figures. One stroke weight and one label scale across all twelve,
	   so a diagram drawn for DevOps and one drawn for compliance read as the
	   same hand. Everything resolves through the neutrals and the brand, so a
	   Customizer brand change re-tints every figure with no second asset. */
	--ci-fig-ink: var(--ci-text-2);
	--ci-fig-line: var(--ci-line);
	--ci-fig-wash: color-mix(in srgb, var(--ci-brand) 7%, transparent);
	--ci-fig-accent: var(--ci-brand-600);
	--ci-fig-on-accent: var(--ci-on-brand);
	--ci-fig-label: var(--ci-text-3);
	--ci-fig-label-strong: var(--ci-text);
	--ci-fig-stroke: 1.5px;

	/* A fifth surface step. --ci-band separates two bands; --ci-tint is for a
	   band that has to read as a deliberate interruption rather than as the
	   next paragraph -- the problem statement, the mid-page CTA. */
	--ci-tint: color-mix(in srgb, var(--ci-brand) 8%, var(--ci-paper));
	--ci-tint-line: color-mix(in srgb, var(--ci-brand) 18%, var(--ci-paper));

	/* Focus. One ring, one offset. */
	--ci-focus: var(--ci-brand-600);
	--ci-focus-w: 3px;
	--ci-focus-off: 3px;

	/* Gap, to end the hand-written values. */
	--ci-gap-xs: .5rem;
	--ci-gap-sm: 1rem;
	--ci-gap-xl: clamp(2rem, 1.4rem + 2.6vw, 3.25rem);

	--ci-fs-h4: 1.0625rem;
	--ci-dur-fast: .18s;

	--ci-header-h: 76px;
	/* How far the "at a glance" band rides up over the hero. */
	--ci-glance-lift: 56px;
}

/* Fallback for browsers without color-mix(). */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
	:root {
		--ci-brand-700: #16317d;
		--ci-brand-500: #3a5cc0;
		--ci-brand-100: #e4e9f7;
		--ci-brand-050: #f2f5fc;
		--ci-surface-bd-hover: #9fb0da;
		--ci-chip-bd: #d4dcf1;
		--ci-d-surface-bd-hover: rgba(255, 255, 255, .26);
		--ci-d-chip-bd: rgba(255, 255, 255, .12);
		--ci-ink-900: #060e20;
		--ci-ink-700: #0d1d3e;
		--ci-sh-brand: 0 14px 32px -12px rgba(29, 63, 160, .55);
		--ci-brand-dark: #16317d;

		--ci-d-brand-700: #5a74bd;
		--ci-d-brand-fg: #8e9fd0;
		--ci-d-brand-100: #2b3b63;
		--ci-d-brand-050: #212a45;
		--ci-d-sh-brand: 0 10px 28px -14px rgba(29, 63, 160, .75);
		--ci-fig-wash: #eef1f9;
		--ci-d-fig-wash: #27324e;
		--ci-d-fig-on-accent: #0c1016;
		--ci-tint: #f5f7fc;
		--ci-tint-line: #dde4f2;
		--ci-success-bg: #f1f8f4;
		--ci-success-line: #c3dfd1;

		/* These hold whole gradients. Without a fallback the declaration is
		   dropped at parse time and the band renders with NO background —
		   white headings on white. */
		--ci-hero-bg: linear-gradient(165deg, #0d1d3e 0%, #060e20 100%);
		--ci-d-hero-bg: linear-gradient(165deg, #1b2d5a 0%, #0f1931 100%);
		--ci-closing-bg: linear-gradient(140deg, #16317d, #060e20);
		--ci-d-closing-bg: linear-gradient(140deg, #1b2d5a, #0f1931);
	}
}

/* ---- Dark mode switch ------------------------------------------------------
   Two blocks, deliberately. `ci_inline_brand_tokens()` prints its Customizer
   values on a bare `:root` AFTER this stylesheet, so a plain `:root` override
   inside a media query would have equal specificity and lose on source order —
   silently dragging --ci-ink (and the whole band ramp) back to light values.
   `:root:not([data-theme="light"])` and `:root[data-theme="dark"]` both outrank
   it. The values themselves live once, as --ci-d-* above; these blocks only
   alias, so a palette change is a one-place edit.
   -------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		color-scheme: dark;

		--ci-paper: var(--ci-d-paper);
		--ci-paper-2: var(--ci-d-paper-2);
		--ci-paper-3: var(--ci-d-paper-3);
		--ci-band: var(--ci-d-band);
		--ci-band-line: var(--ci-d-line);
		--ci-raised: var(--ci-d-raised);
		--ci-elev-bd: var(--ci-d-elev-bd);
		--ci-surface-bd-hover: var(--ci-d-surface-bd-hover);
		--ci-chip-bd: var(--ci-d-chip-bd);
		--ci-fig-wash: var(--ci-d-fig-wash);
		--ci-fig-accent: var(--ci-d-fig-accent);
		--ci-fig-on-accent: var(--ci-d-fig-on-accent);
		--ci-tint: var(--ci-d-tint);
		--ci-tint-line: var(--ci-d-tint-line);
		--ci-focus: var(--ci-d-focus);
		--ci-elev-bd-soft: var(--ci-d-elev-bd-soft);
		--ci-hover-surface: var(--ci-d-hover-surface);
		--ci-quote-rule: var(--ci-d-brand-fg);
		--ci-hero-bg: var(--ci-d-hero-bg);
		--ci-closing-bg: var(--ci-d-closing-bg);
		--ci-btn-fill-hover: var(--ci-brand-500);
		--ci-scrim: var(--ci-d-scrim);
		--ci-drawer-bg: var(--ci-d-drawer-bg);
		--ci-drawer-bd: var(--ci-d-drawer-bd);
		--ci-chip-on: var(--ci-d-brand-fg);
		--ci-chip-on-fg: var(--ci-d-paper);
		--ci-switch-thumb: var(--ci-d-switch-thumb);

		--ci-text: var(--ci-d-text);
		--ci-text-2: var(--ci-d-text-2);
		--ci-text-3: var(--ci-d-text-3);
		--ci-line: var(--ci-d-line);
		--ci-line-2: var(--ci-d-line-2);

		--ci-ink-700: var(--ci-d-ink-700);
		--ci-ink-800: var(--ci-d-ink-800);
		--ci-ink-900: var(--ci-d-ink-900);
		--ci-band-edge: var(--ci-d-band-edge);

		--ci-brand-700: var(--ci-d-brand-700);
		--ci-brand-fg: var(--ci-d-brand-fg);
		--ci-brand-100: var(--ci-d-brand-100);
		--ci-brand-050: var(--ci-d-brand-050);

		--ci-header-bg: var(--ci-d-header-bg);
		--ci-header-sh: var(--ci-d-header-sh);
		--ci-drawer-sh: var(--ci-d-drawer-sh);

		--ci-field-bg: var(--ci-d-field-bg);
		--ci-field-bg-focus: var(--ci-d-field-bg-focus);
		--ci-control-accent: var(--ci-d-brand-fg);

		--ci-danger: var(--ci-d-danger);
		--ci-danger-bg: var(--ci-d-danger-bg);
		--ci-success: var(--ci-d-success);
		--ci-success-fg: var(--ci-d-success-fg);
		--ci-success-bg: var(--ci-d-success-bg);
		--ci-success-line: var(--ci-d-success-line);

		--ci-sh-1: var(--ci-d-sh-1);
		--ci-sh-2: var(--ci-d-sh-2);
		--ci-sh-3: var(--ci-d-sh-3);
		--ci-sh-brand: var(--ci-d-sh-brand);

		--ci-decor-grid: rgba(255, 255, 255, .07);
		--ci-decor-orb-o: .34;
		--ci-media-filter: brightness(.9) contrast(1.02);
		--ci-map-filter: grayscale(.4) contrast(.92) brightness(.78) invert(.9) hue-rotate(180deg);
	}
}

:root[data-theme="dark"] {
	color-scheme: dark;

	--ci-paper: var(--ci-d-paper);
	--ci-paper-2: var(--ci-d-paper-2);
	--ci-paper-3: var(--ci-d-paper-3);
	--ci-band: var(--ci-d-band);
	--ci-band-line: var(--ci-d-line);
	--ci-raised: var(--ci-d-raised);
	--ci-elev-bd: var(--ci-d-elev-bd);
	--ci-surface-bd-hover: var(--ci-d-surface-bd-hover);
	--ci-chip-bd: var(--ci-d-chip-bd);
	--ci-fig-wash: var(--ci-d-fig-wash);
	--ci-fig-accent: var(--ci-d-fig-accent);
	--ci-fig-on-accent: var(--ci-d-fig-on-accent);
	--ci-tint: var(--ci-d-tint);
	--ci-tint-line: var(--ci-d-tint-line);
	--ci-focus: var(--ci-d-focus);
	--ci-elev-bd-soft: var(--ci-d-elev-bd-soft);
	--ci-hover-surface: var(--ci-d-hover-surface);
	--ci-quote-rule: var(--ci-d-brand-fg);
	--ci-hero-bg: var(--ci-d-hero-bg);
	--ci-closing-bg: var(--ci-d-closing-bg);
	--ci-btn-fill-hover: var(--ci-brand-500);
	--ci-scrim: var(--ci-d-scrim);
	--ci-drawer-bg: var(--ci-d-drawer-bg);
	--ci-drawer-bd: var(--ci-d-drawer-bd);
	--ci-chip-on: var(--ci-d-brand-fg);
	--ci-chip-on-fg: var(--ci-d-paper);
	--ci-switch-thumb: var(--ci-d-switch-thumb);

	--ci-text: var(--ci-d-text);
	--ci-text-2: var(--ci-d-text-2);
	--ci-text-3: var(--ci-d-text-3);
	--ci-line: var(--ci-d-line);
	--ci-line-2: var(--ci-d-line-2);

	--ci-ink-700: var(--ci-d-ink-700);
	--ci-ink-800: var(--ci-d-ink-800);
	--ci-ink-900: var(--ci-d-ink-900);
	--ci-band-edge: var(--ci-d-band-edge);

	--ci-brand-700: var(--ci-d-brand-700);
	--ci-brand-fg: var(--ci-d-brand-fg);
	--ci-brand-100: var(--ci-d-brand-100);
	--ci-brand-050: var(--ci-d-brand-050);

	--ci-header-bg: var(--ci-d-header-bg);
	--ci-header-sh: var(--ci-d-header-sh);
	--ci-drawer-sh: var(--ci-d-drawer-sh);

	--ci-field-bg: var(--ci-d-field-bg);
	--ci-field-bg-focus: var(--ci-d-field-bg-focus);
	--ci-control-accent: var(--ci-d-brand-fg);

	--ci-danger: var(--ci-d-danger);
	--ci-danger-bg: var(--ci-d-danger-bg);
	--ci-success: var(--ci-d-success);
	--ci-success-fg: var(--ci-d-success-fg);
	--ci-success-bg: var(--ci-d-success-bg);
	--ci-success-line: var(--ci-d-success-line);

	--ci-sh-1: var(--ci-d-sh-1);
	--ci-sh-2: var(--ci-d-sh-2);
	--ci-sh-3: var(--ci-d-sh-3);
	--ci-sh-brand: var(--ci-d-sh-brand);

	--ci-decor-grid: rgba(255, 255, 255, .07);
	--ci-decor-orb-o: .34;
	--ci-media-filter: brightness(.9) contrast(1.02);
	--ci-map-filter: grayscale(.4) contrast(.92) brightness(.78) invert(.9) hue-rotate(180deg);
}

/* 2. Reset & base
   ========================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body.ci {
	margin: 0;
	font-family: var(--ci-font);
	font-size: var(--ci-fs-base);
	line-height: 1.65;
	color: var(--ci-text);
	background: var(--ci-paper);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	font-feature-settings: "cv05" 1, "ss01" 1;
	overflow-x: hidden;
}

img, svg, video, iframe { max-width: 100%; display: block; }
img { height: auto; }

a { color: var(--ci-brand-fg); text-decoration-thickness: 1px; text-underline-offset: 3px; }

button { font: inherit; color: inherit; }

:focus-visible {
	outline: 2px solid var(--ci-accent);
	outline-offset: 3px;
	border-radius: var(--ci-r-xs);
}

.ci-skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 999;
	padding: 12px 20px;
	background: var(--ci-ink-800);
	color: #fff;
	border-radius: 0 0 var(--ci-r-sm) 0;
	text-decoration: none;
}
.ci-skip:focus { left: 0; }

.screen-reader-text {
	border: 0;
	clip: rect(1px, 1px, 1px, 1px);
	clip-path: inset(50%);
	height: 1px;
	width: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute !important;
	word-wrap: normal !important;
}
.screen-reader-text:focus {
	clip: auto;
	clip-path: none;
	height: auto;
	width: auto;
	position: static !important;
}

.ci-icon { flex: none; }

/* 3. Layout primitives
   ========================================================================== */

.ci-wrap {
	width: 100%;
	max-width: var(--ci-wrap);
	margin-inline: auto;
	padding-inline: var(--ci-wrap-pad);
}

.ci-main { display: block; }

.ci-section {
	position: relative;
	padding-block: var(--ci-section-y);
}
.ci-section--tight { padding-block: clamp(2.5rem, 1.8rem + 3vw, 4.5rem); }
.ci-section--alt {
	background: var(--ci-band);
	border-block: 1px solid var(--ci-band-line);
}

/* Band rhythm.
   Two problems, one rule set. First, the alternation did no visual work at all:
   --ci-paper-2 was #f6f8fc against a #ffffff page -- 1.05:1, with no border --
   so every --alt band was invisible and the whole default/alt sequence was a
   decision with no rendered consequence. It now has its own --ci-band role at
   1.13:1 plus a hairline, and the hairline carries most of the signal.
   Second, there was not one adjacent-sibling rule in nearly three thousand
   lines, so two consecutive sections of the same tone stacked two full
   --ci-section-y runs with nothing between them -- up to 224px of dead ground
   that reads as one section twice as long as it should be. Same-tone
   neighbours now take a content-width hairline and a reduced inset, so the
   pair reads as one band with an internal division rather than two identical
   slabs. The hairline stops at the content width on purpose: an edge that
   spanned the viewport would claim to be a band boundary, which it is not. */
.ci-section--alt:not(.ci-section--seam) + .ci-section--alt:not(.ci-section--seam),
.ci-section--ink:not(.ci-section--seam) + .ci-section--ink:not(.ci-section--seam),
.ci-section--tint:not(.ci-section--seam) + .ci-section--tint:not(.ci-section--seam),
.ci-section:not(.ci-section--alt):not(.ci-section--ink):not(.ci-section--tint):not(.ci-closing):not(.ci-section--seam) + .ci-section:not(.ci-section--alt):not(.ci-section--ink):not(.ci-section--tint):not(.ci-closing):not(.ci-section--seam) {
	padding-block-start: 0;
	border-top-width: 0;
}
/* .ci-closing is excluded: it zeroes its own padding-block and holds a dark
   card with its own margin, so the same-tone rule painted a full-width rule
   in empty background above it and pushed it 60px down the page. */
/* --seam is excluded on BOTH sides: a seam band must not take a divider, and
   a band that follows one must not either -- the gap the divider needs does
   not exist. Writing the exemption as its own rule does not work: this
   selector is (0,8,0) and would outrank it. */
.ci-section--alt:not(.ci-section--seam) + .ci-section--alt:not(.ci-section--seam) > .ci-wrap,
.ci-section--ink:not(.ci-section--seam) + .ci-section--ink:not(.ci-section--seam) > .ci-wrap,
.ci-section--tint:not(.ci-section--seam) + .ci-section--tint:not(.ci-section--seam) > .ci-wrap,
.ci-section:not(.ci-section--alt):not(.ci-section--ink):not(.ci-section--tint):not(.ci-closing):not(.ci-section--seam) + .ci-section:not(.ci-section--alt):not(.ci-section--ink):not(.ci-section--tint):not(.ci-closing):not(.ci-section--seam) > .ci-wrap {
	padding-block-start: calc(var(--ci-section-y) * .55);
	border-top: 1px solid var(--ci-line);
}
.ci-section--tint:not(.ci-section--seam) + .ci-section--tint:not(.ci-section--seam) > .ci-wrap { border-top-color: var(--ci-tint-line); }
.ci-section--alt:not(.ci-section--seam) + .ci-section--alt:not(.ci-section--seam) > .ci-wrap { border-top-color: var(--ci-band-line); }
.ci-section--ink:not(.ci-section--seam) + .ci-section--ink:not(.ci-section--seam) > .ci-wrap { border-top-color: var(--ci-on-dark-line); }
.ci-section--ink {
	background: linear-gradient(170deg, var(--ci-ink-700), var(--ci-ink-900));
	background:
		radial-gradient(900px 420px at 12% -10%, color-mix(in srgb, var(--ci-brand) 40%, transparent), transparent 70%),
		linear-gradient(170deg, var(--ci-ink-700), var(--ci-ink-900));
	color: var(--ci-on-dark);
}
.ci-section--ink h2,
.ci-section--ink h3 { color: #fff; }
.ci-section--ink p { color: var(--ci-on-dark-2); }

.ci-grid {
	display: grid;
	gap: var(--ci-gap);
}
.ci-grid--2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.ci-grid--3 { grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); }
.ci-grid--4 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* ===========================================================================
   PRIMITIVES

   One box, one row, one chip, one pill, one rule-block. Before these, roughly
   two dozen components each declared their own padding, background, border,
   radius, shadow, transition and hover, which had two consequences. A card on
   the home page, the services overview and an industry page were three
   unrelated objects that happened to look similar. And a visitor was taught
   five different lift distances -- 2px, 3px, 4px, 5px, 8px -- for the single
   meaning "this is clickable", one of them on a list item that was not
   clickable at all.

   Two rules hold the system together:

   1. Density is the only thing a component chooses. It sets --sf-pad, --sf-bg,
      --sf-bd or --sf-r, or it takes a named modifier. It does not restate the
      box.
   2. Interactivity is decided by the ELEMENT, never the class. The hover rules
      below are keyed on a.ci-surface and button.ci-surface, so a component
      rendered as a <li> cannot accidentally advertise a click, and one
      rendered as an <a> cannot forget to.
   =========================================================================== */

.ci-surface {
	--sf-bg: var(--ci-raised);
	--sf-bd: var(--ci-elev-bd);
	--sf-pad: var(--ci-pad);
	--sf-r: var(--ci-r);
	/* Width, not just colour. A component that had no border could otherwise
	   only set --sf-bd: transparent, which still occupies 2px of box -- every
	   grouped capability on all ten service pages grew by that much. */
	--sf-bw: 1px;

	position: relative;
	padding: var(--sf-pad);
	background: var(--sf-bg);
	border: var(--sf-bw) solid var(--sf-bd);
	border-radius: var(--sf-r);
	color: inherit;
	text-decoration: none;
}

/* Layout is opt-in, not imposed. More than half of what adopts this
   primitive is a grid or a flex row -- the mega panel is two columns, the
   value band is 88px/1fr/auto, the availability note is an icon beside text --
   so a base that forced `flex-direction: column` would be a primitive every
   second consumer has to fight. --stack is the common case, named. */
.ci-surface--stack { display: flex; flex-direction: column; }
.ci-surface--row   { display: flex; align-items: center; }

/* Density. Four rungs off --ci-pad-*, and nothing in between. */
/* Density only. It used to set --sf-r too, so the five components that took
   it for the smaller inset each lost a corner rung they never asked for --
   including the callouts that carry the client's protected availability
   statements. Shape is its own axis: set --sf-r where a smaller corner is
   actually wanted. */
.ci-surface--tight   { --sf-pad: var(--ci-pad-sm); }
.ci-surface--wide    { --sf-pad: var(--ci-pad-lg); }
.ci-surface--feature { --sf-pad: var(--ci-pad-xl); --sf-r: var(--ci-r-lg); }
/* Edge-to-edge media or a nested header: the box keeps its radius and clips. */
.ci-surface--flush   { --sf-pad: 0; overflow: hidden; }

/* Tone. */
.ci-surface--sunken { --sf-bg: var(--ci-paper-2); --sf-bd: var(--ci-line-2); }
/* On a permanently dark band. The fill is a lift off the band rather than a
   fixed colour, so one modifier works on the hero, the ink section, the
   closing band and the footer without four variants. */
.ci-surface--ink {
	--sf-bg: rgba(255, 255, 255, .05);
	--sf-bd: var(--ci-on-dark-line);
	color: var(--ci-on-dark);
}
.ci-surface--ink p { color: var(--ci-on-dark-2); }

/* A sunken box sitting ON the alt band would be a 1.07:1 step, so it flips to
   raised and reads as lifted instead. This is why --ci-band and --ci-paper-2
   are separate roles. */
.ci-section--alt .ci-surface--sunken { --sf-bg: var(--ci-raised); --sf-bd: var(--ci-line-2); }

/* The two hover rules. Element-keyed, as above. */
a.ci-surface,
button.ci-surface {
	/* background-color belongs here even though the fill is set through
	   --sf-bg: a custom property change propagates to the property that uses
	   it, and that property transitions normally. Without it the hover fill
	   snaps -- and in dark mode the fill is the hover feedback that reads,
	   because a drop shadow on a dark surface is close to invisible.
	   This list REPLACES each component's own, since a.ci-surface (0,1,1)
	   outranks a bare component class (0,1,0), which is why the components
	   below no longer declare one. */
	transition:
		transform var(--ci-dur) var(--ci-ease),
		box-shadow var(--ci-dur) var(--ci-ease),
		border-color var(--ci-dur) var(--ci-ease),
		background-color var(--ci-dur) var(--ci-ease);
}
a.ci-surface:hover,
button.ci-surface:hover {
	transform: translateY(-4px);
	box-shadow: var(--ci-sh-2);
	border-color: var(--ci-surface-bd-hover);
}
/* A boxed link inside a tight vertical stack: it is a row that happens to
   have an edge, and the rule for rows is that they do not move -- one lifting
   out of an 8px gap drops a shadow on its neighbours. Still keeps the border
   and fill response; it gives up only the movement. */
a.ci-surface--still:hover,
button.ci-surface--still:hover { transform: none; box-shadow: none; }

/* Press. Before this, .ci-btn was the only thing in the theme that answered a
   press at all, so every card lifted on hover and then ignored the click. */
a.ci-surface:active,
button.ci-surface:active { transform: translateY(0); box-shadow: var(--ci-sh-1); }
a.ci-row:active { background: var(--ci-brand-100); }

/* A row in a list: the horizontal counterpart. No lift -- a row that rises off
   a stack of rows breaks the stack. The tint is the whole affordance. */
.ci-row {
	display: grid;
	align-items: center;
	gap: .9rem;
	padding: 1.1rem .25rem;
	border-bottom: 1px solid var(--ci-line);
	color: inherit;
	text-decoration: none;
}
/* NOT applied by default. .ci-svc-list draws a border-top and closes itself
   with the last row's bottom border, and .ci-themes is a two-column grid
   where :last-child is one cell in one column -- dropping that hairline makes
   the two columns visibly uneven. Opt in per list. */
.ci-row--open-end:last-child { border-bottom: 0; }
.ci-row--ink { border-bottom-color: var(--ci-on-dark-line); color: var(--ci-on-dark); }

a.ci-row { transition: background-color var(--ci-dur) var(--ci-ease); }
a.ci-row:hover { background: var(--ci-brand-050); }
a.ci-row--ink:hover { background: rgba(255, 255, 255, .06); }
/* No button.ci-row rule: nothing in the theme renders a row as a button. The
   drawer's disclosure control is .ci-drawer__sub-toggle and carries its own
   hover beside the links it sits among. */

/* Icon chip. Sizes come from --ci-chip-*; the radius rule is fixed to them. */
.ci-chip-icon {
	flex: none;
	display: grid;
	place-items: center;
	width: var(--chip, var(--ci-chip-md));
	height: var(--chip, var(--ci-chip-md));
	border-radius: var(--ci-r-sm);
	background: var(--ci-brand-100);
	border: 1px solid var(--ci-chip-bd);
	color: var(--ci-brand-fg);
	transition:
		background-color var(--ci-dur) var(--ci-ease),
		border-color var(--ci-dur) var(--ci-ease),
		color var(--ci-dur) var(--ci-ease);
}
.ci-chip-icon--sm { --chip: var(--ci-chip-sm); }
.ci-chip-icon--lg { --chip: var(--ci-chip-lg); border-radius: var(--ci-r); }
/* No hairline: none of the chips this replaced had one, and the media-card
   badge sits over a photograph where an outline costs it contrast. The lift
   in the fill is the whole treatment. */
.ci-chip-icon--ink {
	background: rgba(255, 255, 255, .09);
	border-color: transparent;
	color: var(--ci-on-dark);
}
/* The accent-on-ink chip: used where a chip sits on a dark band and has to
   read as a marker rather than as a surface. */
.ci-chip-icon--accent { color: var(--ci-accent); }
/* Filled: the chip IS the mark, not a tint behind one. The only chip that
   inverts, and the reason it needs naming is that --circle alone would have
   left it a pale tint with brand text, which reads as disabled. */
.ci-chip-icon--solid {
	background: var(--ci-brand-600);
	border-color: var(--ci-brand-600);
	color: var(--ci-on-brand);
}
/* Quiet: no fill, for a chip on an already-tinted surface where a brand wash
   would be a box inside a box. */
.ci-chip-icon--quiet {
	background: var(--ci-raised);
	border-color: var(--ci-elev-bd-soft);
}

/* The one icon reaction, so the chip answers a hover on its container rather
   than each component inventing its own. No scale: a chip that grows shifts
   the text next to it. */
a.ci-surface:hover .ci-chip-icon,
button.ci-surface:hover .ci-chip-icon,
a.ci-row:hover .ci-chip-icon {
	background: var(--ci-brand-600);
	border-color: var(--ci-brand-600);
	color: var(--ci-on-brand);
}

/* Four rungs the components actually need, added here rather than invented
   locally -- a one-off value per component is exactly how sixteen radii
   happened before v1.5.0. */
/* A chip that is a circle: .ci-flow__num is chip-sized but round. */
.ci-chip-icon--circle { border-radius: 50%; }
/* The drawer's disclosure <button> sits in a list of <a> rows and is styled
   identically, so it has to answer a hover identically. The primitive only
   defined a.ci-row:hover. */

/* Pill: a non-box label or control. */
.ci-pill {
	display: inline-flex;
	align-items: center;
	gap: .45rem;
	padding: .55rem 1.1rem;
	border: 1px solid var(--ci-line);
	border-radius: 999px;
	background: var(--ci-raised);
	font-size: var(--ci-fs-sm);
	font-weight: 500;
	color: var(--ci-text-2);
}
.ci-pill--ink {
	background: rgba(255, 255, 255, .06);
	border-color: var(--ci-on-dark-line);
	color: var(--ci-on-dark-2);
}

/* A smaller, brand-tinted pill: the accountability tag on the parties list
   is a third the size of a label pill and must not be mistaken for one. */
.ci-pill--sm {
	padding: .2rem .55rem;
	font-size: .6875rem;
	font-weight: 700;
	letter-spacing: .08em;
	text-transform: uppercase;
}
.ci-pill--brand {
	background: var(--ci-brand-100);
	border-color: var(--ci-chip-bd);
	color: var(--ci-brand-fg);
}
/* The only pill that is ever a link. .ci-pill ships no hover, so without this
   the post tags would have lost their feedback when the false-affordance
   hover came off the <li> around them. */
a.ci-pill { text-decoration: none; transition: border-color var(--ci-dur) var(--ci-ease), background-color var(--ci-dur) var(--ci-ease), color var(--ci-dur) var(--ci-ease); }

/* Rule-block: a titled group with no box around it. The alternative to a
   nested card, which is what made grouped content read as boxes-in-boxes.
   Three weights, because three distinct jobs: brand marks a capability,
   --line divides equal-weight steps, --quiet is a hairline. */
.ci-rule-block {
	padding-top: 1.5rem;
	border-top: 2px solid var(--ci-brand-600);
}
.ci-rule-block--line { border-top-color: var(--ci-line); }
.ci-rule-block--quiet { border-top: 1px solid var(--ci-line); }
.ci-rule-block--ink { border-top-color: var(--ci-on-dark-line); }
/* The same idea turned ninety degrees: a left rule instead of a top one, for
   an item inside an already-grouped list where a top rule would read as a
   divider between siblings. */
.ci-rule-block--left {
	padding-top: 0;
	padding-left: .85rem;
	border-top: 0;
	border-left: 2px solid var(--ci-brand-600);
}


.ci-center { text-align: center; }
.ci-mt { margin-top: 2rem; }

/* 4. Typography & prose
   ========================================================================== */

h1, h2, h3, h4, h5, h6 {
	font-family: var(--ci-font-display);
	font-weight: 600;
	line-height: 1.14;
	letter-spacing: -.021em;
	color: var(--ci-text);
	margin: 0 0 .5em;
	text-wrap: balance;
}

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

.ci-eyebrow {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	margin: 0 0 .85rem;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 600;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--ci-brand-fg);
}
.ci-eyebrow--light { color: var(--ci-accent); }
/* In-card label. Same role, quieter voice: the tracked uppercase eyebrow is
   for opening a section, and repeating it inside every card flattened the
   page's hierarchy. */
.ci-eyebrow--card {
	font-size: .8125rem;
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--ci-text-3);
	margin-bottom: .5rem;
}
.ci-eyebrow--card.ci-eyebrow--light { color: var(--ci-on-dark-3); }

.ci-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: var(--ci-accent);
	box-shadow: 0 0 0 4px color-mix(in srgb, var(--ci-accent) 25%, transparent);
}

.ci-sec-head { max-width: 62ch; margin-bottom: clamp(2rem, 1.4rem + 2vw, 3.25rem); }
.ci-sec-head--center { margin-inline: auto; text-align: center; }
.ci-sec-head--spaced { margin-top: clamp(3rem, 2rem + 3vw, 5rem); }
.ci-sec-head__title { font-size: var(--ci-fs-h2); margin-bottom: .4em; }
.ci-sec-head__lead {
	font-size: var(--ci-fs-lead);
	color: var(--ci-text-2);
	margin: 0;
}
.ci-sec-head--light .ci-sec-head__title { color: #fff; }
.ci-sec-head--light .ci-sec-head__lead { color: var(--ci-on-dark-2); }

.ci-prose { max-width: 74ch; }
.ci-prose--article { font-size: 1.0625rem; }
.ci-prose > * + * { margin-top: 1.1em; }
.ci-prose h2 { font-size: var(--ci-fs-h2); margin-top: 1.8em; }
.ci-prose h3 { font-size: var(--ci-fs-h3); margin-top: 1.6em; }
.ci-prose ul,
.ci-prose ol { padding-left: 1.3em; }
.ci-prose li + li { margin-top: .4em; }
.ci-prose img { border-radius: var(--ci-r); }
.ci-prose blockquote {
	margin: 2em 0;
	padding: 1.2em 1.6em;
	border-left: 3px solid var(--ci-brand-600);
	background: var(--ci-brand-050);
	border-radius: 0 var(--ci-r) var(--ci-r) 0;
	font-size: 1.08em;
}
.ci-prose code {
	background: var(--ci-paper-3);
	padding: .15em .4em;
	border-radius: var(--ci-r-xs);
	font-size: .9em;
}
.ci-prose table { width: 100%; border-collapse: collapse; }
.ci-prose th,
.ci-prose td { padding: .7em .9em; border-bottom: 1px solid var(--ci-line); text-align: left; }

.ci-note {
	--sf-bg: var(--ci-paper-2);
	display: flex;
	gap: .65rem;
	align-items: flex-start;
	margin: 1.5rem 0 0;
	font-size: var(--ci-fs-sm);
	line-height: 1.55;
	color: var(--ci-text-2);
}
.ci-note .ci-icon { margin-top: .22em; color: var(--ci-brand-fg); }
.ci-note--standalone { max-width: 72ch; margin-top: 2.5rem; }
/* The availability statements the client requires printed verbatim render
   through .ci-note, and on the datacenter and connectivity pages that note
   sits inside the value band's gradient. Measured there, --ci-paper-2 is
   1.04:1 against its own backdrop -- a callout whose fill does nothing, with
   a 1px hairline doing the entire job. Raising the fill and firming the edge
   on tinted ground keeps the frame around the protected text legible. Same
   reasoning as .ci-section--alt .ci-surface--sunken. */
.ci-value-band .ci-note,
.ci-section--alt .ci-note,
.ci-panel--bordered .ci-note {
	--sf-bg: var(--ci-raised);
	--sf-bd: var(--ci-line);
}
.ci-note--light {
	color: var(--ci-on-dark-3);
	background: rgba(255, 255, 255, .04);
	border-color: var(--ci-on-dark-line);
}
.ci-note--light .ci-icon { color: var(--ci-accent); }

/* 5. Buttons & links
   ========================================================================== */

.ci-btn {
	--btn-bg: var(--ci-brand-600);
	--btn-fg: var(--ci-on-brand);
	--btn-bd: transparent;
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	padding: .85rem 1.4rem;
	border: 1px solid var(--btn-bd);
	border-radius: 999px;
	background: var(--btn-bg);
	color: var(--btn-fg);
	font-weight: 600;
	font-size: .9375rem;
	letter-spacing: -.005em;
	text-decoration: none;
	cursor: pointer;
	white-space: nowrap;
	transition: transform var(--ci-dur) var(--ci-ease), box-shadow var(--ci-dur) var(--ci-ease), background-color var(--ci-dur) var(--ci-ease), color var(--ci-dur) var(--ci-ease), border-color var(--ci-dur) var(--ci-ease);
}
.ci-btn:hover { transform: translateY(-2px); }
.ci-btn:active { transform: translateY(0); }
.ci-btn__icon { transition: transform var(--ci-dur) var(--ci-ease-out); }
.ci-btn:hover .ci-btn__icon { transform: translateX(var(--ci-nudge)); }

.ci-btn--primary { box-shadow: var(--ci-sh-brand); }
.ci-btn--primary:hover { --btn-bg: var(--ci-btn-fill-hover); }

.ci-btn--secondary {
	--btn-bg: transparent;
	--btn-fg: var(--ci-text);
	--btn-bd: var(--ci-line);
}
.ci-btn--secondary:hover {
	--btn-bd: var(--ci-brand-600);
	--btn-fg: var(--ci-brand-fg);
	--btn-bg: var(--ci-brand-050);
}

.ci-btn--light {
	--btn-bg: #fff;
	--btn-fg: var(--ci-ink);
	box-shadow: 0 14px 32px -16px rgba(0, 0, 0, .6);
}
.ci-btn--light:hover { --btn-bg: #fff; --btn-fg: var(--ci-brand-dark); }

.ci-btn--ghost-light {
	--btn-bg: transparent;
	--btn-fg: #fff;
	--btn-bd: rgba(255, 255, 255, .3);
}
.ci-btn--ghost-light:hover {
	--btn-bg: rgba(255, 255, 255, .1);
	--btn-bd: rgba(255, 255, 255, .55);
}

.ci-btn--text {
	--btn-bg: transparent;
	--btn-fg: var(--ci-brand-fg);
	padding: .35rem 0;
	border-radius: 0;
}
.ci-btn--text:hover { transform: none; --btn-fg: var(--ci-brand-700); }
.ci-btn--text span { box-shadow: inset 0 -1px 0 currentColor; }

.ci-btn--sm { padding: .62rem 1.1rem; font-size: .875rem; }
.ci-btn--block { width: 100%; justify-content: center; }

.ci-card__more {
	display: inline-flex;
	align-items: center;
	gap: .4rem;
	margin-top: auto;
	padding-top: 1.1rem;
	font-size: .875rem;
	font-weight: 600;
	color: var(--ci-brand-fg);
}
.ci-card__more .ci-icon { transition: transform var(--ci-dur) var(--ci-ease-out); }

/* 6. Header, navigation, drawer
   ========================================================================== */

.ci-topbar {
	background: var(--ci-ink-900);
	color: var(--ci-on-dark-2);
	font-size: .8125rem;
}
.ci-topbar__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1.5rem;
	min-height: 40px;
	padding-block: .4rem;
}
.ci-topbar__text { margin: 0; opacity: .9; }
.ci-topbar__links { display: flex; gap: 1.25rem; flex: none; }
.ci-topbar__links a {
	display: inline-flex;
	align-items: center;
	gap: .4rem;
	color: inherit;
	text-decoration: none;
	transition: color var(--ci-dur) var(--ci-ease);
}
.ci-topbar__links a:hover { color: var(--ci-accent); }
@media (max-width: 900px) {
	.ci-topbar__text { display: none; }
	.ci-topbar__inner { justify-content: center; }
}
@media (max-width: 520px) {
	/* Visually hidden, NOT display: none. The only other content in these
	   links is an aria-hidden icon, so display: none left them with no
	   accessible name at all below 520px — a serious WCAG 2.4.4 / 4.1.2
	   failure that no audit run at desktop width can see. The label is still
	   gone visually; it is only gone from sight. */
	.ci-topbar__links a span {
		position: absolute;
		width: 1px;
		height: 1px;
		margin: -1px;
		padding: 0;
		overflow: hidden;
		clip: rect(0 0 0 0);
		clip-path: inset(50%);
		white-space: nowrap;
		border: 0;
	}
}

.ci-header {
	position: sticky;
	top: 0;
	z-index: 100;
	background: var(--ci-header-bg);
	backdrop-filter: saturate(180%) blur(14px);
	-webkit-backdrop-filter: saturate(180%) blur(14px);
	border-bottom: 1px solid transparent;
	transition: border-color var(--ci-dur) var(--ci-ease), box-shadow var(--ci-dur) var(--ci-ease), background-color var(--ci-dur) var(--ci-ease);
}
.ci-header.is-stuck {
	border-bottom-color: var(--ci-line);
	box-shadow: var(--ci-header-sh);
}
.ci-header__inner {
	display: flex;
	align-items: center;
	gap: 1.5rem;
	min-height: var(--ci-header-h);
}

/* The header needs more room than the text columns do. */
.ci-header > .ci-wrap { max-width: 1400px; }

.ci-brand { flex: none; display: flex; align-items: center; }
.ci-brand .custom-logo-link { display: block; }
.ci-brand img.custom-logo { max-height: 42px; width: auto; }
.ci-brand__text {
	display: inline-flex;
	align-items: center;
	gap: .6rem;
	font-family: var(--ci-font-display);
	font-weight: 700;
	font-size: 1.1875rem;
	letter-spacing: -.02em;
	color: var(--ci-text);
	text-decoration: none;
}
.ci-brand__text--light { color: #fff; }
.ci-brand__mark {
	width: 24px;
	height: 24px;
	border-radius: var(--ci-r-xs);
	background: linear-gradient(135deg, var(--ci-brand-600), var(--ci-accent));
}

.ci-nav { flex: 1; min-width: 0; }
.ci-nav__list {
	display: flex;
	align-items: center;
	gap: .25rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-nav__item { position: static; }

.ci-nav__link {
	display: inline-flex;
	align-items: center;
	gap: .3rem;
	padding: .55rem .7rem;
	border: 0;
	border-radius: var(--ci-r-sm);
	background: none;
	font-size: .9375rem;
	font-weight: 500;
	white-space: nowrap;
	color: var(--ci-text);
	text-decoration: none;
	cursor: pointer;
	transition: color var(--ci-dur) var(--ci-ease), background-color var(--ci-dur) var(--ci-ease);
}

/* Tighten the bar progressively before it collapses to the drawer. */
@media (max-width: 1560px) {
	.ci-nav__link { padding-inline: .55rem; font-size: .9rem; }
	.ci-header__inner { gap: 1rem; }
}
@media (max-width: 1360px) {
	.ci-nav__link { padding-inline: .42rem; font-size: .875rem; }
	.ci-header__inner { gap: .75rem; }
	.ci-header__cta { padding-inline: .9rem; }
}
.ci-nav__link:hover,
.ci-nav__item.is-open > .ci-nav__link { color: var(--ci-brand-fg); background: var(--ci-brand-050); }
.ci-nav__link[aria-current="page"] { color: var(--ci-brand-fg); font-weight: 600; }
.ci-nav__chev { transform: rotate(90deg); transition: transform var(--ci-dur) var(--ci-ease); }
.ci-nav__item.is-open .ci-nav__chev { transform: rotate(-90deg); }

/* Mega panel */
.ci-mega {
	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	z-index: 90;
	padding: 0 var(--ci-wrap-pad) 1.25rem;
	opacity: 0;
	visibility: hidden;
	transform: translateY(-8px);
	transition: opacity var(--ci-dur-fast) var(--ci-ease), transform var(--ci-dur) var(--ci-ease-out), visibility var(--ci-dur-fast);
}
.ci-nav__item.is-open > .ci-mega { opacity: 1; visibility: visible; transform: translateY(0); }

/* The no-JS disclosure path.

   .is-open is written by app.js and by nothing else, so above 1200px -- where
   .ci-nav is the only navigation rendered and the burger is display:none --
   a visitor whose app.js never arrives could not reach any of the ten service
   pages from the header. visibility: hidden also takes the panel's links out
   of the tab order, so hover and Tab both failed, and the walker's own
   docblock claimed the opposite.

   Guarded on html:not(.ci-anim) so it is strictly a fallback: while the
   component is live it must not fight app.js's hover-intent and its Escape
   handling. :focus-within carries the keyboard, which a :hover-only fallback
   would leave out. */
/* Scoped to the width it was written for. Unscoped, it also applied to the
   narrow fallback strip below -- where tabbing onto the trigger opened a
   1200px-wide two-column panel over a 360px phone. */
@media (min-width: 1201px) {
	html:not(.ci-anim) .ci-nav__item--has-mega:hover > .ci-mega,
	html:not(.ci-anim) .ci-nav__item--has-mega:focus-within > .ci-mega {
		opacity: 1;
		visibility: visible;
		transform: none;
	}
}

/* The rows arrive just behind the panel rather than with it.
   Capped at six steps: a ten-row panel at the full step would still be
   arriving after the pointer has already reached the row it came for. */
@media (prefers-reduced-motion: no-preference) {
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__list > li {
		animation: ci-mega-in var(--ci-dur) var(--ci-enter) backwards;
	}
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__list > li:nth-child(2) { animation-delay: var(--ci-stagger-tight); }
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__list > li:nth-child(3) { animation-delay: calc(var(--ci-stagger-tight) * 2); }
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__list > li:nth-child(4) { animation-delay: calc(var(--ci-stagger-tight) * 3); }
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__list > li:nth-child(5) { animation-delay: calc(var(--ci-stagger-tight) * 4); }
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__list > li:nth-child(n+6) { animation-delay: calc(var(--ci-stagger-tight) * 5); }
	html.ci-anim .ci-nav__item.is-open > .ci-mega .ci-mega__aside { animation: ci-mega-in var(--ci-dur) var(--ci-enter) calc(var(--ci-stagger-tight) * 3) backwards; }
}
@keyframes ci-mega-in {
	from { opacity: 0; transform: translate3d(0, 6px, 0); }
}

.ci-mega__inner {
	--sf-r: var(--ci-r-lg);
	display: grid;
	grid-template-columns: minmax(0, 1fr) 280px;
	gap: 1.75rem;
	max-width: var(--ci-wrap);
	margin-inline: auto;
	box-shadow: var(--ci-sh-3);
}
.ci-mega__list {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: .25rem .75rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-mega__link {
	display: grid;
	grid-template-columns: 36px minmax(0, 1fr);
	grid-template-rows: auto auto;
	column-gap: .75rem;
	padding: .7rem .8rem;
	border-radius: var(--ci-r);
	text-decoration: none;
	color: var(--ci-text);
	/* No transform leg: no rule anywhere gives .ci-mega__link a transform, so
	   it was a dead declaration that read as a source of truth. */
	transition: background-color var(--ci-dur) var(--ci-ease);
}
.ci-mega__link:hover { background: var(--ci-brand-050); }
.ci-mega__icon {
	grid-row: 1 / span 2;
}
.ci-mega__link:hover .ci-mega__icon { background: var(--ci-brand-600); color: var(--ci-on-brand); }
.ci-mega__label { font-weight: 600; font-size: .9375rem; line-height: 1.35; }
.ci-mega__desc {
	font-size: .8125rem;
	line-height: 1.45;
	color: var(--ci-text-3);
	margin-top: .15rem;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
/* Grouped mega panel. The list is a two-by-two grid of THEMES rather than a
   two-column grid of ten services, which is the same structure the overview
   page uses -- a visitor meets one information architecture, not two.
   .ci-sub is the walker's own class for depth-1 children (nav-walker.php
   start_lvl) and had no rule at all until this change, so a grouped
   provisioned menu rendered its services as a browser-default nested list. */
/* Home-page theme grid. Four cards, ten links, no truncation -- the flat grid
   it replaces showed ten equal cards with no indication of which belonged
   together, and the two six-card teasers elsewhere silently dropped Managed
   Services. */
.ci-theme-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
	gap: var(--ci-gap);
}
.ci-theme-card { gap: .35rem; }
.ci-theme-card__title { font-size: var(--ci-fs-h3); margin: 0; }
.ci-theme-card__intro { color: var(--ci-text-2); font-size: var(--ci-fs-sm); margin: 0 0 .85rem; }
.ci-theme-card__list {
	display: grid;
	gap: .15rem;
	/* Top-aligned, not bottom-aligned. `margin: auto 0 0` pushed each list to
	   the foot of its card so the four lists shared a baseline -- which reads
	   as deliberate only while the cards hold a similar number of links. They
	   do not: Security and compliance holds two where the others hold three,
	   so it alone opened a 101px hole between its intro and its first link and
	   looked like a card with something missing. The cards still stretch to the
	   row's height; only the content's alignment inside them changed. */
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-theme-card__link {
	display: flex;
	align-items: center;
	gap: .65rem;
	padding: .5rem .55rem;
	margin-inline: -.55rem;
	border-radius: var(--ci-r-sm);
	text-decoration: none;
	color: var(--ci-text);
	font-weight: 500;
	font-size: .9375rem;
	transition: background-color var(--ci-dur) var(--ci-ease);
}
.ci-theme-card__link:hover { background: var(--ci-brand-050); }
.ci-theme-card__arrow { margin-left: auto; color: var(--ci-text-3); flex: none; transition: transform var(--ci-dur) var(--ci-ease-out); }
.ci-theme-card__link:hover .ci-theme-card__arrow { transform: translateX(var(--ci-nudge)); color: var(--ci-brand-fg); }

/* Keyed on the group title's presence rather than on a modifier class: the
   provisioned menu is rendered by CI_Nav_Walker, which has no way to know its
   children are grouped and so cannot add a modifier. The explicit class stays
   for the hand-built fallback. */
.ci-mega__list--grouped,
.ci-mega__list:has(> li > .ci-mega__group-title) {
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 1.25rem 1.5rem;
}
.ci-mega__group { display: block; }
.ci-sub {
	display: grid;
	gap: .1rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-mega__group-title {
	display: block;
	grid-column: 1 / -1;
	padding: .6rem .8rem .2rem;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--ci-text-3);
}
.ci-mega__list--grouped .ci-mega__desc,
.ci-mega__list:has(.ci-mega__group-title) .ci-mega__desc { display: none; }
.ci-mega__list--grouped .ci-mega__link,
.ci-mega__list:has(.ci-mega__group-title) .ci-mega__link { padding: .5rem .8rem; }
.ci-mega__group-title {
	padding: 0 .8rem .35rem;
	border-bottom: 1px solid var(--ci-line);
	margin-bottom: .35rem;
}
/* Default-walker sublists inside the mega panel, for completeness: the panel
   is CI_Nav_Walker's today, but nothing stops a site assigning the location
   to a plugin-rendered menu. */
.ci-mega__list .sub-menu {
	display: grid;
	gap: .1rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.ci-mega__aside {
	--sf-bd: transparent;
	/* The one gradient deliberately NOT routed through --sf-bg. Declaring a
	   custom property twice gives no fallback: its value is an arbitrary
	   token stream, so the parser accepts color-mix() regardless of support,
	   the later declaration always wins, and an engine that cannot compute it
	   drops `background` to transparent rather than to the plain gradient.
	   Declaring `background` itself twice is a real fallback, so it stays
	   here and overrides the primitive. */
	background: linear-gradient(160deg, var(--ci-ink-700), var(--ci-ink-900));
	background:
		radial-gradient(400px 200px at 90% 0%, color-mix(in srgb, var(--ci-accent) 30%, transparent), transparent 70%),
		linear-gradient(160deg, var(--ci-ink-700), var(--ci-ink-900));
	color: var(--ci-on-dark);
}
.ci-mega__aside-eyebrow {
	margin: 0 0 .5rem;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--ci-accent);
}
.ci-mega__aside-text { margin: 0 0 1rem; font-size: .875rem; line-height: 1.55; color: var(--ci-on-dark-2); }
.ci-mega__aside-link {
	transition: color var(--ci-dur-fast) var(--ci-ease);
	display: inline-flex;
	align-items: center;
	gap: .4rem;
	font-size: .875rem;
	font-weight: 600;
	color: #fff;
	text-decoration: none;
}
.ci-mega__aside-link:hover { color: var(--ci-accent); }

@media (max-width: 1180px) {
	.ci-mega__inner { grid-template-columns: minmax(0, 1fr); }
	.ci-mega__aside { display: none; }
}

.ci-header__actions { flex: none; display: flex; align-items: center; gap: .75rem; }

/* A transform, not a width, and no transition at all.
 *
 * Animating `width` relayouts on every scroll frame, and a .1s ease on top of
 * a value that is already being written once per frame means the bar trails
 * the reader all the way down the page and then eases forward for another
 * tenth of a second after the scroll has stopped -- so at the foot of a long
 * page it briefly reads as not quite finished. rAF is already the clock; the
 * bar does not need a second one. */
.ci-header__progress {
	position: absolute;
	left: 0;
	bottom: -1px;
	height: 2px;
	width: 100%;
	transform: scaleX(0);
	transform-origin: 0 50%;
	background: linear-gradient(90deg, var(--ci-brand-600), var(--ci-accent));
}

.ci-burger {
	display: none;
	/* 44px literal became the token: same value, now on the scale. */
	width: var(--ci-chip-md);
	height: var(--ci-chip-md);
	padding: 0;
	border: 1px solid var(--ci-line);
	border-radius: var(--ci-r-sm);
	background: var(--ci-raised);
	cursor: pointer;
}
.ci-burger__box {
	display: grid;
	gap: 4px;
	width: 20px;
	margin-inline: auto;
}
.ci-burger__box span {
	display: block;
	height: 2px;
	border-radius: 2px;
	background: var(--ci-text);
	transition: transform var(--ci-dur) var(--ci-ease), opacity var(--ci-dur-fast) var(--ci-ease);
}
.ci-burger[aria-expanded="true"] .ci-burger__box span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.ci-burger[aria-expanded="true"] .ci-burger__box span:nth-child(2) { opacity: 0; }
.ci-burger[aria-expanded="true"] .ci-burger__box span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 1200px) {
	.ci-nav { display: none; }
	.ci-burger { display: block; }
	.ci-header__inner { justify-content: space-between; }
	.ci-header__cta { display: none; }
}
@media (min-width: 600px) and (max-width: 1200px) {
	.ci-header__cta { display: inline-flex; }
}

/* Navigation that survives app.js not arriving.
 *
 * Below 1200px the bar is display:none and the burger is the only affordance
 * — and the burger does nothing at all without app.js, because the drawer
 * ships with a literal `hidden` attribute that only openDrawer() removes. So
 * a phone visitor whose script is blocked had no site navigation whatsoever
 * beyond the footer's links.
 *
 * The bar is already in the document at every width; it is hidden only
 * because the drawer is meant to replace it. When nothing claims the gate,
 * put it back as a scrolling strip of the top-level destinations and take the
 * dead burger away. The mega panels stay shut — there is no hover on a phone
 * to open them — but every top-level page is reachable, and the ten service
 * pages are one step further on from the IT Services overview the trigger
 * already links to. */
@media (max-width: 1200px) {
	/* Its own row. Sharing the header row with the brand and the CTA left
	   roughly 100px for the whole menu on a 360px phone -- one truncated
	   label, with the scrollbar suppressed so nothing said it scrolled. */
	html:not(.ci-anim) .ci-header__inner { flex-wrap: wrap; }
	html:not(.ci-anim) .ci-nav { display: block; min-width: 0; flex: 1 0 100%; order: 3; }
	html:not(.ci-anim) .ci-burger { display: none; }
	/* A fade on the end that still has content, since the scrollbar is
	   hidden and there is otherwise no cue that the strip moves. */
	html:not(.ci-anim) .ci-nav__list {
		mask-image: linear-gradient(90deg, #000 85%, transparent);
		-webkit-mask-image: linear-gradient(90deg, #000 85%, transparent);
	}
	html:not(.ci-anim) .ci-nav__list {
		flex-wrap: nowrap;
		overflow-x: auto;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}
	html:not(.ci-anim) .ci-nav__list::-webkit-scrollbar { display: none; }
	html:not(.ci-anim) .ci-nav__chev { display: none; }
}

/* Drawer */
.ci-drawer {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: grid;
	grid-template-columns: 1fr;
}
.ci-drawer[hidden] { display: none; }
.ci-drawer__scrim {
	position: absolute;
	inset: 0;
	border: 0;
	padding: 0;
	background: var(--ci-scrim);
	backdrop-filter: blur(3px);
	opacity: 0;
	transition: opacity var(--ci-dur) var(--ci-ease);
	cursor: pointer;
}
.ci-drawer.is-open .ci-drawer__scrim { opacity: 1; }
.ci-drawer__panel {
	position: relative;
	z-index: 1;
	margin-left: auto;
	display: flex;
	flex-direction: column;
	width: min(420px, 92vw);
	height: 100%;
	background: var(--ci-drawer-bg);
	border-left: 1px solid var(--ci-drawer-bd);
	box-shadow: var(--ci-drawer-sh);
	transform: translateX(100%);
	transition: transform var(--ci-dur) var(--ci-ease-out);
}
.ci-drawer.is-open .ci-drawer__panel { transform: translateX(0); }
.ci-drawer__head {
	/* 1.25rem inline, shared with .ci-drawer__list > li > a and
	   .ci-drawer__foot so the drawer's rows line up. */
	padding: 1.1rem 1.25rem;
	display: flex;
	align-items: center;
	justify-content: space-between;
}
.ci-drawer__title { font-family: var(--ci-font-display); font-weight: 600; }
.ci-drawer__close {
	display: grid;
	place-items: center;
	/* Was 40px, which is on no scale. --ci-chip-md is 44px, which is also
	   the WCAG 2.5.5 target size -- the right direction for a control. */
	width: var(--ci-chip-md);
	height: var(--ci-chip-md);
	border: 1px solid var(--ci-line);
	border-radius: var(--ci-r-sm);
	background: var(--ci-raised);
	cursor: pointer;
}
.ci-drawer__body { flex: 1; overflow-y: auto; padding: .5rem 0 1rem; -webkit-overflow-scrolling: touch; }
.ci-drawer__list,
.ci-drawer__list ul { margin: 0; padding: 0; list-style: none; }
.ci-drawer__list > li > a,
.ci-drawer__list > li > .ci-drawer__sub-toggle {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .5rem;
	width: 100%;
	padding: .85rem 1.25rem;
	border: 0;
	background: none;
	font-size: 1rem;
	font-weight: 500;
	color: var(--ci-text);
	text-decoration: none;
	text-align: left;
	cursor: pointer;
}
/* The disclosure button is styled identically to the links beside it and
   takes cursor: pointer, so it has to answer a hover the same way. */
.ci-drawer__list > li > a:hover,
.ci-drawer__list > li > .ci-drawer__sub-toggle:hover { background: var(--ci-paper-2); }
/* The drawer's submenu, on the theme's own disclosure rules.

   Three things were wrong here and they compounded.

   1. overflow: hidden clips PAINTING. It does not remove anything from the
      tab order or the accessibility tree, so a collapsed submenu reporting
      aria-expanded="false" still held eleven reachable links with no visible
      focus ring anywhere, and .ci-drawer__body scrolled as the browser tried
      to bring each zero-height target into view. The FAQ accordion already
      solved this four sections down with a delayed `visibility`; the drawer
      was never brought across.

   2. max-height: 1400px against roughly 650px of real content means the
      easing curve spends its whole run on travel that does not exist, so the
      stated duration was a fiction — the list appeared to finish early and
      then sat still. app.js now measures the list and writes --sub-h, so the
      transition covers exactly the distance the content occupies, and
      max-height: none afterwards removes the cap entirely so a longer menu,
      a larger font or a zoomed page can never be silently clipped.

   3. The collapsed state was gated on nothing, so if app.js never arrives the
      submenu is shut with no control that can open it. html.ci-anim is the
      same gate the accordion uses, and resolves to open. */
.ci-drawer__list .sub-menu {
	max-height: 0;
	overflow: hidden;
	visibility: hidden;
	background: var(--ci-paper-2);
	transition:
		max-height var(--ci-dur) var(--ci-ease-out),
		visibility 0s linear var(--ci-dur);
}
/* Without the gate the list is simply a list. */
html:not(.ci-anim) .ci-drawer__list .sub-menu {
	max-height: none;
	visibility: visible;
	transition: none;
}
.ci-drawer__list li.is-open > .sub-menu {
	max-height: var(--sub-h, 1400px);
	visibility: visible;
	transition:
		max-height var(--ci-dur) var(--ci-ease-out),
		visibility 0s;
}
/* Set by app.js once the opening transition has finished, so the cap stops
   constraining anything at all while the panel is open. */
.ci-drawer__list li.is-open.is-expanded > .sub-menu { max-height: none; }

/* Theme groups inside the drawer are headings, not a second accordion. One
   accordion containing four labelled groups is one gesture to learn; an
   accordion inside an accordion is two, on the viewport with the least room
   to recover from a wrong guess. */
.ci-drawer__group-title {
	display: block;
	padding: .9rem 1.25rem .35rem;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--ci-text-3);
}
/* A group's own list is never collapsed: only the top-level accordion
   collapses, so the nested list must not inherit the closed max-height. */
.ci-drawer__list .sub-menu .sub-menu,
.ci-drawer__list .sub-menu .ci-sub {
	visibility: inherit;
	max-height: none;
	background: none;
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-drawer__list .sub-menu a {
	display: block;
	padding: .7rem 1.25rem .7rem 2.1rem;
	font-size: .9375rem;
	color: var(--ci-text-2);
	text-decoration: none;
}
.ci-drawer__list .sub-menu a:hover { color: var(--ci-brand-fg); }
.ci-drawer__chev { transition: transform var(--ci-dur) var(--ci-ease); color: var(--ci-text-3); }
.ci-drawer__list li.is-open > * .ci-drawer__chev,
.ci-drawer__list li.is-open > .ci-drawer__chev { transform: rotate(90deg); }
.ci-drawer__foot { padding: 1.1rem 1.25rem 1.5rem; border-top: 1px solid var(--ci-line); }
.ci-drawer__contact { display: grid; gap: .5rem; margin-top: 1rem; font-size: .875rem; }
.ci-drawer__contact a {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	color: var(--ci-text-2);
	text-decoration: none;
}
body.ci-no-scroll { overflow: hidden; }
/* The scroll lock removes the page's scrollbar, which on any pointer platform
   with a classic space-taking scrollbar shifts the whole page sideways as the
   drawer opens and again as it closes. Reserving the gutter at all times costs
   nothing visually and makes the lock free. */
html { scrollbar-gutter: stable; }
/* A flick inside the drawer should not chain to the page behind it. */
.ci-drawer__body { overscroll-behavior: contain; }

/* The sheet arrives, then its contents do.
   Without this the panel and everything in it travel as one rigid board: the
   head, the whole list and the foot are already composited at their final
   positions when the slide starts, so a 440ms movement carries no sense of
   the menu assembling. Three beats, not ten -- the list is one of them, so a
   long menu cannot stretch the tail. */
@media (prefers-reduced-motion: no-preference) {
	html.ci-anim .ci-drawer.is-open .ci-drawer__head,
	html.ci-anim .ci-drawer.is-open .ci-drawer__body,
	html.ci-anim .ci-drawer.is-open .ci-drawer__foot {
		animation: ci-drawer-in var(--ci-dur) var(--ci-enter) backwards;
	}
	html.ci-anim .ci-drawer.is-open .ci-drawer__body { animation-delay: calc(var(--ci-stagger-tight) * 3); }
	html.ci-anim .ci-drawer.is-open .ci-drawer__foot { animation-delay: calc(var(--ci-stagger-tight) * 5); }
}
@keyframes ci-drawer-in {
	from { opacity: 0; transform: translate3d(14px, 0, 0); }
}

/* 7. Hero & decoration
   ========================================================================== */

.ci-hero {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	color: var(--ci-on-dark);
	background: var(--ci-hero-bg);
}

/* Keep the glow proportionate on narrow screens. */
@media (max-width: 760px) {
	.ci-decor__orb--a { width: 260px; height: 260px; top: -110px; right: -70px; opacity: .4; }
	.ci-decor__orb--b { width: 240px; height: 240px; bottom: -130px; left: -90px; opacity: .4; }
	.ci-decor__grid { background-size: 44px 44px; }
}
.ci-hero__inner { position: relative; z-index: 2; }
.ci-hero--home .ci-hero__inner { padding-block: clamp(3.5rem, 2rem + 7vw, 7.5rem) clamp(3rem, 2rem + 4vw, 5rem); }
.ci-hero--page .ci-hero__inner { padding-block: clamp(2.5rem, 1.8rem + 3.5vw, 4.5rem) clamp(3rem, 2.2rem + 4vw, 5.5rem); }
.ci-hero--compact .ci-hero__inner { padding-block: clamp(2rem, 1.5rem + 2.5vw, 3.25rem) clamp(2.5rem, 2rem + 3vw, 4rem); }

.ci-hero__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 340px);
	gap: clamp(2rem, 1rem + 4vw, 4rem);
	align-items: center;
}
.ci-hero__grid--home { grid-template-columns: minmax(0, 1fr) minmax(0, .95fr); align-items: center; }
@media (max-width: 960px) {
	.ci-hero__grid,
	.ci-hero__grid--home { grid-template-columns: minmax(0, 1fr); }

	/* Reflow, do not remove. The panel carries the service highlights and the
	   visual carries the operating-model diagram; both are content. */
	.ci-hero__panel,
	.ci-hero__visual { margin-top: var(--ci-gap); }
	.ci-hero__visual .ci-hero__figure { padding: var(--ci-pad); }
}

.ci-hero__copy { max-width: 62ch; }
.ci-hero__copy--narrow { max-width: 50ch; }
.ci-hero__eyebrow { margin-bottom: 1.1rem; }

.ci-hero__title {
	font-size: var(--ci-fs-h1);
	color: #fff;
	letter-spacing: -.028em;
	margin-bottom: .55rem;
}
.ci-hero__title--xl { font-size: var(--ci-fs-hero); line-height: 1.04; }

.ci-hero__lead {
	font-size: var(--ci-fs-lead);
	font-weight: 500;
	color: #fff;
	opacity: .96;
	max-width: 52ch;
	margin-bottom: 1.1rem;
}
.ci-hero__text { color: var(--ci-on-dark-2); max-width: 60ch; }
.ci-hero__actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; }
.ci-hero__search { margin-top: 1.75rem; max-width: 440px; }

.ci-hero__panel-inner {
	--sf-r: var(--ci-r-lg);
	backdrop-filter: blur(10px);
}
.ci-hero__panel-icon {
	/* Its own gradient, unchanged from v1.5.0: brand into accent. An earlier
	   pass rewrote this to accent-into-pale-cyan, which took the brand hue out
	   of the hero panel on every inner page. */
	background: linear-gradient(135deg, color-mix(in srgb, var(--ci-brand) 70%, #fff 30%), var(--ci-accent));
	border-color: transparent;
	color: #06142e;
	margin-bottom: 1.25rem;
}
.ci-hero__chips { margin: 0; padding: 0; list-style: none; display: grid; gap: .7rem; }
.ci-hero__chips li {
	display: flex;
	gap: .6rem;
	align-items: flex-start;
	font-size: .9375rem;
	color: var(--ci-on-dark);
}
.ci-hero__chips .ci-icon { margin-top: .22em; color: var(--ci-accent); }

/* Crumbs */
.ci-crumbs { margin-bottom: 1.5rem; font-size: .8125rem; }
.ci-crumbs ol { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0; padding: 0; list-style: none; }
.ci-crumbs li { display: flex; align-items: center; gap: .4rem; color: var(--ci-on-dark-3); }
.ci-crumbs li + li::before { content: "/"; opacity: .5; }
/* One of the two links in the header stack whose hover snapped, next to a
   topbar and a nav that both fade. The other is .ci-mega__aside-link, whose
   transition is declared on its own rule in the mega panel section -- a
   component's motion lives with the component. */
.ci-crumbs a { transition: color var(--ci-dur-fast) var(--ci-ease); }
.ci-crumbs a { color: var(--ci-on-dark-2); text-decoration: none; }
.ci-crumbs a:hover { color: #fff; text-decoration: underline; }
.ci-crumbs [aria-current] { color: #fff; }

/* Decoration layer */
.ci-decor { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.ci-decor__grid {
	position: absolute;
	inset: -1px;
	background-image:
		linear-gradient(var(--ci-decor-grid) 1px, transparent 1px),
		linear-gradient(90deg, var(--ci-decor-grid) 1px, transparent 1px);
	background-size: 64px 64px;
	mask-image: radial-gradient(900px 500px at 50% 0%, #000 20%, transparent 80%);
	-webkit-mask-image: radial-gradient(900px 500px at 50% 0%, #000 20%, transparent 80%);
}
.ci-decor__orb {
	position: absolute;
	border-radius: 50%;
	filter: blur(70px);
	opacity: var(--ci-decor-orb-o);
}
.ci-decor__orb--a {
	width: 480px;
	height: 480px;
	top: -180px;
	right: -80px;
	background: var(--ci-accent);
	animation: ci-float 18s var(--ci-ease) infinite alternate;
}
.ci-decor__orb--b {
	width: 420px;
	height: 420px;
	bottom: -200px;
	left: -120px;
	background: var(--ci-brand-600);
	animation: ci-float 22s var(--ci-ease) infinite alternate-reverse;
}
.ci-decor__lines { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--ci-accent); opacity: .45; }

@keyframes ci-float {
	from { transform: translate3d(0, 0, 0) scale(1); }
	to { transform: translate3d(-40px, 30px, 0) scale(1.12); }
}

/* The layered stack visual that used to fill the home hero is gone: it was
   aria-hidden decoration carrying the same four category names the service
   grid lists immediately below it, and it has been replaced by the
   operating-model figure, which says something the page does not say twice.
   Its rules are removed rather than left behind. */

/* Marquee */
.ci-hero__marquee {
	position: relative;
	z-index: 2;
	border-top: 1px solid var(--ci-on-dark-line);
	padding-block: 1rem;
}
.ci-marquee { overflow: hidden; mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.ci-marquee__track { display: flex; width: max-content; animation: ci-marquee 46s linear infinite; }
.ci-marquee__item {
	display: inline-flex;
	align-items: center;
	gap: 2rem;
	padding-right: 2rem;
	font-size: .875rem;
	font-weight: 500;
	letter-spacing: .02em;
	color: var(--ci-on-dark-3);
	white-space: nowrap;
}
.ci-marquee__item i { width: 4px; height: 4px; border-radius: 50%; background: var(--ci-accent); opacity: .6; }
@keyframes ci-marquee {
	from { transform: translateX(0); }
	to { transform: translateX(-50%); }
}

/* 8. Sections & components
   ========================================================================== */

/* Intro split */
.ci-intro-split {
	display: grid;
	grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
	gap: clamp(1.5rem, 1rem + 3vw, 4rem);
	align-items: start;
}
@media (max-width: 860px) { .ci-intro-split { grid-template-columns: 1fr; } }
.ci-intro-split__head h2 { font-size: var(--ci-fs-h2); margin: 0; }
.ci-intro-split__body { font-size: var(--ci-fs-lead); color: var(--ci-text-2); }
.ci-intro-split__body .ci-btn { margin-top: 1.25rem; }

/* Cards */
.ci-card {
	--sf-r: var(--ci-r-lg);
	position: relative;
	display: flex;
	flex-direction: column;
	text-decoration: none;
	color: inherit;
	overflow: hidden;
	/* No transition here. The surface primitive already declares the whole
	   property for both element keyings, and its comment says that list
	   REPLACES each component's own -- so a transition written here is
	   overridden on every card that has a hover, and is a false source of
	   truth for anyone tuning the number. */
}
a.ci-card:hover .ci-card__more .ci-icon { transform: translateX(var(--ci-nudge)); }
.ci-card__glow {
	position: absolute;
	inset: -1px -1px auto;
	height: 3px;
	background: linear-gradient(90deg, var(--ci-brand-600), var(--ci-accent));
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--ci-m-md) var(--ci-ease-out);
}
a.ci-card:hover .ci-card__glow { transform: scaleX(1); }
.ci-card__icon {
	margin-bottom: 1.1rem;
}
/* No scale on the card icon: .ci-chip-icon carries no transform in its
   transition, so this jumped instantly while the colour beside it eased --
   and a chip that grows shifts the text next to it. */

.ci-card__title { font-size: var(--ci-fs-h3); margin: 0 0 .5rem; }
.ci-card__text { font-size: .9375rem; color: var(--ci-text-2); margin: 0; }
.ci-card--industry { cursor: default; }

/* Capability cards */
.ci-caps { margin: 0; padding: 0; list-style: none; }
.ci-cap {
	--sf-bg: var(--ci-paper-2);
	--sf-bd: transparent;
	--sf-r: var(--ci-r-lg);
	position: relative;
	transition: background-color var(--ci-dur) var(--ci-ease), border-color var(--ci-dur) var(--ci-ease), transform var(--ci-dur) var(--ci-ease-out);
}
/* No lift, no shadow: .ci-cap is a bare <li> in both renderers, so a lift
   here advertised a click that does not exist -- on every capability on all
   ten service pages. A background step is the honest amount of feedback for
   something the pointer is merely over. */
.ci-cap:hover { background: var(--ci-hover-surface); }
.ci-cap__num {
	display: block;
	margin-bottom: .75rem;
	font-family: var(--ci-font-display);
	font-size: .8125rem;
	font-weight: 700;
	letter-spacing: .08em;
	color: var(--ci-brand-fg);
}
.ci-cap__title { font-size: 1.0625rem; margin: 0 0 .45rem; line-height: 1.3; }
.ci-cap__text { font-size: .9375rem; color: var(--ci-text-2); margin: 0; }
.ci-section--alt .ci-cap { background: var(--ci-raised); border-color: var(--ci-line-2); }

/* Capability groups --------------------------------------------------------
   Replaces the flat wall of 8-12 equal cards. Each group is a labelled band
   with a rule, so the page has a spine a reader can scan before deciding to
   read. The capability itself is now a flat item, not a card: it sits inside a
   group, and nesting a card in a card is what made the old page feel busy. */

.ci-capgroup { margin-top: clamp(2.25rem, 1.6rem + 2.4vw, 3.5rem); }
.ci-capgroup:first-of-type { margin-top: 0; }

.ci-capgroup__head {
	display: flex;
	align-items: baseline;
	gap: 1rem;
	padding-bottom: .9rem;
	margin-bottom: 1.6rem;
	border-bottom: 1px solid var(--ci-line);
}
.ci-capgroup__title {
	font-size: clamp(1.1875rem, 1.1rem + .35vw, 1.4375rem);
	margin: 0;
	letter-spacing: -.02em;
}
.ci-capgroup__count {
	margin-left: auto;
	flex: none;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 600;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--ci-text-3);
}

.ci-capgroup__list {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
	gap: 1.5rem 2.25rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-capgroup__list .ci-cap {
	--sf-bw: 0;
	/* NOT --flush: that sets --sf-pad: 0 and overflow only, leaving the
	   fill, edge and radius in place -- which would put a white card
	   around every capability inside a group, the exact nesting this
	   reset exists to prevent. */
	--sf-pad: 0;
	--sf-bg: none;
	--sf-bd: transparent;
	--sf-r: 0;
}
/* Was here to cancel .ci-cap's lift. The lift is gone, so this only needs to
   opt the grouped renderer out of the background step. */
.ci-capgroup__list .ci-cap:hover { background: none; }
.ci-capgroup__list .ci-cap__title {
	font-size: 1rem;
	margin-bottom: .35rem;
	/* A short brand rule instead of a number: capabilities are not steps, and
	   numbering them implied a sequence that does not exist. */
	padding-left: .85rem;
	border-left: 2px solid var(--ci-brand-fg);
	line-height: 1.35;
}
.ci-capgroup__list .ci-cap__text { padding-left: .85rem; }

@media (max-width: 600px) {
	.ci-capgroup__head { flex-wrap: wrap; gap: .25rem 1rem; }
	.ci-capgroup__count { margin-left: 0; width: 100%; }
}

/* Steps */
.ci-steps {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
	gap: var(--ci-gap);
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: none;
}
.ci-step { position: relative; padding-top: 1.5rem; border-top: 2px solid var(--ci-line); }
.ci-step__num {
	display: block;
	margin-bottom: .75rem;
	font-family: var(--ci-font-display);
	font-size: 1.75rem;
	font-weight: 700;
	letter-spacing: -.02em;
	color: var(--ci-brand-fg);
}
.ci-step__title { font-size: var(--ci-fs-h3); margin: 0 0 .5rem; }
.ci-step__text { color: var(--ci-text-2); margin: 0; font-size: .9375rem; }
.ci-steps--light .ci-step { border-top-color: var(--ci-on-dark-line); }
.ci-steps--light .ci-step__num { color: var(--ci-accent); }

/* Who does what ------------------------------------------------------------
   The coordination story, made structural. Code-Inspect is the only party
   present at every stage, so its card is the one that is raised and marked. */

.ci-parties {
	margin-top: clamp(2.5rem, 1.8rem + 2.8vw, 4rem);
	padding-top: clamp(2rem, 1.5rem + 2vw, 3rem);
	border-top: 1px solid var(--ci-line);
}
.ci-parties__lead {
	max-width: 48ch;
	margin-bottom: 1.75rem;
	font-size: var(--ci-fs-lead);
	color: var(--ci-text-2);
}
.ci-parties__list {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
	gap: var(--ci-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-party {
	/* --sunken's softer --ci-line-2 faded these cards, and in dark mode that
	   edge is doing most of the separating. */
	--sf-bd: var(--ci-elev-bd);

}
.ci-party--lead {
	background: var(--ci-raised);
	border-color: color-mix(in srgb, var(--ci-brand) 32%, var(--ci-elev-bd));
	box-shadow: var(--ci-sh-2);
}
.ci-party__icon {
	margin-bottom: .9rem;
}
.ci-party--lead .ci-party__icon {
	background: var(--ci-brand-600);
	border-color: var(--ci-brand-600);
	color: var(--ci-on-brand);
}
.ci-party__role {
	display: flex;
	align-items: center;
	gap: .6rem;
	flex-wrap: wrap;
	font-size: 1.0625rem;
	margin-bottom: .4rem;
}
.ci-party__tag {
	font-family: var(--ci-font);
	color: var(--ci-brand-fg);
}
.ci-party__text { font-size: .9375rem; color: var(--ci-text-2); margin: 0; }

/* Vision / mission */
.ci-vm { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--ci-gap); }
.ci-vm__card {
	--sf-r: var(--ci-r-lg);
}
/* These two cards used to label themselves with the in-card eyebrow at
   .8125rem and then set the statement at --ci-fs-lead, which resolves to its
   1.25rem ceiling (20px) from about 1200px up -- so the heading was 13px over a
   20px body, the smallest type in the card over the largest, which is the
   hierarchy upside down.
   The title takes a rung of its own, between the shared card title and the
   section heading. --ci-fs-h3 was the obvious choice and is not quite enough:
   its floor is 1.125rem, which at phone width leaves an 18px title over 16px
   body -- a hierarchy you have to measure to see. These two cards are the
   entire band and carry the statements the section exists to make, so they can
   afford the rung above the ten-a-page card titles. */
.ci-vm__title { font-size: clamp(1.25rem, 1.1rem + .6vw, 1.5rem); margin: 0 0 .65rem; }
.ci-vm__text { font-size: var(--ci-fs-base); line-height: 1.65; color: var(--ci-text-2); margin: 0; }
.ci-vm--light .ci-vm__card { background: rgba(255, 255, 255, .05); border-color: var(--ci-on-dark-line); }
.ci-vm--light .ci-vm__title { color: #fff; }
/* --ci-on-dark, as before, NOT the dimmer --ci-on-dark-2 that reads as the
   right token for body-rung text. The client asked for these statements to be
   smaller, not quieter, and pairing the two would have taken the About band
   from 10.67:1 to 6.70:1 -- still AA, and still a contrast cut nobody asked
   for. */
.ci-vm--light .ci-vm__text { color: var(--ci-on-dark); }

/* Values */
.ci-values {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
	gap: 1.25rem var(--ci-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-value { display: flex; gap: 1rem; }
.ci-value h3 { font-size: 1rem; margin: 0 0 .25rem; }
.ci-value p { font-size: .9375rem; color: var(--ci-text-2); margin: 0; }

/* Teasers */
.ci-teasers { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--ci-gap); }
.ci-teaser {
	--sf-r: var(--ci-r-lg);
	display: flex;
	flex-direction: column;
	text-decoration: none;
	color: inherit;
}
.ci-teaser:hover { transform: translateY(-4px); background: var(--ci-raised); box-shadow: var(--ci-sh-2); }
.ci-teaser:hover .ci-card__more .ci-icon { transform: translateX(var(--ci-nudge)); }
.ci-teaser__icon {
	margin-bottom: 1.25rem;
	box-shadow: var(--ci-sh-1);
}
.ci-teaser h2 { font-size: var(--ci-fs-h3); margin: 0 0 .5rem; }
.ci-teaser p { color: var(--ci-text-2); font-size: .9375rem; margin: 0; }

/* Service theme groups (overview page) ------------------------------------
   Ten flat rows is the provider's taxonomy; a buyer arrives with a problem.
   The themes give the overview a spine and shorten each list to two or three. */

.ci-theme-group { margin-top: clamp(2.5rem, 1.8rem + 2.6vw, 3.75rem); }
.ci-theme-group:first-of-type { margin-top: 0; }
.ci-theme-group__head {
	display: grid;
	gap: .3rem;
	margin-bottom: 1.25rem;
}
.ci-theme-group__title {
	font-size: clamp(1.1875rem, 1.1rem + .35vw, 1.4375rem);
	margin: 0;
	letter-spacing: -.02em;
}
.ci-theme-group__intro {
	margin: 0;
	font-size: .9375rem;
	color: var(--ci-text-3);
	max-width: 60ch;
}

/* Service list rows (overview page) */
.ci-svc-list { display: grid; border-top: 1px solid var(--ci-line); }
.ci-svc-row {
	--row-pad: 1.4rem .5rem;
	padding: var(--row-pad);
	display: grid;
	grid-template-columns: var(--ci-chip-md) minmax(0, 1fr) 40px;
	align-items: center;
	gap: .75rem;
	text-decoration: none;
	color: inherit;
}
/* No inset slide: its transition went with the move to a.ci-row, so the
   content jumped 8px in one frame. The arrow nudge carries the direction. */
.ci-svc-row:hover { background: var(--ci-brand-050); }
.ci-svc-row:hover .ci-svc-row__icon { background: var(--ci-brand-600); border-color: var(--ci-brand-600); color: var(--ci-on-brand); }
.ci-svc-row__body { display: grid; gap: .2rem; }
.ci-svc-row__title { font-family: var(--ci-font-display); font-size: 1.125rem; font-weight: 600; letter-spacing: -.015em; }
.ci-svc-row__text { font-size: .9375rem; color: var(--ci-text-2); }
.ci-svc-row__arrow { color: var(--ci-text-3); transition: transform var(--ci-dur) var(--ci-ease-out), color var(--ci-dur) var(--ci-ease); }
.ci-svc-row:hover .ci-svc-row__arrow { transform: translateX(var(--ci-nudge)); color: var(--ci-brand-fg); }
@media (max-width: 680px) {
	.ci-svc-row { grid-template-columns: var(--ci-chip-md) minmax(0, 1fr); row-gap: .5rem; }
	.ci-svc-row__arrow { display: none; }
}

/* Approach band */
.ci-approach {
	display: grid;
	grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
	gap: var(--ci-gap-lg);
	align-items: start;
}
@media (max-width: 860px) { .ci-approach { grid-template-columns: 1fr; } }
.ci-approach__head h2 { font-size: var(--ci-fs-h2); margin: 0; }
.ci-approach__body { font-size: var(--ci-fs-lead); color: var(--ci-text-2); margin: 0; }

/* Business value band */
.ci-value-band__inner {
	/* Gradient forwarded as the surface fill rather than lost. */
	--sf-bg: linear-gradient(135deg, var(--ci-brand-050), var(--ci-paper-3));
	--sf-r: var(--ci-r-lg);
	display: grid;
	grid-template-columns: 88px minmax(0, 1fr) auto;
	gap: clamp(1.25rem, 1rem + 2vw, 2.5rem);
	align-items: center;
}
.ci-value-band__mark {
	box-shadow: var(--ci-sh-1);
}
.ci-value-band__text { font-size: var(--ci-fs-lead); color: var(--ci-text); margin: 0; }
@media (max-width: 920px) {
	.ci-value-band__inner { grid-template-columns: minmax(0, 1fr); }
	.ci-value-band__mark { display: none; }
}

/* Flow (partners) */
.ci-flow {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
	gap: var(--ci-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-flow__item {
	--sf-r: var(--ci-r-lg);
	position: relative;
}
.ci-flow__num {
	margin-bottom: 1rem;
	font-family: var(--ci-font-display);
	font-size: .8125rem;
	font-weight: 700;
}
.ci-flow__item h3 { font-size: 1.0625rem; margin: 0 0 .4rem; }
.ci-flow__item p { font-size: .9375rem; color: var(--ci-text-2); margin: 0; }

/* Panels */
.ci-panel {
	/* The widest box on its page: it keeps the large corner it had, or it
	   reads tighter than the small cards above it. */
	--sf-r: var(--ci-r-lg);
	/* Deliberately no fill and no edge in the base: every current use
	   carries --bordered or --invert, and a bare .ci-panel must not
	   become an opaque white box just because it adopted the box. */
	--sf-bg: none;
	--sf-bd: transparent;
	display: grid;
	grid-template-columns: minmax(0, 1.2fr) minmax(0, .8fr);
	gap: var(--ci-gap-lg);
	align-items: center;
}
@media (max-width: 860px) { .ci-panel { grid-template-columns: 1fr; } }
.ci-panel--bordered { background: var(--ci-paper-2); border: 1px solid var(--ci-line); }
.ci-panel--invert {
	grid-template-columns: minmax(0, 1fr);
	text-align: center;
	background: rgba(255, 255, 255, .05);
	border: 1px solid var(--ci-on-dark-line);
}
.ci-panel--invert .ci-panel__copy { max-width: 58ch; margin-inline: auto; }
.ci-panel--invert h2 { color: #fff; font-size: var(--ci-fs-h2); }
.ci-panel--invert .ci-btn { margin-top: 1.5rem; }
.ci-panel__copy h2 { font-size: var(--ci-fs-h2); }
.ci-panel__copy p { color: var(--ci-text-2); font-size: var(--ci-fs-lead); }
.ci-panel__copy .ci-btn { margin-top: 1.5rem; }
.ci-panel__list { margin: 0; padding: 0; list-style: none; display: grid; gap: .85rem; }
.ci-panel__list li { display: flex; gap: .65rem; align-items: flex-start; font-size: .9375rem; }
.ci-panel__list .ci-icon { margin-top: .22em; color: var(--ci-brand-fg); }

/* Two-up */
.ci-two-up { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: clamp(2rem, 1.2rem + 3vw, 4rem); }
.ci-two-up__item h2 { font-size: var(--ci-fs-h2); }
.ci-two-up__item p { color: var(--ci-text-2); font-size: var(--ci-fs-lead); margin: 0; }

/* Themes list */
.ci-themes { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 0 var(--ci-gap); margin: 0; padding: 0; list-style: none; }
.ci-theme {
	/* Zero inline inset is deliberate: these rows sit flush to the
	   section's content edge on an ink band. */
	padding: 1.1rem 0;
	display: flex;
	align-items: center;
	gap: 1rem;
}
.ci-theme__num { font-family: var(--ci-font-display); font-size: .8125rem; font-weight: 700; letter-spacing: .08em; color: var(--ci-accent); }
.ci-theme__label { font-size: 1.0625rem; font-weight: 500; color: #fff; }

/* Tags. The base rule here used to restate .ci-pill declaration for
   declaration -- it was the pill primitive, written before the primitive
   existed. All that is left is the list and the weight. */
.ci-tags { display: flex; flex-wrap: wrap; gap: .6rem; margin: 0; padding: 0; list-style: none; }
/* Everything else about a tag is .ci-pill now, and a.ci-pill carries the
   transition. */
/* .ci-tag is gone. Once the box moved to .ci-pill all it still carried was a
   font-weight and a `color: inherit` on its child anchor that fought the
   pill's own colour, so post tags rendered darker than the identical-looking
   topic labels on Insights. The weight moved to .ci-pill; the class did not
   need to exist. */
/* Interactivity by element, as everywhere else: --link is the variant that
   wraps an anchor. The insights page renders the same shape as a bare label
   and used to light up with a brand border and go nowhere. */
a.ci-pill--link:hover { border-color: var(--ci-brand-600); color: var(--ci-brand-fg); background: var(--ci-brand-050); }
.ci-tags--post { margin-top: 2.5rem; }

/* Closing CTA */
.ci-closing { padding-block: 0; }
.ci-closing__inner {
	/* --ci-closing-bg is already indirected with four light/dark
	   definitions, so forwarding the token is all this needs. Declared
	   AFTER --ink in the cascade or --ink's translucent fill wins. */
	--sf-bg: var(--ci-closing-bg);
	/* No --sf-bd here: the dark-structural section sets a real
	   `border: 1px solid var(--ci-band-edge)` on this same selector, which is
	   the band seam this band is supposed to have. A --sf-bd would be
	   overridden by it and read as though it did something. */
	display: grid;
	/* max-content on the actions track, not 0. At 0 the column was free to go
	   under its own buttons: 234px at 1024 and 202px at 901 against a
	   "Request an Assessment" pill that needs 244, so the label sat outside
	   the shape that was supposed to contain it. A content floor cannot be
	   got wrong by a longer label or a translation, which a measured pixel
	   value can. */
	grid-template-columns: minmax(0, 1.4fr) minmax(max-content, .6fr);
	gap: clamp(1.75rem, 1.2rem + 3vw, 3.5rem);
	align-items: center;
	color: var(--ci-on-dark);
	box-shadow: var(--ci-sh-3);
}
.ci-closing { margin-block: var(--ci-section-y); }
@media (max-width: 900px) { .ci-closing__inner { grid-template-columns: 1fr; } }
.ci-closing__copy h2 { color: #fff; font-size: var(--ci-fs-h2); }
.ci-closing__copy p { color: var(--ci-on-dark-2); max-width: 60ch; }
/* Equal-width buttons, stacked.
   The two labels differ by several characters ("Talk to Our Team" against
   "Request an Assessment"), so the flex row this replaces sized each to its
   own text and the pair read as an accident rather than a choice.

   One column, not two. The first attempt used two fr tracks with
   `width: max-content`, on the reasoning that equal fr tracks under a
   max-content constraint all resolve to the widest item. That is true of a
   grid free to be its max-content width; this one is not. It sits in
   .ci-closing__inner's second track, `minmax(0, .6fr)` -- about 331px at the
   1240px wrap cap -- and the two buttons need 191.6 + 12 + 245.5 = 449px side
   by side. `max-width: 100%` clamped the grid, `minmax(0, 1fr)` let both
   tracks shrink under their content, and the result was two 140px pills with
   their labels spilling out of both ends and overlapping each other, on every
   page's closing band at every width from about 901px up.

   The measurement that missed it asserted the two widths were EQUAL and that
   the group did not exceed its parent. Both were true. 140px for "Request an
   Assessment" was in the output the whole time and should have been the
   finding. Equality is not the property that matters on its own.

   Stacked, each button is the column's width, so they are equal by
   construction and there is no width at which the label can overflow. */
.ci-closing__actions {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: .75rem;
	justify-items: stretch;
}
/* .ci-btn is inline-flex and left-aligns its label; stretched to a track it
   has to centre it, or the equal widths read as ragged text. */
.ci-closing__actions .ci-btn { justify-content: center; }
/* Where the band stacks, the actions get the full card width and two full-width
   pills look like a drawer rather than a choice, so cap them. */
@media (max-width: 900px) {
	/* Same reasoning as the track above: auto-fit decides the column count from
	   the floor it is given, so a floor below the widest label puts two
	   too-narrow columns side by side instead of dropping to one. */
	.ci-closing__actions { grid-template-columns: repeat(auto-fit, minmax(max-content, 1fr)); max-width: 26rem; }
}

/* Two labelled addresses in the footer's contact column. */
.ci-footer__place { display: grid; gap: .15rem; min-width: 0; }
.ci-footer__place-label {
	font-size: var(--ci-fs-eyebrow);
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--ci-on-dark-3);
}

/* Featured image */
.ci-featured { margin: 0 0 2.5rem; border-radius: var(--ci-r-lg); overflow: hidden; }
.ci-featured img { width: 100%; }

/* 9. Forms & contact
   ========================================================================== */

.ci-contact__grid {
	display: grid;
	grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
	gap: clamp(2rem, 1.2rem + 3vw, 4rem);
	align-items: start;
}
@media (max-width: 900px) { .ci-contact__grid { grid-template-columns: 1fr; } }

.ci-contact__title { font-size: var(--ci-fs-h3); margin-bottom: 1.25rem; }
.ci-contact__sub { color: var(--ci-text-2); margin-bottom: 1.75rem; }
.ci-contact__list { margin: 0 0 2rem; padding: 0; list-style: none; display: grid; gap: 1.25rem; }
.ci-contact__list li { display: flex; gap: 1rem; }
.ci-contact__body { display: grid; gap: .15rem; min-width: 0; }
.ci-contact__label {
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--ci-text-3);
}
.ci-contact__body a { color: var(--ci-text); text-decoration: none; font-weight: 500; overflow-wrap: anywhere; }
.ci-contact__body a:hover { color: var(--ci-brand-fg); text-decoration: underline; }
.ci-contact__body address { font-style: normal; color: var(--ci-text-2); font-size: .9375rem; }
.ci-contact__phones { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.ci-contact__phones span { color: var(--ci-line); }
.ci-contact__alt p:last-child { font-size: .9375rem; color: var(--ci-text-2); }

.ci-contact__form {
	--sf-r: var(--ci-r-lg);
	box-shadow: var(--ci-sh-2);
}

.ci-form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.1rem; }
@media (max-width: 620px) { .ci-form__grid { grid-template-columns: 1fr; } }
.ci-field { display: grid; gap: .4rem; }
.ci-field--full { grid-column: 1 / -1; }
.ci-field label,
.ci-field__legend {
	font-size: .875rem;
	font-weight: 600;
	color: var(--ci-text);
}
.ci-req { color: var(--ci-danger); }
.ci-opt { font-weight: 400; color: var(--ci-text-3); }

.ci-field input[type="text"],
.ci-field input[type="email"],
.ci-field input[type="tel"],
.ci-field textarea,
.ci-select select,
.ci-search__input {
	width: 100%;
	padding: .8rem 1rem;
	font: inherit;
	font-size: .9375rem;
	color: var(--ci-text);
	background: var(--ci-field-bg);
	border: 1px solid var(--ci-line);
	border-radius: var(--ci-r-sm);
	transition: border-color var(--ci-dur-fast) var(--ci-ease), box-shadow var(--ci-dur-fast) var(--ci-ease), background-color var(--ci-dur-fast) var(--ci-ease);
}
.ci-field textarea { resize: vertical; min-height: 120px; }
.ci-field input:focus,
.ci-field textarea:focus,
.ci-select select:focus {
	background: var(--ci-field-bg-focus);
	border-color: var(--ci-control-accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--ci-control-accent) 28%, transparent);
}
.ci-field.has-error input,
.ci-field.has-error textarea { border-color: var(--ci-danger); background: var(--ci-danger-bg); color: var(--ci-text); }
.ci-field__error { margin: 0; font-size: .8125rem; color: var(--ci-danger); min-height: 0; }
/* :empty collapses the field's grid gap, which is why it is display:none and
   not visibility:hidden. The cost is that the message cannot transition in --
   display is not interpolable -- so four errors used to materialise in a
   single frame and shove the two-column grid as they did it.
   An ANIMATION can, for the same reason the tab panels can: going from
   display:none to displayed restarts it. */
.ci-field__error:empty { display: none; }
@media (prefers-reduced-motion: no-preference) {
	html.ci-anim .ci-field.has-error .ci-field__error {
		animation: ci-err-in var(--ci-dur) var(--ci-enter) backwards;
	}
}
@keyframes ci-err-in {
	from { opacity: 0; transform: translate3d(0, -5px, 0); }
}

.ci-select { position: relative; }
.ci-select select { appearance: none; -webkit-appearance: none; padding-right: 2.5rem; cursor: pointer; }
.ci-select .ci-icon {
	position: absolute;
	right: .9rem;
	top: 50%;
	transform: translateY(-50%) rotate(90deg);
	pointer-events: none;
	color: var(--ci-text-3);
}

.ci-chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.ci-chip { position: relative; }
.ci-chip input { position: absolute; opacity: 0; inset: 0; cursor: pointer; }
.ci-chip span {
	display: inline-block;
	padding: .5rem 1rem;
	border: 1px solid var(--ci-line);
	border-radius: 999px;
	background: var(--ci-field-bg);
	font-size: .875rem;
	font-weight: 500;
	color: var(--ci-text-2);
	/* Never `all`: outline is in `all`, so the focus ring grew in over a
	   quarter second instead of appearing, and during fast tabbing it trailed
	   the actual focus position. Only the three properties that change. */
	transition:
		background-color var(--ci-dur-fast) var(--ci-ease),
		border-color var(--ci-dur-fast) var(--ci-ease),
		color var(--ci-dur-fast) var(--ci-ease);
}
.ci-chip input:checked + span {
	background: var(--ci-chip-on);
	border-color: var(--ci-chip-on);
	color: var(--ci-chip-on-fg);
}

.ci-consent { display: flex; gap: .7rem; align-items: flex-start; font-weight: 400 !important; font-size: .875rem !important; color: var(--ci-text-2) !important; line-height: 1.55; }
.ci-consent input { margin-top: .25em; accent-color: var(--ci-control-accent); width: 17px; height: 17px; flex: none; }

.ci-hp { position: absolute !important; left: -9999px; height: 0; overflow: hidden; }

.ci-form__foot { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-top: 1.75rem; }
.ci-form__submit { border: 0; }
/* Pending.
 *
 * `opacity: .6` alone says "unavailable", which is the opposite of what is
 * happening: the request is in flight and the button is the only thing on
 * screen that knows. A 2px indeterminate sweep along its lower edge says
 * "working" without claiming a percentage it cannot know, and it costs one
 * pseudo-element.
 *
 * The dim is kept, but lighter -- the label still has to clear contrast while
 * the sweep runs. */
.ci-form__submit[aria-busy="true"],
.ci-form__submit[disabled] { opacity: .78; cursor: progress; transform: none; }
.ci-form__submit { position: relative; overflow: hidden; }
.ci-form__submit[aria-busy="true"]::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	inset-block-end: 0;
	height: 2px;
	background: linear-gradient(90deg, transparent, var(--ci-on-brand), transparent);
	transform: translateX(-100%);
	/* 1.1s is deliberately off the interaction scale: it is a CYCLE LENGTH,
	   not a response time, so it belongs with the marquee's 46s and the orbs'
	   22s rather than with --ci-dur. Nothing waits on it. */
	animation: ci-sweep 1.1s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
	/* A loop is the one kind of motion a reduced-motion visitor should never
	   be given, so the state is carried by the static bar alone. */
	.ci-form__submit[aria-busy="true"]::after {
		animation: none;
		transform: none;
		background: var(--ci-on-brand);
		opacity: .55;
	}
}
@media (forced-colors: active) {
	/* A gradient is a background image, which forced colours drop entirely --
	   so the bar never painted and the pending state was `opacity: .78`, which
	   reads as a disabled button rather than a working one. A flat forced
	   colour survives, and the sweep still reads as motion on top of it. */
	.ci-form__submit[aria-busy="true"]::after { background: CanvasText; }
}
@keyframes ci-sweep {
	from { transform: translateX(-100%); }
	to { transform: translateX(100%); }
}
.ci-form__status { margin: 0; font-size: .875rem; font-weight: 500; }
/* `is-success` used to be here too and never applied to anything. app.js
   removes that class and never adds it, and the server now answers a
   successful submission by replacing the form with .ci-form__done rather than
   by recolouring its status line -- so the rule could not be reached from
   either path. `is-error` is the opposite: app.js adds it on a failed AJAX
   submission, and ci_enquiry_form() adds it when it renders server-side
   errors. */
.ci-form.is-error .ci-form__status { color: var(--ci-danger); }
/* The confirmation arrives rather than appearing.
   The swap replaces roughly 900px of form with a ~120px card, so without this
   the map and the footer simply leap up the viewport. app.js holds the form's
   height for a frame so the collapse is a movement rather than a jump; this
   is the card's own half of it. */
@media (prefers-reduced-motion: no-preference) {
	html.ci-anim .ci-form__done { animation: ci-rise-in var(--ci-m-md) var(--ci-enter) backwards; }
	/* A fade, not a second rise. ci-rise-in on the card AND on its children
	   composes: 20px of card plus 20px of heading means the <h3> starts 40px
	   low and arrives from a different distance than the box around it. */
	html.ci-anim .ci-form__done > * { animation: ci-fade-in var(--ci-m-md) var(--ci-enter) calc(var(--ci-stagger-tight) * 3) backwards; }
	html.ci-anim .ci-form__done > :last-child { animation-delay: calc(var(--ci-stagger-tight) * 5); }
}
/* The #enquiry fragment has a sticky header to clear.
   app.js scrolls to the form itself and can compensate; the no-script path
   gets a native fragment jump from the success redirect, which cannot. Both the
   form and the card it is replaced by carry the same offset the rest of the
   theme's anchor targets use, so the confirmation does not arrive underneath
   the header. */
.ci-form,
.ci-form__done { scroll-margin-top: calc(var(--ci-header-h) + 4.5rem); }

.ci-form__done {
	--sf-bg: var(--ci-success-bg);
	--sf-bd: var(--ci-success-line);
	--sf-r: var(--ci-r-lg);
	display: grid;
	gap: .75rem;
	justify-items: start;
}
.ci-form__done h3 { margin: 0; font-size: var(--ci-fs-h3); color: var(--ci-success-fg); }
.ci-form__done p { margin: 0; color: var(--ci-text-2); }

/* Search */
.ci-search { position: relative; display: flex; gap: .5rem; }
.ci-search__icon { position: absolute; left: .9rem; top: 50%; transform: translateY(-50%); color: var(--ci-text-3); pointer-events: none; }
.ci-search__input { padding-left: 2.7rem; background: var(--ci-field-bg-focus); }
.ci-search__submit {
	flex: none;
	padding: .8rem 1.3rem;
	border: 0;
	border-radius: var(--ci-r-sm);
	background: var(--ci-brand-600);
	color: var(--ci-on-brand);
	font-weight: 600;
	font-size: .9375rem;
	cursor: pointer;
	transition: background-color var(--ci-dur-fast) var(--ci-ease);
}
.ci-search__submit:hover { background: var(--ci-btn-fill-hover); }

.ci-map iframe { display: block; border: 0; filter: var(--ci-map-filter); }

/* 10. Footer
   ========================================================================== */

.ci-footer {
	background: linear-gradient(180deg, var(--ci-ink-800), var(--ci-ink-900));
	color: var(--ci-on-dark-2);
	padding-block: clamp(3rem, 2rem + 3.5vw, 4.5rem) 0;
	font-size: .9375rem;
}
.ci-footer__top {
	display: grid;
	/* 2, not 1.7: the IT Services column now runs two sub-columns (below), and
	   halving its height is only worth having if the links still fit on one or
	   two lines each.
	   The brand column does pay for it, which an earlier comment here denied:
	   measured at 1440 its track goes from 406px to 371px, and its pitch (capped
	   at 42ch = 397px) and search (22rem = 352px) were NOT already short of it --
	   the pitch now wraps inside 371px instead of reaching its cap. That costs the
	   pitch a line in a column whose natural height is 284px against 370px of
	   links beside it, so it costs the footer nothing. .95fr/2.1fr was tried first
	   and took the brand to 342px, which squeezed the search below its cap too for
	   no gain in the list. */
	grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
	gap: clamp(2rem, 1.2rem + 3vw, 4rem);
	padding-bottom: clamp(2.5rem, 2rem + 2vw, 3.5rem);
}
@media (max-width: 900px) { .ci-footer__top { grid-template-columns: 1fr; } }
.ci-footer__logo img { max-height: 44px; width: auto; }
.ci-footer__pitch { margin: 1.25rem 0; max-width: 42ch; color: var(--ci-on-dark-3); line-height: 1.6; }
.ci-social {
	display: inline-grid;
	place-items: center;
	width: var(--ci-chip-sm);
	height: var(--ci-chip-sm);
	border-radius: var(--ci-r-sm);
	border: 1px solid var(--ci-on-dark-line);
	color: var(--ci-on-dark-2);
	transition: background-color var(--ci-dur-fast) var(--ci-ease), color var(--ci-dur-fast) var(--ci-ease), border-color var(--ci-dur-fast) var(--ci-ease);
}
.ci-social:hover { background: var(--ci-brand-600); color: #fff; border-color: var(--ci-brand-600); }

/* Explicit tracks rather than auto-fit, because the three columns are not
   interchangeable: IT Services carries ten links under four headings, Company
   five, Contact three lines. Equal thirds gave the longest column the same
   width as the shortest and stacked all four service groups vertically -- 580
   pixels of footer that the brand column spent stretched alongside, empty. */
.ci-footer__cols {
	display: grid;
	grid-template-columns: minmax(0, 2.2fr) minmax(0, .85fr) minmax(0, 1.1fr);
	gap: 2rem;
}
/* Below the point where three unequal tracks stop being readable, IT Services
   takes a full row of its own and the other two share the next one.
   Two tracks, not auto-fit's three: auto-fit only collapses tracks that hold
   no items, and `grid-column: 1 / -1` puts an item in every one of them, so
   the count is pinned by whatever auto-fit decided and nothing collapses.
   Measured at 780, 760 and 700 that produced three 190-216px tracks with
   Company and Contact in the first two and a void in the third -- a ragged row
   that v1.9.0, where all three tracks held a column, did not have. */
@media (max-width: 780px) {
	.ci-footer__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.ci-footer__col--it { grid-column: 1 / -1; }
}
/* ...and one track below the width where two stop being columns.
   445px is not a round number picked for looks. v1.9.0's
   `auto-fit, minmax(190px, 1fr)` was measured flipping to a single track
   somewhere between 450px (two tracks) and 440px (one), so a breakpoint inside
   that interval gives this row the same column count as the previous release at
   every width either side of it.
   Without it the explicit two carried on down, and the phone footer paid for
   its 165px of saved height with 161px columns at 390 and 146px at 360 -- an
   address wrapping every word or two, which is not what "make the footer
   shorter" asked for. */
@media (max-width: 445px) {
	.ci-footer__cols { grid-template-columns: minmax(0, 1fr); }
}

/* There is deliberately NO sub-column rule for this band.
   One was written -- `repeat(auto-fit, minmax(150px, 1fr))`, on the reasoning
   that a column with a whole row to itself should use it -- and it was dead on
   arrival: same selector and same specificity as the unconditional rule below,
   which sits later in the file, and a media query adds no specificity. It never
   applied at any width.
   Measuring what it WOULD have done turned out to be the more useful answer. It
   is not better; it is mostly worse. Footer height, two explicit columns versus
   auto-fit, at the same widths:

       780px   1084  vs  1015  (auto-fit takes 4 columns)
       760px   1082  vs  1013  (4)
       700px   1077  vs  1146  (3)
       640px   1043  vs  1112  (3)
       600px   1070  vs  1114  (3)
       520px and below: identical, auto-fit resolves to 2 anyway

   Three columns wrap worse than two for these link labels and leave a ragged
   second row, so auto-fit pays 69px MORE from 600 to 700 to save 69px in the
   40px band from 740 to 780. Two columns everywhere is both shorter overall and
   one rule instead of two. */
.ci-footer__heading {
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: #fff;
	margin: 0 0 1.1rem;
}
.ci-footer__list { margin: 0; padding: 0; list-style: none; display: grid; gap: .6rem; }
/* Keyed on the structure that is actually present, not on a class the
   template adds in hope. A site upgrading with an existing FLAT footer menu
   keeps the flat spacing until the provisioner rebuilds the menu; the class
   remains for the hand-built fallback path, which always emits groups. */
.ci-footer__list--grouped,
.ci-footer__list:has(> li > ul) { gap: 1.25rem; }
/* The one column that is long enough to be worth splitting. Two sub-columns,
   stated explicitly rather than left to auto-fit: inside a fixed share of a row
   auto-fit has a cliff, and it falls off it silently. Measured on this build at
   1024, the 2.2fr track is 294px and two 150px columns plus their 24px gap want
   324 -- thirty pixels short, so auto-fit returned ONE column and the list went
   back to 442px tall with nothing in the CSS to say why. An explicit two has no
   cliff: past that point the sub-columns simply narrow and a long service name
   takes a third line, which cost 420px against the cliff's 442px at that width.
   Column gap only: the row gap belongs to the grouped rule above, and
   restating it here would quietly re-tighten the flat-menu path. */
.ci-footer__col--it .ci-footer__list {
	grid-template-columns: repeat(2, minmax(0, 1fr));
	column-gap: 1.5rem;
	align-items: start;
}
/* Both sublist classes: .ci-sub is CI_Nav_Walker's, .sub-menu is the default
   walker's, and this column is rendered by whichever path the site uses. */
.ci-footer__list .ci-sub,
.ci-footer__list .sub-menu {
	display: grid;
	gap: .5rem;
	margin: 0;
	padding: 0;
	list-style: none;
}
/* Both paths render a real <h3>: the fallback writes one directly, and
   ci_menu_heading_items() converts the provisioned menu's '#' placeholder
   into one, so neither leaves a focusable dead link in the tab order. */
.ci-footer__subheading {
	display: block;
	margin: 0 0 .5rem;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--ci-on-dark-3);
}
.ci-footer__list a {
	color: var(--ci-on-dark-2);
	text-decoration: none;
	font-size: .9375rem;
	transition: color var(--ci-dur-fast) var(--ci-ease);
}
.ci-footer__list a:hover { color: var(--ci-accent); }

.ci-footer__contact { margin: 0; padding: 0; list-style: none; display: grid; gap: 1rem; }
/* min-width: 0 on the text half, because a flex item's automatic minimum size
   is its min-content width and refuses to shrink past it. With the contact
   column on a .1fr share it is the narrowest in the footer, and between 901 and
   980px the address was measured breaching its own column by up to 14.6px --
   into the 2rem gutter, so nothing collided and no page scrollbar appeared,
   which is exactly why a horizontal-overflow check does not catch it. */
.ci-footer__contact li { display: flex; gap: .7rem; align-items: flex-start; }
.ci-footer__contact li > :not(.ci-icon) { min-width: 0; }
.ci-footer__contact .ci-icon { margin-top: .2em; color: var(--ci-accent); flex: none; }
.ci-footer__contact a { color: var(--ci-on-dark-2); text-decoration: none; overflow-wrap: anywhere; }
.ci-footer__contact a:hover { color: var(--ci-accent); }
/* overflow-wrap, not just min-width: `anywhere` is the one value that also
   reduces the element's MIN-CONTENT size, so the address can follow its column
   down instead of standing at the width of its longest word. The sibling rule
   on `a` above has carried it since v1.5 for the same reason. */
.ci-footer__contact address { font-style: normal; color: var(--ci-on-dark-3); line-height: 1.55; overflow-wrap: anywhere; }
/* One number per line, sharing a left edge. See the note in footer.php: the
   inline "a | b" pair broke after the separator in this column's width and set
   the two numbers four pixels apart. justify-items keeps each link's hit area
   to its own text rather than the full column. */
.ci-footer__phones { display: grid; gap: .2rem; justify-items: start; }

.ci-footer__widgets { padding-block: 2rem; border-top: 1px solid var(--ci-on-dark-line); }

.ci-footer__bar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 1rem;
	padding-block: 1.5rem;
	border-top: 1px solid var(--ci-on-dark-line);
	font-size: .8125rem;
	color: var(--ci-on-dark-3);
}
.ci-footer__copy { margin: 0; }
.ci-footer__legal { display: flex; gap: 1.25rem; margin: 0 0 0 auto; padding: 0; list-style: none; }
.ci-footer__legal a { color: var(--ci-on-dark-3); text-decoration: none; }
.ci-footer__legal a:hover { color: var(--ci-accent); }
.ci-totop {
	display: grid;
	place-items: center;
	width: var(--ci-chip-sm);
	height: var(--ci-chip-sm);
	border-radius: var(--ci-r-sm);
	border: 1px solid var(--ci-on-dark-line);
	background: none;
	color: var(--ci-on-dark-2);
	cursor: pointer;
	/* It is an <a> now rather than a <button>, so it inherits the footer's
	   link styling unless told otherwise. Its only content is an icon, so an
	   underline would not paint anything today -- but the rule is here so that
	   stays true if a label is ever added beside the glyph. */
	text-decoration: none;
	transition: background-color var(--ci-dur-fast) var(--ci-ease), color var(--ci-dur-fast) var(--ci-ease), transform var(--ci-dur-fast) var(--ci-ease);
}
.ci-totop:hover { background: var(--ci-brand-600); color: #fff; transform: translateY(-2px); }

/* 11. Blog & utilities
   ========================================================================== */

.ci-post { display: flex; }
.ci-post__link {
	--sf-r: var(--ci-r-lg);
	display: flex;
	flex-direction: column;
	width: 100%;
	text-decoration: none;
	color: inherit;
}
.ci-post__link:hover { transform: translateY(-4px); box-shadow: var(--ci-sh-2); }
.ci-post__link:hover .ci-card__more .ci-icon { transform: translateX(var(--ci-nudge)); }
.ci-post__media { display: block; aspect-ratio: 3 / 2; overflow: hidden; }
.ci-post__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--ci-m-lg) var(--ci-ease-out); }
.ci-post__link:hover .ci-post__media img { transform: scale(1.05); }
.ci-post__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .6rem;
	padding: 1.25rem 1.35rem 0;
	font-size: .75rem;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--ci-text-3);
}
.ci-post__cat { color: var(--ci-brand-fg); font-weight: 700; }
.ci-post__title { font-size: var(--ci-fs-h3); margin: .6rem 1.35rem .5rem; }
.ci-post__excerpt { margin: 0 1.35rem; font-size: .9375rem; color: var(--ci-text-2); }
.ci-post .ci-card__more { margin: 0 1.35rem 1.35rem; padding-top: 1rem; }

.ci-article__meta { display: flex; gap: .6rem; font-size: .875rem; color: var(--ci-on-dark-2); margin: 1rem 0 0; }

.ci-postnav { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--ci-gap); margin-top: 3rem; }
.ci-postnav__item {
	/* No fill of its own today, and it should not gain one. */
	--sf-bg: none;
	display: grid;
	gap: .25rem;
	text-decoration: none;
	color: inherit;
}
/* No border-color here: a.ci-surface:hover (0,2,1) outranks this (0,2,0), so
   it never applied. One hover border across the site is the point. */
.ci-postnav__item:hover { background: var(--ci-brand-050); }
.ci-postnav__item span { font-size: var(--ci-fs-eyebrow); font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--ci-text-3); }
.ci-postnav__item strong { font-family: var(--ci-font-display); font-weight: 600; }
.ci-postnav__item--next { text-align: right; }

.ci-pagination { margin-top: 3rem; }
.ci-pagination .nav-links { display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center; }
.ci-pagination .page-numbers {
	display: grid;
	place-items: center;
	min-width: 42px;
	height: 42px;
	padding-inline: .7rem;
	border: 1px solid var(--ci-line);
	border-radius: var(--ci-r-sm);
	text-decoration: none;
	color: var(--ci-text-2);
	font-weight: 500;
	transition:
		background-color var(--ci-dur-fast) var(--ci-ease),
		border-color var(--ci-dur-fast) var(--ci-ease),
		color var(--ci-dur-fast) var(--ci-ease);
}
/* a. only: .page-numbers is also emitted on the ellipsis <span> and on the
   current page, neither of which goes anywhere. */
.ci-pagination a.page-numbers:hover,
.ci-pagination .page-numbers.current {
	background: var(--ci-brand-600);
	border-color: var(--ci-brand-600);
	color: var(--ci-on-brand);
}

.ci-empty { max-width: 560px; margin-inline: auto; text-align: center; }
.ci-empty .ci-search { margin-top: 1.5rem; }

.ci-footer__widgets {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 2rem;
}
.ci-widget { margin: 0; }
.ci-widget__title,
.ci-widget h2,
.ci-widget h3 {
	color: #fff;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	margin: 0 0 1rem;
}
.ci-widget ul,
.ci-widget ol { margin: 0; padding: 0; list-style: none; display: grid; gap: .55rem; }
.ci-widget li { font-size: .9375rem; color: var(--ci-on-dark-3); }
.ci-widget a { color: var(--ci-on-dark-2); text-decoration: none; }
.ci-widget a:hover { color: var(--ci-accent); }
.ci-widget p { color: var(--ci-on-dark-3); font-size: .9375rem; }
.ci-widget select,
.ci-widget input[type="search"],
.ci-widget input[type="text"] {
	width: 100%;
	padding: .6rem .8rem;
	color: var(--ci-on-dark);
	background: rgba(255, 255, 255, .06);
	border: 1px solid var(--ci-on-dark-line);
	border-radius: var(--ci-r-sm);
}
.ci-widget .ci-search__input { color: var(--ci-on-dark); background: rgba(255, 255, 255, .06); border-color: var(--ci-on-dark-line); }
.ci-widget .ci-search__icon { color: var(--ci-on-dark-3); }
.ci-widget input::placeholder { color: var(--ci-on-dark-3); opacity: 1; }

.ci-pagelinks { margin-top: 2rem; display: flex; gap: .5rem; font-size: .875rem; }

/* 12. Page sections — glance band, expanding panels, tabs, quote
   ========================================================================== */

/* ---- "At a glance" band, straddling the foot of the hero ---------------- */

.ci-glance {
	position: relative;
	z-index: 3;
	margin-top: calc(var(--ci-glance-lift) * -1);
	margin-bottom: clamp(2rem, 1.4rem + 2vw, 3.5rem);
}

.ci-glance__list {
	/* The hairlines between cells are THIS background showing through
	   gap: 1px, with each child painting --ci-raised over it. Let --sf-bg
	   fall back to its default and every divider silently disappears. */
	--sf-bg: var(--ci-elev-bd);
	--sf-r: var(--ci-r-lg);
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
	gap: 1px;
	margin: 0;
	list-style: none;
	overflow: hidden;
	box-shadow: var(--ci-sh-2);
}
.ci-glance__item {
	--sf-bw: 0;
	/* No border and no radius today, and both must stay off, or the
	   parent's 1px divider gaps double up. */
	--sf-pad: 1.5rem 1.4rem;
	--sf-bd: transparent;
	--sf-r: 0;
	display: flex;
	flex-direction: column;
	gap: .35rem;
}
.ci-glance__value {
	font-family: var(--ci-font-display);
	font-size: clamp(1.6rem, 1.3rem + 1.2vw, 2.25rem);
	font-weight: 700;
	line-height: 1;
	letter-spacing: -.025em;
	color: var(--ci-brand-fg);
}
/* The phrase variant: same family and weight, a step down in size and with
   normal tracking, so it reads as a label rather than as a figure. */
.ci-glance__value--text {
	font-size: var(--ci-fs-h3);
	letter-spacing: -.01em;
	line-height: 1.2;
	color: var(--ci-text);
}
.ci-glance__label {
	font-size: .875rem;
	line-height: 1.45;
	color: var(--ci-text-2);
}

@media (max-width: 720px) {
	:root { --ci-glance-lift: 28px; }  /* intentionally on :root, same as the base */
	.ci-glance__item { padding: 1.15rem 1.2rem; }
}

/* ---- Expanding panel row ------------------------------------------------ */

.ci-panels {
	--ci-panels-collapsed: 84px;
	--ci-panels-n: 4;
	display: flex;
	gap: 10px;
	min-height: 380px;
}
.ci-panels__item {
	position: relative;
	display: flex;
	flex-direction: column;
	width: var(--ci-panels-collapsed);
	overflow: hidden;
	border-radius: var(--ci-r-lg);
	border: 1px solid var(--ci-elev-bd);
	background:
		radial-gradient(120% 90% at 85% 0%, color-mix(in srgb, var(--ci-brand) 24%, transparent), transparent 70%),
		linear-gradient(165deg, var(--ci-ink-700), var(--ci-ink-900));
	color: var(--ci-on-dark);
	transition: width var(--ci-m-md) var(--ci-ease-out);
}
/* The open panel takes whatever the collapsed ones leave behind. */
.ci-panels__item.is-open {
	width: calc(100% - (var(--ci-panels-n) - 1) * (var(--ci-panels-collapsed) + 10px));
}

.ci-panels__btn {
	display: flex;
	align-items: center;
	gap: .9rem;
	width: 100%;
	padding: 1.4rem;
	border: 0;
	background: none;
	color: inherit;
	text-align: left;
	cursor: pointer;
	flex: none;
}

.ci-panels__num {
	font-family: var(--ci-font-display);
	font-size: .8125rem;
	font-weight: 700;
	letter-spacing: .08em;
	color: var(--ci-accent);
	flex: none;
}
.ci-panels__label {
	font-family: var(--ci-font-display);
	font-size: 1.0625rem;
	font-weight: 600;
	letter-spacing: -.015em;
	color: #fff;
	white-space: nowrap;
}
.ci-panels__chev {
	margin-left: auto;
	flex: none;
	color: var(--ci-on-dark-3);
	transition: transform var(--ci-dur) var(--ci-ease);
}
.ci-panels__item.is-open .ci-panels__chev { transform: rotate(90deg); }

.ci-panels__body {
	flex: 1;
	min-height: 0;
	overflow: hidden;
}
/* Gated, like everything else that starts hidden. Left on the base rule this
   was the whole bug in miniature: the :not(.is-open) override below was moved
   under html.ci-anim, but the opacity: 0 it was overriding stayed where it
   was -- so with no script three of the four bodies were still painted at
   zero. In the accessibility tree, which display:none at least was not, but
   invisible all the same. */
html.ci-anim .ci-panels__body {
	opacity: 0;
	transition: opacity var(--ci-dur) var(--ci-ease);
}
.ci-panels__item.is-open .ci-panels__body { opacity: 1; transition-delay: var(--ci-dur-fast); }
/* No max-width here any more. 46ch used to live on this rule and is now dead
   in all three states: above 860px with the gate up, `width: min(46ch, ...)`
   below is by construction never larger; and both of the other branches
   (html:not(.ci-anim) on desktop, and the accordion under 860px) set
   `max-width: none` outright. A declaration that cannot change anything at any
   width is a trap for whoever edits it next, so the measure lives in exactly
   one place. */
.ci-panels__inner { padding: 0 1.4rem 1.6rem; }
.ci-panels__lead {
	font-size: var(--ci-fs-lead);
	color: #fff;
	margin-bottom: 1.1rem;
}
.ci-panels__points { margin: 0; padding: 0; list-style: none; display: grid; gap: .6rem; }
.ci-panels__points li {
	display: flex;
	gap: .6rem;
	align-items: flex-start;
	font-size: .9375rem;
	color: var(--ci-on-dark-2);
}
.ci-panels__points .ci-icon { margin-top: .2em; color: var(--ci-accent); flex: none; }

/* Collapsed panels turn their label on its side, which is what buys the
   horizontal layout its compactness. */
@media not all and (max-width: 860px) {
	.ci-panels__item:not(.is-open) .ci-panels__btn {
		flex-direction: column;
		align-items: center;
		height: 100%;
		gap: 1.1rem;
		padding: 1.4rem .6rem;
	}
	.ci-panels__item:not(.is-open) .ci-panels__label {
		writing-mode: vertical-rl;
		transform: rotate(180deg);
		margin-bottom: auto;
	}
	.ci-panels__item:not(.is-open) .ci-panels__chev { margin: 0; transform: rotate(-90deg); }

	/* NOT display:none, for two separate reasons.

	   Motion: display is not interpolable, so the body's carefully written
	   opacity fade never ran in either direction on desktop. Closing a panel
	   deleted its lead and its bullets in the frame of the click, and the box
	   then spent 420ms shrinking as an empty rectangle.

	   Content: display:none is also gated on nothing, so with app.js absent
	   three of the four panels' copy was not rendered, not in the
	   accessibility tree, and not reachable by any means. visibility under
	   html.ci-anim keeps both honest -- it leaves the accessibility tree the
	   way display:none did while the component is live, and resolves to
	   visible when nothing claims the gate. */
	html.ci-anim .ci-panels__item:not(.is-open) .ci-panels__body {
		opacity: 0;
		visibility: hidden;
		/* ...and it is sized as though it were empty, which is what the row's
		   height was actually made of.

		   visibility, unlike display, still LAYS THE TEXT OUT. A collapsed
		   panel is an 84px sliver, so each of the three hidden bodies wrapped
		   its lead and its three bullets into a column about 39px wide and ran
		   several hundred pixels tall -- and a flex item's max-content
		   contribution reaches its container, so the tallest of the three set
		   the height of the whole row. Measured at 1440: 1270px, for a
		   component whose open panel needs 325px and whose declared min-height
		   is 380px. The section was three and a half times its designed
		   height, and every pixel of the excess was invisible text.

		   `contain: size` is the one tool that fixes this without moving
		   anything: the box is sized as if it had no contents, so it stops
		   feeding the row, while staying exactly where it is in flow. Taking
		   the body out of flow instead would have jumped it under the button
		   for the length of the closing fade. Where size containment is not
		   supported the row is merely tall again, which is where it started. */
		contain: size;
		transition:
			opacity var(--ci-dur-fast) var(--ci-ease),
			visibility 0s linear var(--ci-dur-fast);
	}

	/* And the text column is pinned to a width that does not depend on the box
	   it is in -- for every panel, open or closed, which is the half of this
	   that is easy to get wrong.

	   Containment alone fixes the resting height and leaves a 420ms flicker
	   behind it: the panel that is OPENING is no longer size-contained, and for
	   the first ~70ms of the widening it is still narrow enough to wrap its
	   lead into a column several hundred pixels tall. The row would spike to
	   ~970px and fall back to 380px on every click, while the text that caused
	   it was still at opacity 0.

	   So the measure answers to the viewport and never to the container, which
	   makes it identical collapsed, mid-transition and open: nothing reflows and
	   nothing spikes.

	   `32vw` was the first form of that and it overshot. It is container-blind,
	   which was the point, but it is also narrower than 46ch at every width
	   below ~1450 -- so it quietly took the open panel's text column from 464px
	   down to 328px at 1024 and 276px at 861, a third narrower than v1.9.0 for
	   no reason the reader can see, and the extra lines pushed the row from
	   383px to 460px. Subtracting the chrome instead keeps the full measure
	   wherever it fits: 430px covers the three collapsed slivers, their gaps,
	   the wrap's gutters and a scrollbar, so `100vw - 430px` only binds below
	   ~894px, where the open panel genuinely cannot hold 46ch -- which is also
	   the case a large root font size creates, and the one where 46ch alone
	   would overflow a box with overflow: hidden and silently clip the copy.
	   Measured: 464px of measure and a 383px row at every desktop width from
	   861 to 1920. */
	html.ci-anim .ci-panels__inner { width: min(46ch, 100vw - 430px); }
}

/* ...and without the script, so is the desktop.
 *
 * Gating the collapse was only half the no-JS story for this component. The
 * horizontal layout IS the collapse: a panel that is not open is an 84px
 * sliver with overflow: hidden, so its lead and its bullets are clipped to
 * nothing by the geometry whatever the opacity says. Three quarters of the
 * section was still unreadable.
 *
 * So when nothing claims the gate, take the layout the component already falls
 * back to one breakpoint down — the vertical stack — at every width. Every
 * panel is then open and readable, which is what the markup ships. */
@media not all and (max-width: 860px) {
	html:not(.ci-anim) .ci-panels { flex-direction: column; min-height: 0; }
	html:not(.ci-anim) .ci-panels__item,
	html:not(.ci-anim) .ci-panels__item.is-open { width: 100%; }
	html:not(.ci-anim) .ci-panels__item:not(.is-open) .ci-panels__btn {
		flex-direction: row;
		align-items: center;
		height: auto;
		gap: .9rem;
		padding: 1.4rem;
	}
	html:not(.ci-anim) .ci-panels__item:not(.is-open) .ci-panels__label {
		writing-mode: horizontal-tb;
		transform: none;
		margin-bottom: 0;
	}
	html:not(.ci-anim) .ci-panels__item:not(.is-open) .ci-panels__chev { margin-left: auto; transform: rotate(90deg); }
	html:not(.ci-anim) .ci-panels__inner { max-width: none; }
}

/* Below the breakpoint it is just a vertical accordion.
   The desktop branches above are `not all and (max-width: 860px)` rather than
   `min-width: 861px`, so the two meet exactly. A min/max pair one pixel apart
   leaves every FRACTIONAL width between them matching neither -- which a
   Windows machine at 125% scaling, or an intermediate zoom step, lands on, and
   the component then has no collapse rule at all: four 84px columns with
   unrotated labels clipped mid-word. */
@media (max-width: 860px) {
	.ci-panels { flex-direction: column; min-height: 0; }
	.ci-panels__item,
	.ci-panels__item.is-open { width: 100%; }
	.ci-panels__body { display: grid; grid-template-rows: 1fr; transition: grid-template-rows var(--ci-m-md) var(--ci-ease-out); }
	/* The fade is a DESKTOP affordance -- there, the box spends 420ms widening
	   and the text has to wait for somewhere to land. Down here the row simply
	   opens, so the collapse is grid rows alone. Written at the same
	   specificity as the gated rule above and later in the file, because a
	   plain `opacity: 1` here loses to it and would be a declaration that
	   reads like it governs this breakpoint and does not. */
	html.ci-anim .ci-panels__body { opacity: 1; }
	html.ci-anim .ci-panels__item:not(.is-open) .ci-panels__body { grid-template-rows: 0fr; }
	/* The open delay belongs to the desktop layout, where the box has to
	   finish widening before its text can land in it. Inherited down here it
	   was 180ms of nothing after a tap, with the chevron already turned --
	   580ms from tap to open, and no delay at all on the way back. */
	.ci-panels__item.is-open .ci-panels__body { grid-template-rows: 1fr; transition-delay: 0s; }

	/* The mobile branch collapses with grid rows rather than visibility, so it
	   needs the accessibility half stated separately: without it a screen
	   reader reads all four leads and all twelve bullets while aria-expanded
	   says three of them are closed. */
	html.ci-anim .ci-panels__item:not(.is-open) .ci-panels__inner {
		visibility: hidden;
		transition: visibility 0s linear var(--ci-m-md);
	}
	.ci-panels__inner { min-height: 0; max-width: none; }
	.ci-panels__chev { transform: rotate(90deg); }
	.ci-panels__item.is-open .ci-panels__chev { transform: rotate(-90deg); }
}

/* ---- Tabs ---------------------------------------------------------------- */

.ci-tabs__list-wrap {
	margin-bottom: clamp(1.75rem, 1.2rem + 2vw, 2.75rem);
	border-bottom: 1px solid var(--ci-line);
	overflow-x: auto;
	scrollbar-width: none;
}
.ci-tabs__list-wrap::-webkit-scrollbar { display: none; }
.ci-tabs__list { display: flex; gap: .25rem; min-width: max-content; }

.ci-tabs__tab {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	padding: .85rem 1.1rem;
	border: 0;
	border-bottom: 2px solid transparent;
	background: none;
	font-size: .9375rem;
	font-weight: 500;
	white-space: nowrap;
	color: var(--ci-text-2);
	cursor: pointer;
	transition: color var(--ci-dur) var(--ci-ease), border-color var(--ci-dur) var(--ci-ease), background-color var(--ci-dur) var(--ci-ease);
}
.ci-tabs__tab:hover { color: var(--ci-brand-fg); background: var(--ci-brand-050); }
.ci-tabs__tab[aria-selected="true"] {
	color: var(--ci-brand-fg);
	border-bottom-color: var(--ci-brand-600);
	font-weight: 600;
}
.ci-tabs__tab .ci-icon { color: currentColor; }

.ci-tabs__panel[hidden] { display: none; }

/* An arrow key moves focus, and the browser then scrolls the focused tab
   flush to the top of the scrollport -- which puts it under the 76px sticky
   header, and under the section rail as well on a service page. */
.ci-tabs__tab { scroll-margin-top: calc(var(--ci-header-h) + 4.5rem); }

/* One indicator that travels, instead of twelve that repaint.
 *
 * A pseudo-element rather than a span, because .ci-tabs__list is the
 * role="tablist" and a tablist's children are supposed to be tabs.
 * app.js writes --tab-x and --tab-w from the selected tab's own box and adds
 * .has-ink, so with no script the per-tab border below is still the indicator
 * and nothing here applies. */
.ci-tabs__list { position: relative; }
.ci-tabs__list.has-ink::after {
	content: "";
	position: absolute;
	left: 0;
	bottom: 0;
	height: 2px;
	width: var(--tab-w, 0);
	/* --ci-brand-fg, not --ci-brand-600: the foreground variant is the one
	   that lightens in dark mode. Identical in light, and a 2px indicator on a
	   dark band is a non-text UI component that still has to clear 3:1. */
	background: var(--ci-brand-fg);
	transform: translate3d(var(--tab-x, 0px), 0, 0);
	transition:
		transform var(--ci-dur) var(--ci-ease-out),
		width var(--ci-dur) var(--ci-ease-out);
	pointer-events: none;
}
/* With the bar doing the work, the per-tab border would be a second, static
   indicator sitting under the travelling one. */
.ci-tabs__list.has-ink .ci-tabs__tab[aria-selected="true"] { border-bottom-color: transparent; }

/* The folder variant marks selection by filling the tab into the panel below
   it, so a travelling underline would be a second answer to a question the
   shape has already answered. */
.ci-folder .ci-tabs__list.has-ink::after { display: none; }

@media (forced-colors: active) {
	/* The bar is a background and disappears in forced colours, so a border
	   has to come back or there is no selected state at all.

	   The border is restored WHOLE, not just its colour: the folder variant
	   sets `border: 0`, so restoring a colour alone left that tab set with no
	   selected state whatsoever -- its filled background is a background too,
	   and it disappears for the same reason. */
	.ci-tabs__list.has-ink::after { display: none; }
	.ci-tabs__list .ci-tabs__tab[aria-selected="true"],
	.ci-folder .ci-tabs__tab[aria-selected="true"] { border-bottom: 2px solid currentColor; }
}

/* The panel arrives instead of being swapped.
 *
 * A transition cannot do this: the outgoing panel goes to display:none in the
 * same frame, and display is not interpolable. An animation can, because a
 * CSS animation RESTARTS when an element goes from display:none to displayed
 * -- so the incoming panel plays this every time it is selected, and on first
 * paint for the one that starts open. */
@media (prefers-reduced-motion: no-preference) {
	html.ci-anim .ci-tabs__panel {
		animation: ci-tab-in var(--ci-m-md) var(--ci-enter) backwards;
	}
}
@keyframes ci-tab-in {
	from { opacity: 0; transform: translate3d(0, 7px, 0); }
}

/* The industry panel: description beside the services that usually open it. */
.ci-industry {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: clamp(1.75rem, 1.2rem + 3vw, 3.5rem);
	align-items: start;
}
@media (max-width: 860px) { .ci-industry { grid-template-columns: minmax(0, 1fr); } }
.ci-industry__icon {
	margin-bottom: 1.1rem;
}
.ci-industry__title { font-size: var(--ci-fs-h2); margin-bottom: .5rem; }
.ci-industry__text { font-size: var(--ci-fs-lead); color: var(--ci-text-2); }
.ci-industry__aside-title {
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--ci-text-3);
	margin-bottom: 1rem;
}
.ci-industry__links { display: grid; gap: .5rem; margin: 0; padding: 0; list-style: none; }
.ci-industry__link {
	gap: .75rem;
	color: var(--ci-text);
	font-weight: 500;
	font-size: .9375rem;
}
/* The component used to slide sideways. A row that moves breaks the stack it
   sits in, so the nudge moves to the arrow, where it reads as direction
   rather than as the whole row coming loose. */

.ci-industry__link:hover .ci-industry__link-arrow {
	transform: translateX(var(--ci-nudge));
	color: var(--ci-brand-fg);
}
.ci-industry__link .ci-icon:first-child { color: var(--ci-brand-fg); flex: none; }
.ci-industry__link-arrow {
	margin-left: auto;
	color: var(--ci-text-3);
	flex: none;
	transition: transform var(--ci-dur) var(--ci-ease-out), color var(--ci-dur) var(--ci-ease);
}

/* ---- Media service card -------------------------------------------------- */

.ci-card--media {
	padding: 0;
	overflow: hidden;
}
.ci-card__media {
	position: relative;
	display: block;
	aspect-ratio: 16 / 10;
	overflow: hidden;
	background: linear-gradient(165deg, var(--ci-ink-700), var(--ci-ink-900));
}
.ci-card--media-fallback .ci-card__media {
	background:
		radial-gradient(90% 80% at 88% 12%, color-mix(in srgb, var(--ci-accent) 20%, transparent), transparent 68%),
		radial-gradient(70% 70% at 6% 95%, color-mix(in srgb, var(--ci-brand) 55%, transparent), transparent 70%),
		linear-gradient(165deg, var(--ci-ink-700), var(--ci-ink-900));
}
.ci-card__img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	filter: var(--ci-media-filter);
	transition: transform var(--ci-m-lg) var(--ci-ease-out);
}
a.ci-card--media:hover .ci-card__img { transform: scale(1.055); }

/* No featured image: a generated brand panel so the grid stays consistent. */
.ci-card__pattern {
	position: absolute;
	inset: 0;
	background-image:
		linear-gradient(var(--ci-decor-grid) 1px, transparent 1px),
		linear-gradient(90deg, var(--ci-decor-grid) 1px, transparent 1px);
	background-size: 34px 34px;
	mask-image: radial-gradient(130% 90% at 80% 10%, #000 10%, transparent 75%);
	-webkit-mask-image: radial-gradient(130% 90% at 80% 10%, #000 10%, transparent 75%);
}
.ci-card__glyph {
	position: absolute;
	right: -14px;
	top: 50%;
	transform: translateY(-58%);
	color: #fff;
	opacity: .3;
	transition: transform var(--ci-m-lg) var(--ci-ease-out), opacity var(--ci-m-md) var(--ci-ease);
}
a.ci-card--media:hover .ci-card__glyph { transform: translateY(-58%) scale(1.08); opacity: .4; }

/* Keeps the overlaid title legible on any photograph. */
.ci-card__scrim {
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgba(6, 12, 24, .92) 0%, rgba(6, 12, 24, .62) 38%, rgba(6, 12, 24, .12) 70%, transparent 100%);
}
.ci-card--media-fallback .ci-card__scrim {
	background: linear-gradient(to top, rgba(6, 12, 24, .78) 0%, rgba(6, 12, 24, .34) 45%, transparent 80%);
}
.ci-card__badge {
	position: absolute;
	top: 14px;
	left: 14px;
	backdrop-filter: blur(6px);
	-webkit-backdrop-filter: blur(6px);
}
.ci-card__title--over {
	position: absolute;
	left: 1.25rem;
	right: 1.25rem;
	bottom: 1rem;
	margin: 0;
	font-size: 1.125rem;
	line-height: 1.25;
	color: #fff;
	text-wrap: balance;
}

.ci-card__body {
	display: flex;
	flex-direction: column;
	flex: 1;
	padding: 1.25rem 1.35rem 1.35rem;
}
.ci-card--media .ci-card__text {
	display: block;
	font-size: .9375rem;
	color: var(--ci-text-2);
}
.ci-card--media .ci-card__more { padding-top: 1rem; }

@media (max-width: 520px) {
	.ci-card__media { aspect-ratio: 16 / 9; }
	.ci-card__title--over { font-size: 1.0625rem; }
}

/* ---- Pull quote ---------------------------------------------------------- */

.ci-quote {
	position: relative;
	max-width: 62ch;
	margin: 0 auto;
	padding: clamp(2rem, 1.4rem + 2.4vw, 3rem) clamp(1.5rem, 1rem + 2vw, 3rem);
	text-align: center;
}
.ci-quote__mark {
	display: block;
	font-family: var(--ci-font-display);
	font-size: 5rem;
	line-height: .6;
	color: var(--ci-brand-100);
	margin-bottom: .5rem;
}
.ci-quote__text { margin: 0; }
.ci-quote__text p {
	font-family: var(--ci-font-display);
	font-size: clamp(1.25rem, 1.05rem + .9vw, 1.75rem);
	font-weight: 500;
	line-height: 1.4;
	letter-spacing: -.015em;
	color: var(--ci-text);
	text-wrap: balance;
}
.ci-quote__by { margin-top: 1.5rem; display: grid; gap: .15rem; }
.ci-quote__name { font-weight: 600; color: var(--ci-text); }
.ci-quote__role { font-size: .875rem; color: var(--ci-text-3); }

/* 13. Figures, bands and the long-page kit
   The site had one section shape — wrap, head, grid of boxes — repeated five
   to ten times per page, and no imagery of any kind below the hero. Nine of
   the ten service pages were the same five bands with different words in them.
   Everything in this section exists to give a page a shape an eye can
   remember: a diagram that explains the service, a band that interrupts, a
   row that reads as a datasheet, and a rail that makes a long page navigable.
   ========================================================================== */

/* ---- Diagram figures ----------------------------------------------------- */

.ci-figure {
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: var(--ci-gap-sm);
}

.ci-figure__svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
	/* A diagram is read, not pressed: nothing inside it is a target. */
	pointer-events: none;
}

.ci-figure__caption {
	font-size: var(--ci-fs-sm);
	line-height: 1.6;
	color: var(--ci-text-3);
	max-width: 46ch;
}

/* The five primitives every figure is drawn from. Stroke width and label
   size are set once here rather than per shape, which is what keeps twelve
   separately-drawn diagrams looking like one set. */
.ci-figure .f-box {
	fill: none;
	stroke: var(--ci-fig-ink);
	stroke-width: var(--ci-fig-stroke);
}

.ci-figure .f-box--wash {
	fill: var(--ci-fig-wash);
	stroke: var(--ci-fig-line);
}

.ci-figure .f-box--accent {
	fill: var(--ci-fig-accent);
	stroke: var(--ci-fig-accent);
}

/* A containing boundary rather than a node: present, but never competing
   with the things inside it. */
.ci-figure .f-box--ghost {
	fill: none;
	stroke: var(--ci-fig-line);
	stroke-width: var(--ci-fig-stroke);
	stroke-dasharray: 5 5;
}

/* An open path drawn with the box stroke — the cylinder's body, which cannot
   be a rect. */
.ci-figure .f-box--open {
	fill: none;
	stroke: var(--ci-fig-ink);
	stroke-width: var(--ci-fig-stroke);
}

.ci-figure .f-link {
	fill: none;
	stroke: var(--ci-fig-ink);
	stroke-width: var(--ci-fig-stroke);
	stroke-linecap: round;
}

.ci-figure .f-link--accent { stroke: var(--ci-fig-accent); }
.ci-figure .f-link--dashed,
.ci-figure .f-link--dashed-accent { stroke: var(--ci-fig-line); stroke-dasharray: 4 5; }
.ci-figure .f-link--dashed-accent { stroke: var(--ci-fig-accent); }
.ci-figure .f-link--edge { stroke: var(--ci-fig-line); }

.ci-figure .f-head { fill: var(--ci-fig-ink); stroke: none; }
.ci-figure .f-head--accent { fill: var(--ci-fig-accent); }

.ci-figure .f-glyph {
	fill: none;
	stroke: var(--ci-fig-ink);
	stroke-width: calc(var(--ci-fig-stroke) + .2px);
	stroke-linecap: round;
	stroke-linejoin: round;
}

.ci-figure .f-glyph--accent { stroke: var(--ci-fig-accent); }
.ci-figure .f-glyph--on-accent { stroke: var(--ci-fig-on-accent); }

.ci-figure .f-dot { fill: var(--ci-fig-line); }

.ci-figure {
	/* In SVG user units, not CSS pixels: the figure is 440 units wide, so a
	   12-unit label renders at 12px only when the figure is drawn at 440px.
	   A slot narrower than that raises this rather than shrinking the type. */
	--fig-label: 12;
}

.ci-figure--tight { --fig-label: 15; }

/* Below these widths the figure is drawn at roughly 350-430px, where 12 units
   renders at 9-11px. The slot shrinks; the label must not. */
@media (max-width: 880px) { .ci-figure { --fig-label: 15; } .ci-figure--tight { --fig-label: 16; } }
@media (max-width: 520px) { .ci-figure { --fig-label: 17; } .ci-figure--tight { --fig-label: 18; } }

.ci-figure .f-label {
	font-family: var(--ci-font);
	font-size: calc(var(--fig-label) * 1px);
	font-weight: 600;
	letter-spacing: .005em;
	fill: var(--ci-fig-label);
}

.ci-figure .f-label--strong { fill: var(--ci-fig-label-strong); }
.ci-figure .f-label--quiet { font-size: calc((var(--fig-label) - 1) * 1px); font-weight: 500; }
.ci-figure .f-label--on-accent { fill: var(--ci-fig-on-accent); }

/* On a permanently dark band the figure cannot use the neutrals, which stay
   light there. One override block rather than a second set of figures. */
.ci-section--ink .ci-figure,
.ci-split .ci-figure,
.ci-hero .ci-figure,
.ci-closing .ci-figure {
	--ci-fig-ink: var(--ci-on-dark-2);
	--ci-fig-line: var(--ci-on-dark-line);
	--ci-fig-wash: var(--ci-on-dark-fill);
	--ci-fig-accent: var(--ci-accent);
	--ci-fig-on-accent: #06121f;
	--ci-fig-label: var(--ci-on-dark-3);
	--ci-fig-label-strong: var(--ci-on-dark);
}

/* The hero was missing from this list, so its caption kept --ci-text-3 and
   rendered at 2.86:1 on the hero gradient. --on-dark-2 rather than -3: a
   caption is running text, and -3 only just clears AA on these bands. */
.ci-section--ink .ci-figure__caption,
.ci-split .ci-figure__caption,
.ci-hero .ci-figure__caption,
.ci-closing .ci-figure__caption { color: var(--ci-on-dark-2); }

/* ---- Band ladder --------------------------------------------------------- */

/* A deliberate interruption. --alt is the quiet alternation that carries the
   page's rhythm; --tint is louder and is used two or three times at most,
   where a band has to stop the reader rather than continue them. */
.ci-section--tint {
	background: var(--ci-tint);
	border-block: 1px solid var(--ci-tint-line);
}

/* Two coloured bands that must touch. Without this they meet across two lots
   of --ci-section-y and read as unrelated. */
.ci-section--seam { padding-block: 0; }

/* Content stays at --ci-wrap; the band's own fill runs to the viewport edge.
   .ci-section already spans full width, so this only removes the inline
   padding the wrap would otherwise impose on a child that must bleed. */
.ci-section--bleed > .ci-wrap {
	max-width: none;
	padding-inline: 0;
}

/* ---- Half-colour split band ---------------------------------------------- */

/* A list inside a colour block, with the figure running to the viewport edge
   beside it. The colour block does the containing, so the rows need no cards.
   Derived from the device CtrlS uses for its datacentre list; the right half
   is a drawn figure here rather than a photograph, because a consultancy has
   no facility to photograph and may not publish a customer's. */
.ci-split {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: stretch;
	min-height: 420px;
}

.ci-split__copy {
	background: var(--ci-hero-bg);
	color: var(--ci-on-dark);
	padding: var(--ci-section-y) var(--ci-wrap-pad);
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--ci-gap);
}

/* The copy must start on the page's own content edge, not on the bleeding
   band's. Doing that with a viewport-unit padding gets it wrong twice: .ci-wrap
   is border-box, so its content edge is (width - --ci-wrap) / 2 PLUS
   --ci-wrap-pad, and 100vw counts the classic scrollbar that the page's own
   centring does not. Right-aligning a fixed-measure column inside the half
   lands on it exactly, with no viewport units at all:

     left edge = half - inner = W/2 - (wrap/2 - 2*pad) = (W - wrap)/2 + pad

   which is the content edge, for any W. Below the wrap width min() takes over
   and the half's own padding does the job instead. */
.ci-split__inner {
	width: min(100%, calc(var(--ci-wrap) / 2 - var(--ci-wrap-pad) * 2));
	margin-inline-start: auto;
}

.ci-split__art {
	background: var(--ci-ink-900);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--ci-pad-xl);
	overflow: hidden;
}

.ci-split__art .ci-figure { width: min(100%, 560px); }

.ci-split .ci-rule-list { margin: 0; }

@media (max-width: 900px) {
	.ci-split { grid-template-columns: 1fr; min-height: 0; }
	.ci-split__inner { width: 100%; margin-inline-start: 0; padding-inline-end: 0; }
	.ci-split__art { padding: var(--ci-pad-lg) var(--ci-wrap-pad); }
}

/* ---- Hairline spec rows -------------------------------------------------- */

/* The capability list as a datasheet rather than a card grid. Twelve cards
   on a service page is twelve boxes the eye has to re-enter; twelve ruled
   rows is one object it can scan. No fill, no border, no shadow: the rules
   carry the structure, which is also what lets the row stay readable when
   the description runs to four lines on a phone. */
.ci-specrows {
	list-style: none;
	margin: 0;
	padding: 0;
	border-top: 1px solid var(--ci-line);
}

.ci-specrow {
	display: grid;
	grid-template-columns: var(--ci-chip-md) minmax(0, 15rem) minmax(0, 1fr);
	gap: var(--ci-gap-sm) var(--ci-gap);
	align-items: start;
	padding-block: 1.15rem;
	border-bottom: 1px solid var(--ci-line);
}

.ci-specrow__mark {
	color: var(--ci-brand-fg);
	display: flex;
	align-items: center;
	justify-content: center;
	padding-top: .1rem;
}

.ci-specrow__title {
	margin: 0;
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-h4);
	font-weight: 650;
	letter-spacing: -.01em;
	line-height: 1.35;
	color: var(--ci-text);
}

.ci-specrow__body {
	margin: 0;
	color: var(--ci-text-2);
	font-size: var(--ci-fs-sm);
	line-height: 1.65;
}

@media (max-width: 760px) {
	.ci-specrow {
		grid-template-columns: var(--ci-chip-sm) minmax(0, 1fr);
	}

	/* The description drops under the title rather than under the icon, so
	   the icon column stays a gutter instead of becoming a column of air. */
	.ci-specrow__body { grid-column: 2; }
}

/* ---- Sticky section rail ------------------------------------------------- */

/* A page-wide bar under the header, not a sidebar. A sticky element is
   constrained to its containing block, so a rail rendered inside one section
   stops sticking when that section leaves — which is what a side rail nested
   in the capability band did, for a page with nine anchors. */
.ci-rail {
	position: sticky;
	top: var(--ci-header-h);
	z-index: 6;
	background: var(--ci-header-bg);
	backdrop-filter: saturate(180%) blur(14px);
	-webkit-backdrop-filter: saturate(180%) blur(14px);
	border-bottom: 1px solid var(--ci-line);
}

.ci-rail__inner {
	display: flex;
	align-items: center;
	gap: var(--ci-gap);
	min-width: 0;
}

.ci-rail__title {
	flex: 0 0 auto;
	margin: 0;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--ci-text-3);
}

.ci-rail__links {
	display: flex;
	align-items: center;
	gap: .15rem;
	min-width: 0;
	overflow-x: auto;
	scrollbar-width: none;
	scroll-behavior: smooth;
	padding-block: .6rem;
	/* The strip scrolls to the container edge, so the first and last entries
	   are not clipped against the gutter. */
	margin-inline: calc(var(--ci-wrap-pad) * -1);
	padding-inline: var(--ci-wrap-pad);
}

.ci-rail__links::-webkit-scrollbar { display: none; }

.ci-rail__link {
	flex: 0 0 auto;
	display: flex;
	align-items: baseline;
	gap: .45rem;
	padding: .45rem .8rem;
	border-radius: 999px;
	color: var(--ci-text-2);
	text-decoration: none;
	font-size: var(--ci-fs-sm);
	line-height: 1.4;
	white-space: nowrap;
	border: 1px solid transparent;
	transition: color var(--ci-dur-fast) var(--ci-ease),
	            border-color var(--ci-dur-fast) var(--ci-ease),
	            background-color var(--ci-dur-fast) var(--ci-ease);
}

.ci-rail__link:hover { color: var(--ci-text); background: var(--ci-paper-2); }

.ci-rail__link[aria-current] {
	color: var(--ci-brand-fg);
	border-color: var(--ci-surface-bd-hover);
	background: var(--ci-brand-050);
	font-weight: 600;
}

.ci-rail__count {
	font-size: var(--ci-fs-eyebrow);
	color: var(--ci-text-3);
	font-variant-numeric: tabular-nums;
}

@media (max-width: 760px) {
	.ci-rail__title { display: none; }
	.ci-rail__count { display: none; }
}

/* Anchored sections must clear both the header and the rail. */
.ci-anchor { scroll-margin-top: calc(var(--ci-header-h) + 4.5rem); }

/* ---- Mid-page conversion strip ------------------------------------------- */

/* Punctuation between bands, not a second closing CTA: one question, one
   button, no icon, no card. Never placed next to .ci-closing. */
.ci-inline-cta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--ci-gap);
	padding-block: clamp(2rem, 1.4rem + 2vw, 3rem);
}

.ci-inline-cta__copy { max-width: 56ch; }

.ci-inline-cta__title {
	margin: 0;
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-h3);
	font-weight: 650;
	letter-spacing: -.015em;
	color: var(--ci-text);
}

.ci-inline-cta__text {
	margin: .4rem 0 0;
	color: var(--ci-text-2);
	font-size: var(--ci-fs-sm);
	line-height: 1.6;
}

/* ---- Vertical accordion -------------------------------------------------- */

/* The theme had no vertical accordion: .ci-panels is horizontal and locked to
   four items. This is the long-form counterpart — scope statements, questions
   a buyer asks before an enquiry — and it is one <button> per region, same as
   .ci-panels, so the keyboard behaviour is the one already in app.js. */
.ci-acc {
	border-top: 1px solid var(--ci-line);
}

.ci-acc__item { border-bottom: 1px solid var(--ci-line); }

.ci-acc__btn {
	width: 100%;
	display: flex;
	align-items: center;
	gap: var(--ci-gap-sm);
	padding: 1.15rem 0;
	background: none;
	border: 0;
	text-align: start;
	cursor: pointer;
	color: var(--ci-text);
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-h4);
	font-weight: 650;
	letter-spacing: -.01em;
	line-height: 1.4;
}

.ci-acc__btn:hover { color: var(--ci-brand-fg); }

.ci-acc__chev {
	margin-inline-start: auto;
	flex: 0 0 auto;
	display: flex;
	color: var(--ci-text-3);
	/* The library chevron points right, which is the disclosure direction for
	   a drawer row and the wrong one here. */
	transform: rotate(90deg);
	transition: transform var(--ci-dur) var(--ci-ease);
}

.ci-acc__btn[aria-expanded="true"] .ci-acc__chev { transform: rotate(-90deg); }

/* Open is the default; collapse is gated.

   The gate is `ci-anim`, NOT `ci-js`, and the difference is the whole point.
   Both are set in <head> before the first paint, so neither flashes — but
   `ci-js` only says "scripting exists", while `ci-anim` says "and something
   has claimed it". Collapsing these answers is a promise that app.js will
   arrive to reopen them; if it 404s, hits a CSP, is blocked, or throws in code
   above the accordion's own init, `ci-js` is still set and three answers stay
   shut with no control that can open them. Scripting is enabled, so <noscript>
   does not help either.

   `ci-anim` is removed by the boot script's own 2.5s timer unless app.js
   cancels it, so that failure resolves to the no-JS state: every answer open
   and readable, which is exactly what the markup ships. */
.ci-acc__body {
	display: grid;
	grid-template-rows: 1fr;
	transition: grid-template-rows var(--ci-dur) var(--ci-ease);
}

html.ci-anim .ci-acc__item:not(.is-open) > .ci-acc__body { grid-template-rows: 0fr; }

.ci-acc__inner {
	overflow: hidden;
	min-height: 0;
}

/* overflow:hidden alone leaves the text in the accessibility tree and in the
   tab order while aria-expanded says it is closed. visibility does both, and
   the delay keeps it readable for the length of the collapse. */
html.ci-anim .ci-acc__item:not(.is-open) > .ci-acc__body > .ci-acc__inner {
	visibility: hidden;
	transition: visibility 0s linear var(--ci-dur);
}

.ci-acc__inner > * { margin-block: 0 1rem; }

/* The breathing room under an open answer lives on the LAST CHILD, not on the
   animated grid item. On .ci-acc__inner it was keyed to .is-open, so the first
   frame of every collapse removed 8px of padding before the grid started
   moving, and the answer jumped up by that much before it began to close. */
.ci-acc__inner > *:last-child { margin-block-end: 1.5rem; }

/* The answer arrives; it is not merely uncovered.
   A grid-template-rows expansion on its own slides a window down over text
   that is already sitting there in full, which reads as a shutter rather than
   as an answer. Giving the content its own shorter, later motion means the row
   opens and the words land inside it, and it costs one keyframe. */
@media (prefers-reduced-motion: no-preference) {
	html.ci-anim .ci-acc__item.is-open > .ci-acc__body > .ci-acc__inner > * {
		animation: ci-acc-in var(--ci-m-md) var(--ci-enter) backwards;
	}
	html.ci-anim .ci-acc__item.is-open > .ci-acc__body > .ci-acc__inner > :nth-child(2) { animation-delay: calc(var(--ci-stagger-tight) * 2); }
	html.ci-anim .ci-acc__item.is-open > .ci-acc__body > .ci-acc__inner > :nth-child(n+3) { animation-delay: calc(var(--ci-stagger-tight) * 4); }
}
@keyframes ci-acc-in {
	from { opacity: 0; transform: translate3d(0, 7px, 0); }
}

.ci-acc__text {
	color: var(--ci-text-2);
	font-size: var(--ci-fs-base);
	line-height: 1.7;
	max-width: 68ch;
}

/* ---- Figure band --------------------------------------------------------- */

/* Copy on one side, diagram on the other. The one layout on the site where
   the two halves are allowed to be unequal: the text column keeps the reading
   measure and the figure takes whatever is left. */
.ci-figband {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
	gap: var(--ci-gap-xl);
	align-items: center;
}

.ci-figband--flip > .ci-figband__art { order: -1; }

.ci-figband__copy { display: flex; flex-direction: column; gap: var(--ci-gap-sm); }

/* The band's own heading is a section heading; a bare h2 would otherwise
   render a step smaller than every other section head on the page. */
.ci-figband__copy h2 { font-size: var(--ci-fs-h2); margin: 0; }

.ci-figband__art {
	background: var(--ci-paper-2);
	border: 1px solid var(--ci-line-2);
	border-radius: var(--ci-r-lg);
	padding: var(--ci-pad-lg);
}

.ci-section--alt .ci-figband__art { background: var(--ci-raised); border-color: var(--ci-line); }

@media (max-width: 880px) {
	.ci-figband { grid-template-columns: 1fr; gap: var(--ci-gap); }
	.ci-figband--flip > .ci-figband__art { order: 0; }
}

/* ---- Folder tabs --------------------------------------------------------- */

/* The tab set on a dark band, with the active tab filled to match the panel
   it opens so the two read as one piece of paper notched into the band. The
   device is CtrlS's city switcher; theirs needs the band and the page below
   it to be different colours, which would mean splitting this section into
   two containers. Matching the tab to the panel instead gets the same read
   inside one band.

   The panel is a surface rather than the page behind it, so the tab row can
   sit flush on top of it without a seam. */
.ci-folder { --folder-bg: var(--ci-on-dark-fill); }

.ci-folder .ci-tabs__list-wrap { margin-bottom: 0; border-bottom: 0; }

.ci-folder .ci-tabs__list { gap: .25rem; border-bottom: 0; }

.ci-folder .ci-tabs__tab {
	color: var(--ci-on-dark-2);
	border: 0;
	border-radius: var(--ci-r-sm) var(--ci-r-sm) 0 0;
	padding: .8rem 1.1rem;
	background: rgba(255, 255, 255, .03);
	white-space: nowrap;
}

/* Lighter than the selected fill, never heavier: hover is a hint, selection
   is a state. */
.ci-folder .ci-tabs__tab:hover { color: var(--ci-on-dark); background: rgba(255, 255, 255, .04); }

.ci-folder .ci-tabs__tab[aria-selected="true"] {
	color: var(--ci-on-dark);
	background: var(--folder-bg);
	font-weight: 650;
}

.ci-folder .ci-tabs__panel {
	background: var(--folder-bg);
	border: 1px solid var(--ci-on-dark-line);
	/* Square where the tabs land, rounded everywhere else. */
	border-radius: 0 var(--ci-r) var(--ci-r) var(--ci-r);
	padding: var(--ci-pad-lg);
}

.ci-folder__pane {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
	gap: var(--ci-gap-lg);
	align-items: start;
}

.ci-folder__lead { margin: 0; color: var(--ci-on-dark-2); font-size: var(--ci-fs-lead); line-height: 1.6; }

.ci-folder__h { margin: 0 0 .6rem; color: var(--ci-on-dark); }

.ci-folder__label {
	margin: 0 0 .75rem;
	font-size: var(--ci-fs-eyebrow);
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--ci-on-dark-3);
}

.ci-folder__links { list-style: none; margin: 0; padding: 0; display: grid; gap: .3rem; }

.ci-folder__link {
	display: flex;
	align-items: center;
	gap: .7rem;
	padding: .7rem .85rem;
	border-radius: var(--ci-r-xs);
	color: var(--ci-on-dark);
	text-decoration: none;
	font-size: var(--ci-fs-sm);
	font-weight: 600;
	border: 1px solid transparent;
	transition: background-color var(--ci-dur) var(--ci-ease), border-color var(--ci-dur) var(--ci-ease);
}

.ci-folder__link:hover { background: var(--ci-on-dark-fill-2); border-color: var(--ci-on-dark-line); }

.ci-folder__link svg { color: var(--ci-accent); flex: 0 0 auto; }

.ci-folder__link .ci-row__go { color: var(--ci-on-dark-3); }

@media (max-width: 880px) {
	.ci-folder__pane { grid-template-columns: 1fr; gap: var(--ci-gap); }
	.ci-folder .ci-tabs__panel { border-radius: var(--ci-r); padding: var(--ci-pad); }
}

/* ---- Scope tiles --------------------------------------------------------- */

/* What replaced the four "at a glance" slots when they stopped pretending to
   be statistics. A scope tile names a stage of the engagement, which is
   verifiable; the numeral treatment it used to carry was not. */
.ci-scope__value {
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-h3);
	font-weight: 650;
	letter-spacing: -.015em;
	line-height: 1.2;
	color: var(--ci-text);
}

/* ---- Problem band -------------------------------------------------------- */

.ci-challenges { list-style: none; margin: 0; padding: 0; }

.ci-challenge {
	--sf-bg: var(--ci-raised);
	gap: .55rem;
	counter-increment: none;
}

.ci-challenge__num {
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-sm);
	font-weight: 700;
	letter-spacing: .04em;
	/* No opacity. At .8 this landed on 4.48:1 against --ci-raised in dark --
	   two hundredths under AA, and invisible to a desktop-only audit. */
	color: var(--ci-brand-fg);
}

.ci-challenge__title {
	margin: 0;
	font-size: var(--ci-fs-h3);
	font-weight: 650;
	letter-spacing: -.015em;
	line-height: 1.3;
	color: var(--ci-text);
}

.ci-challenge__text {
	margin: 0;
	color: var(--ci-text-2);
	font-size: var(--ci-fs-sm);
	line-height: 1.7;
}

/* The consequence line. Pushed to the foot of the card so the three cards
   end on the same rail however long their bodies run. */
.ci-challenge__tail {
	margin: auto 0 0;
	padding-top: .85rem;
	border-top: 1px solid var(--ci-line-2);
	display: flex;
	align-items: baseline;
	gap: .5rem;
	color: var(--ci-brand-fg);
	font-size: var(--ci-fs-sm);
	font-weight: 600;
	line-height: 1.5;
}

.ci-challenge__tail svg { flex: 0 0 auto; transform: translateY(2px); }

/* ---- Industry rows ------------------------------------------------------- */

.ci-indrows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--ci-gap-xs); }

.ci-indrow {
	display: flex;
	align-items: center;
	gap: var(--ci-gap-sm);
	width: 100%;
}

.ci-indrow__copy { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }

.ci-indrow__title {
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-h4);
	font-weight: 650;
	letter-spacing: -.01em;
	color: var(--ci-text);
}

.ci-indrow__text {
	color: var(--ci-text-3);
	font-size: var(--ci-fs-sm);
	line-height: 1.55;
}

/* The arrow that marks a row as a link. Travels on hover, the same distance
   as every other arrow in the theme. */
.ci-row__go {
	margin-inline-start: auto;
	flex: 0 0 auto;
	display: flex;
	color: var(--ci-text-3);
	transition: transform var(--ci-dur) var(--ci-ease), color var(--ci-dur) var(--ci-ease);
}

a.ci-row:hover .ci-row__go { transform: translateX(var(--ci-nudge)); color: var(--ci-brand-fg); }

/* Closed, not open: on devops-services the list holds a single industry, and
   a rule drawn only BETWEEN items left that row with no rule at all. */
.ci-indrows { border-top: 1px solid var(--ci-line); }

.ci-indrows > li { border-bottom: 1px solid var(--ci-line); }

.ci-indrow { padding-block: 1.05rem; }

/* A "see all" beside a section heading. The heading keeps its measure; the
   link sits on the heading's own baseline at the far edge. */
/* The heading keeps its 62ch measure; the head itself widens so the action
   can sit at the content edge rather than at the end of the text column. */
.ci-sec-head--action { position: relative; max-width: none; }

.ci-sec-head--action .ci-sec-head__title,
.ci-sec-head--action .ci-sec-head__lead { max-width: 62ch; }

.ci-sec-head__action { margin: .9rem 0 0; }

@media (min-width: 880px) {
	.ci-sec-head--action { padding-inline-end: 12rem; }
	.ci-sec-head__action { position: absolute; inset-block-end: .25rem; inset-inline-end: 0; margin: 0; }
}

/* The hero's figure panel. The visual used to be four rotated rectangles
   labelled with the same service names the grid below already listed. */
.ci-hero__figure { display: flex; flex-direction: column; gap: .9rem; }

.ci-hero__figure .ci-figure__caption { max-width: 42ch; }

/* ---- Rule list ----------------------------------------------------------- */

/* A titled list separated by hairlines, for a colour block where cards would
   be a second container inside a container. */
.ci-rule-list { list-style: none; margin: 0; padding: 0; }

.ci-rule-list__item {
	display: flex;
	flex-direction: column;
	gap: .25rem;
	padding-block: .95rem;
	border-top: 1px solid var(--ci-on-dark-line);
}

.ci-rule-list__item:last-child { border-bottom: 1px solid var(--ci-on-dark-line); }

.ci-rule-list__title {
	font-family: var(--ci-font-display);
	font-size: var(--ci-fs-h4);
	font-weight: 650;
	letter-spacing: -.01em;
	color: var(--ci-on-dark);
	display: flex;
	align-items: center;
	gap: .6rem;
	flex-wrap: wrap;
}

.ci-rule-list__text { color: var(--ci-on-dark-2); font-size: var(--ci-fs-sm); line-height: 1.6; }

/* ---- Search entry points ------------------------------------------------- */

/* The theme shipped searchform.php, search.php and a search icon, and only
   404.php ever rendered a field. */
.ci-footer__search { margin-block: 1.25rem 1rem; max-width: 22rem; }

.ci-footer__search .ci-search__input {
	background: rgba(255, 255, 255, .06);
	border-color: var(--ci-on-dark-line);
	color: var(--ci-on-dark);
}

.ci-footer__search .ci-search__input::placeholder { color: var(--ci-on-dark-3); }

.ci-footer__search .ci-search__submit { color: var(--ci-on-dark-2); }

.ci-drawer__search { margin-bottom: .9rem; }

/* ---- Comments ------------------------------------------------------------ */

/* Comments are off by default on this site. comments.php exists because
   WordPress printed a deprecation notice into every article without it; these
   rules exist so that an editor who turns comments on for one post does not
   get an unstyled thread in the middle of a designed page. */
.ci-comments { margin-top: var(--ci-gap-xl); }

.ci-comments__inner { border-top: 1px solid var(--ci-line); padding-top: var(--ci-gap-lg); }

.ci-comments__title { font-size: var(--ci-fs-h3); margin: 0 0 var(--ci-gap-sm); }

.ci-comments__list,
.ci-comments__list .children {
	list-style: none;
	margin: 0;
	padding: 0;
}

.ci-comments__list .children { margin-inline-start: var(--ci-gap); padding-inline-start: var(--ci-gap-sm); border-inline-start: 1px solid var(--ci-line-2); }

.ci-comments__list li.comment,
.ci-comments__list li.pingback {
	padding-block: var(--ci-gap-sm);
	border-bottom: 1px solid var(--ci-line-2);
}

.ci-comments__list .comment-author { display: flex; align-items: center; gap: .6rem; font-weight: 650; }

.ci-comments__list .comment-author img { border-radius: 999px; }

.ci-comments__list .comment-metadata,
.ci-comments__list .reply { font-size: var(--ci-fs-sm); color: var(--ci-text-3); }

.ci-comments__list .comment-content { color: var(--ci-text-2); line-height: 1.7; }

.ci-comments__closed { color: var(--ci-text-3); font-size: var(--ci-fs-sm); }

/* The form reuses the enquiry form's field styling; these are the parts
   comment_form() names itself. */
.ci-comments__form { margin-top: var(--ci-gap); display: grid; gap: var(--ci-gap-sm); }

.ci-comments__form .comment-notes,
.ci-comments__form .comment-form-cookies-consent label,
.ci-comments__form .logged-in-as { font-size: var(--ci-fs-sm); color: var(--ci-text-3); margin: 0; }

.ci-comments__form .comment-form-author,
.ci-comments__form .comment-form-email,
.ci-comments__form .comment-form-url { margin: 0; }

.ci-comments__form label { display: block; font-size: var(--ci-fs-sm); font-weight: 600; margin-bottom: .35rem; }

.ci-comments__form input[type="text"],
.ci-comments__form input[type="email"],
.ci-comments__form input[type="url"],
.ci-comments__form textarea {
	width: 100%;
	padding: .75rem .9rem;
	border: 1px solid var(--ci-line);
	border-radius: var(--ci-r-sm);
	background: var(--ci-field-bg);
	color: var(--ci-text);
	font: inherit;
}

.ci-comments__form input:focus-visible,
.ci-comments__form textarea:focus-visible {
	outline: var(--ci-focus-w) solid var(--ci-focus);
	outline-offset: var(--ci-focus-off);
	background: var(--ci-field-bg-focus);
}

.ci-comments__form .form-submit { margin: 0; }

/* ---- Shared small pieces ------------------------------------------------- */

/* A lead paragraph inside a component, where .ci-sec-head__lead would be the
   wrong measure and the wrong margin. */
.ci-lead-sm {
	margin: 0;
	color: var(--ci-text-2);
	font-size: var(--ci-fs-lead);
	line-height: 1.65;
	max-width: 58ch;
}

.ci-ticks {
	list-style: none;
	margin: .25rem 0 0;
	padding: 0;
	display: grid;
	gap: .55rem;
}

.ci-ticks li {
	display: flex;
	align-items: baseline;
	gap: .6rem;
	color: var(--ci-text-2);
	font-size: var(--ci-fs-sm);
	line-height: 1.6;
}

.ci-ticks svg { flex: 0 0 auto; color: var(--ci-brand-fg); transform: translateY(2px); }

.ci-figband__action,
.ci-split__action { margin: .5rem 0 0; }

/* The accordion question is an <h3> for the document outline; it must not
   also carry heading typography, because the button inside it already does. */
.ci-acc__h { margin: 0; font-size: inherit; font-weight: inherit; letter-spacing: normal; }

.ci-split h2 {
	color: var(--ci-on-dark);
	font-size: var(--ci-fs-h2);
	margin-bottom: .4em;
}

.ci-split .ci-lead-sm { color: var(--ci-on-dark-2); }

.ci-split .ci-eyebrow { color: var(--ci-on-dark-3); }

/* ---- Capability column --------------------------------------------------- */

.ci-caps-body { display: flex; flex-direction: column; gap: var(--ci-gap-xl); min-width: 0; }

.ci-caps-body .ci-capgroup { display: flex; flex-direction: column; gap: .9rem; }

.ci-caps-body__note { margin: 0; }

/* ---- Question column ----------------------------------------------------- */

/* The heading sits beside the questions rather than above them: an accordion
   under a full-width heading reads as a footnote, and these are the answers a
   buyer wants before they fill in the form. */
.ci-qa {
	display: grid;
	grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
	gap: var(--ci-gap-xl);
	align-items: start;
}

.ci-qa__head .ci-sec-head { margin-bottom: 0; }

@media (max-width: 900px) {
	.ci-qa { grid-template-columns: 1fr; gap: var(--ci-gap); }
}

/* ---- Connected steps ----------------------------------------------------- */

/* The three stages were three columns with a rule over each and nothing to
   say they were a sequence. Carrying each column's top border across the gap
   makes one rule out of three, which is the whole device.

   Only above 900px: .ci-steps is an auto-fit grid, and once it wraps to two
   columns the connector would bridge a row break. */
@media (min-width: 900px) {
	.ci-steps--rail > .ci-step::after {
		content: "";
		position: absolute;
		inset-block-start: -2px;
		inset-inline-start: 100%;
		inline-size: var(--ci-gap);
		block-size: 2px;
		background: var(--ci-line);
	}

	.ci-steps--rail > .ci-step::before {
		content: "";
		position: absolute;
		inset-block-start: -5px;
		inset-inline-start: 0;
		inline-size: 8px;
		block-size: 8px;
		border-radius: 50%;
		background: var(--ci-brand-600);
	}

	.ci-steps--light.ci-steps--rail > .ci-step::before { background: var(--ci-accent); }

	.ci-steps--rail > .ci-step:last-child::after { content: none; }

	.ci-steps--light.ci-steps--rail > .ci-step::after { background: var(--ci-on-dark-line); }
}

/* ---- The one focus ring -------------------------------------------------- */

.ci-btn:focus-visible,
a.ci-surface:focus-visible,
a.ci-row:focus-visible,
.ci-rail__link:focus-visible,
.ci-acc__btn:focus-visible,
.ci-specrow a:focus-visible,
.ci-theme-card__link:focus-visible,
.ci-svc-row:focus-visible,
.ci-mega__link:focus-visible,
/* Added rather than left to their own one-off rings. Each of these had a 2px
   --ci-accent ring at a different offset, which on white paper is the weakest
   indicator in the theme and in dark mode missed the dark-mode focus colour
   entirely. A keyboard user should not be able to tell which component they
   are in from the shape of the ring. */
.ci-tabs__tab:focus-visible,
.ci-mode__opt:focus-visible,
.ci-totop:focus-visible,
a.ci-pill:focus-visible,
button.ci-surface:focus-visible,
.ci-drawer__list a:focus-visible,
.ci-drawer__sub-toggle:focus-visible {
	/* No border-radius here. An outline already follows the element's own
	   corner, and setting one would square off every pill it touches. */
	outline: var(--ci-focus-w) solid var(--ci-focus);
	outline-offset: var(--ci-focus-off);
}

/* Three of those controls live inside a scroll container, and an outside ring
   has nowhere to be drawn there: .ci-tabs__list-wrap is overflow-x: auto,
   .ci-drawer__body is overflow-y: auto and a drawer submenu is overflow:
   hidden, each with zero padding on the clipped axis. At +3px the ring's bands
   fall outside the padding box and are simply cut away, so a focused tab or
   drawer row reads as two detached horizontal lines rather than a box.

   Only the OFFSET changes. The width and the colour stay on the tokens, so
   this is the same ring drawn on the inside rather than a fourth ring. */
.ci-tabs__tab:focus-visible,
.ci-drawer__list a:focus-visible,
.ci-drawer__sub-toggle:focus-visible,
.ci-panels__btn:focus-visible,
.ci-tabs__panel:focus-visible,
.ci-chip input:focus-visible + span { outline-offset: calc(var(--ci-focus-off) * -1); }

/* 14. Dark mode — structural adjustments
   Everything that a token swap alone cannot express: elevation that needs a
   hairline instead of a shadow, band-against-band seams, and the decorative
   layer. Both switch selectors are listed on every rule for the reason given
   at the top of the file.
   ========================================================================== */

/* Every rule here is driven by a custom property rather than a `[data-theme]`
   selector. That matters: dark mode is reached by TWO routes — the explicit
   attribute, and `prefers-color-scheme` for a visitor who has never toggled
   (and for no-JS visitors). A selector-gated rule would silently apply to only
   one of them. Each token below is inert in light mode, so these rules can be
   stated once, unconditionally, and still be correct on both routes. */

/* Raised surfaces used to be retinted here by name: a group listing
   .ci-card, .ci-vm__card, .ci-flow__item, .ci-post__link, .ci-contact__form,
   .ci-mega__inner and .ci-value-band__inner, all set to
   border-color: var(--ci-elev-bd). Every one of them is now a .ci-surface,
   and --sf-bd already defaults to that same token, so the group had nothing
   left to do. Restating it as a blanket .ci-surface rule would have been
   worse than redundant -- it would override .ci-cap's deliberately
   transparent edge and .ci-teaser's softer --ci-line-2 in LIGHT mode, which
   is a light-mode regression caused by a dark-mode rule. */

/* These carry NO border in light mode and must not gain a visible one, so they
   use the soft token: transparent in light, hairline in dark. */
.ci-teaser__icon,
.ci-value-band__mark,
.ci-drawer__panel {
	border: 1px solid var(--ci-elev-bd-soft);
}

/* The hero keeps its own gradient token (declared with the rule in section 7)
   so it stays the most dominant surface even when the page itself is dark.
   All this adds is the band edge. */
.ci-hero { border-bottom: 1px solid var(--ci-band-edge); }

/* Band-against-band seams. tpl-insights.php and tpl-contact.php do not call
   ci_closing_section(), so an ink band or the map lands flush on the footer —
   two near-identical darks with no edge between them. --ci-band-edge is
   non-transparent in light mode too, so this also fixes the existing
   light-mode seam on the Insights page. */
.ci-section--ink { border-block: 1px solid var(--ci-band-edge); }
.ci-closing__inner { border: 1px solid var(--ci-band-edge); }
.ci-footer { border-top: 1px solid var(--ci-band-edge); }

/* Blockquote: --ci-brand-050 is a deep wash in dark, so the left rule needs
   the lightened brand to stay visible against it. */
.ci-prose blockquote { border-left-color: var(--ci-quote-rule); }

/* User-uploaded imagery: take the glare off without washing out detail. */
.ci-post__media img,
.ci-featured img { filter: var(--ci-media-filter); }

/* NO filter is applied to logos. Inverting a dark wordmark looks right only
   when the file has a transparent background; a logo with a baked-in white
   background — which is the common case — inverts to a solid block. The theme
   ships a prepared transparent pair instead, and falls back to a light plate
   behind the logo when a site supplies its own logo without a dark variant.
   See ci_color_mode_inline_css(). */
.ci-footer__logo img { display: block; }

/* Colour-mode control ------------------------------------------------------ */

/* Three states, not two: Light, System and Dark. Without an explicit System
   option a visitor who once pressed the toggle could never hand control back
   to their device.

   The selected segment is driven by --ci-mode-i, which comes from
   [data-theme-choice] on <html> — written by the boot script before first
   paint. Keying it to ARIA state instead would paint the wrong segment for a
   frame on every load, because ARIA is only set once JS runs. The index lives
   on :root (where the attribute is) and the segment WIDTH on the component
   (where the breakpoint is), so the two never compete on specificity. */
.ci-mode {
	--ci-mode-seg: 32px;
	--ci-mode-i: 1; /* System, until the boot script says otherwise. */
	position: relative;
	flex: none;
	display: inline-flex;
	align-items: center;
	padding: 3px;
	border-radius: 999px;
	background: var(--ci-field-bg);
	border: 1px solid var(--ci-text-3);
	transition: border-color var(--ci-dur) var(--ci-ease), background-color var(--ci-dur) var(--ci-ease);
}
:root[data-theme-choice="light"] .ci-mode { --ci-mode-i: 0; }
:root[data-theme-choice="system"] .ci-mode { --ci-mode-i: 1; }
:root[data-theme-choice="dark"] .ci-mode { --ci-mode-i: 2; }

/* The moving highlight behind the selected segment. */
.ci-mode__pip {
	position: absolute;
	top: 3px;
	left: 3px;
	width: var(--ci-mode-seg);
	height: 24px;
	border-radius: 999px;
	background: var(--ci-switch-thumb);
	box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
	transform: translateX(calc(var(--ci-mode-i) * var(--ci-mode-seg)));
	transition: transform var(--ci-dur) var(--ci-ease-out), background-color var(--ci-dur) var(--ci-ease);
}

.ci-mode__opt {
	position: relative;
	z-index: 1;
	display: grid;
	place-items: center;
	width: var(--ci-mode-seg);
	height: 24px;
	padding: 0;
	border: 0;
	border-radius: 999px;
	background: none;
	cursor: pointer;
	/* State is carried by colour, never opacity: opacity dims the glyph below
	   3:1 and the OS palette does not override it in forced-colors mode. */
	color: var(--ci-text-3);
	transition: color var(--ci-dur) var(--ci-ease);
}
/* The pip is light in BOTH modes, so the selected glyph is the fixed ink
   colour rather than --ci-text (which flips and would vanish on it). */
:root[data-theme-choice="light"] .ci-mode__opt[data-mode="light"],
:root[data-theme-choice="system"] .ci-mode__opt[data-mode="system"],
:root[data-theme-choice="dark"] .ci-mode__opt[data-mode="dark"] { color: var(--ci-ink); }

.ci-mode:hover { border-color: var(--ci-brand-fg); }
.ci-mode__opt:hover { color: var(--ci-text); }

.ci-mode--drawer { margin-left: auto; margin-right: .5rem; }

/* The control cannot work without app.js: every segment ships tabindex="-1"
   and aria-checked="false", and syncToggles() is what makes one of them the
   group's tab stop and marks it checked. The <noscript> rule hides it when
   scripting is off, but that does not fire when scripting is ON and the file
   simply never arrived -- which left a fully painted segmented control, with
   its pip in the right place, that was silent on click and announced as three
   unchecked radios nobody could reach. Same gate as everything else. */
html:not(.ci-anim) .ci-mode { display: none !important; }

@media (max-width: 420px) {
	.ci-mode { --ci-mode-seg: 28px; }
}

@media (forced-colors: active) {
	/* The pip is a background and disappears; mark the selection with a border. */
	.ci-mode__pip { display: none; }
	:root[data-theme-choice="light"] .ci-mode__opt[data-mode="light"],
	:root[data-theme-choice="system"] .ci-mode__opt[data-mode="system"],
	:root[data-theme-choice="dark"] .ci-mode__opt[data-mode="dark"] { border: 2px solid; }
}

/* Native controls: the UA paints placeholders, and nothing in the theme did. */
.ci-field input::placeholder,
.ci-field textarea::placeholder,
.ci-search__input::placeholder {
	color: var(--ci-text-3);
	opacity: 1; /* Firefox applies its own opacity on top of the colour. */
}

/* Chrome repaints autofilled fields with its own palette. */
input:-webkit-autofill,
textarea:-webkit-autofill {
	-webkit-text-fill-color: var(--ci-text);
	-webkit-box-shadow: 0 0 0 1000px var(--ci-field-bg-focus) inset;
	caret-color: var(--ci-text);
}

/* Hover that steps the background only makes sense with a real pointer;
   on touch it sticks after the tap. Inert in light (token == --ci-raised). */
@media (hover: hover) and (pointer: fine) {
/* Re-keyed onto the primitive. This group used to name .ci-card, .ci-teaser
   and .ci-post__link, so it would have stopped matching the moment they
   became surfaces -- and the background lift on hover, which is the only
   feedback that reads in dark mode, would have gone with it. Note the
   inconsistency this also fixes: .ci-card was element-gated here (a.ci-card)
   while the other two were not, so a teaser rendered as a div got a hover it
   had no business having.

   It stays inside the pointer guard on purpose. The lift is authored outside
   the guard, matching the behaviour the components already had; a background
   that sticks after a tap is a different kind of wrong. */
	a.ci-surface:hover,
	button.ci-surface:hover { --sf-bg: var(--ci-hover-surface); }
}

/* Windows high-contrast: let the OS palette through rather than fighting it. */
@media (forced-colors: active) {
	.ci-chip input:checked + span { outline: 2px solid; outline-offset: 2px; }
	.ci-consent input { accent-color: auto; }
	.ci-card,
	.ci-cap,
	.ci-flow__item,
	.ci-post__link { border: 1px solid; }
	.ci-card__glow,
	.ci-decor { display: none; }
	/* Filters survive forced-colors and would erase an inverted wordmark
	   against the OS palette. Neutralise at the element, not the token: a
	   bare :root reset loses to the [data-theme] switch blocks. */
	.ci-post__media img,
	.ci-featured img,
	.ci-map iframe { filter: none; }
}

/* 15. Motion & reduced motion
   ========================================================================== */

/* 15a. Reveal on scroll
   --------------------------------------------------------------------------
   Every start state below is written under html.ci-anim, a class only the
   pre-paint boot script sets and which it removes again after 2.5s if app.js
   never claims it. That gate is the whole reason these rules can safely paint
   an element invisible: with scripting off, with the script 404ing, with a
   blocker in the way, or with a throw earlier in app.js, the class is gone and
   the page is simply finished content. Writing opacity:0 on a bare
   [data-reveal] -- which is what this file used to do -- stakes 40 blocks
   across the site on one file arriving.

   One transform serves every variant: each sets --rv-x / --rv-y / --rv-s and
   inherits the same transition, so a new variant is three custom properties
   and cannot drift onto its own curve or duration.

   There is deliberately no will-change. It buys one frame at the start of the
   transition and costs a compositing layer per element for the whole life of
   the page -- on a 7,900px service page that is upward of thirty layers held
   for a 620ms animation each. The browser promotes a transform/opacity
   transition on its own when it starts. */

html.ci-anim [data-reveal] {
	--rv-x: 0px;
	--rv-y: var(--ci-rise);
	--rv-s: 1;
	opacity: 0;
	transform: translate3d(var(--rv-x), var(--rv-y), 0) scale(var(--rv-s));
	transition: opacity var(--ci-m-lg) var(--ci-enter), transform var(--ci-m-lg) var(--ci-enter);
	/* --i is a capped child index (see ci_reveal()); --d is an explicit
	   millisecond offset for the handful of places that need one. Both, so a
	   group can stagger and still start late as a group. */
	transition-delay: calc(var(--d, 0ms) + var(--i, 0) * var(--ci-stagger));
}
html.ci-anim [data-reveal].is-in {
	opacity: 1;
	transform: none;
}
/* Set together with .is-in on anything the reader has already scrolled past
   when the page loads, so it takes its finished state without animating. */
html.ci-anim [data-reveal].is-instant { transition: none; }

/* The variants. Each is matched to what it reveals rather than offered as a
   choice: text and bands rise, a diagram scales up into place because scaling
   vector artwork costs nothing in sharpness, a hairline rule wipes along its
   own length, and a column glides in from its own side. */
html.ci-anim [data-reveal="fade"]   { --rv-y: 0px; }
html.ci-anim [data-reveal="lift"]   { --rv-y: calc(var(--ci-rise) * 1.8); }
/* A fade and nothing else, on purpose. A figure is usually nested inside a
   band that is itself revealing, and two rises compound into one 40px lurch;
   the motion that belongs to a diagram is the drawing of it (15c), not the
   arrival of its box. */
html.ci-anim [data-reveal="figure"] { --rv-y: 0px; --rv-s: 1; }

/* Leftward only, and that is not a style choice: a right-ward translate on a
   full-width element overflows the inline-end edge and gives the document a
   horizontal scrollbar for the length of the animation. */
html.ci-anim [data-reveal="glide"] { --rv-x: calc(var(--ci-glide) * -1); --rv-y: 0px; }

html.ci-anim [data-reveal="wipe"] {
	--rv-y: 0px;
	clip-path: inset(0 100% 0 0);
	transition-property: opacity, transform, clip-path;
}
/* Generous slack on all four sides of the resting state, and the same four
   components so the two states interpolate instead of snapping. An inset()
   that lands exactly on the box clips the descenders of its own last line and,
   worse, clips the focus ring of any link inside it — the ring sits 6px
   outside the box, so the slack has to clear that. */
html.ci-anim [data-reveal="wipe"].is-in { clip-path: inset(-.75em -.75em -.75em -.75em); }

/* 15b. Page-load choreography
   --------------------------------------------------------------------------
   The hero cannot wait for the scroll observer. It is on screen at load, so an
   IntersectionObserver reveal means the first thing a visitor sees is an empty
   band that fills in a frame later -- and it makes the hero's arrival depend on
   a script, for content that is the page's entire first impression.

   So this runs in CSS alone, gated on ci-js rather than ci-anim: it needs the
   stylesheet and nothing else, and there is no failure mode to disarm.

   The order is semantic, not nth-child: these rules hold across all eight hero
   variants in the theme (home, page, service, article, index, 404) without
   knowing how many children any of them has.

   The h1 runs at delay 0 on purpose. It is the Largest Contentful Paint
   element on nearly every page here, and LCP is recorded when the element
   first paints -- so every millisecond of animation-delay on it is a
   millisecond added to the site's LCP. Everything else is free to stagger
   behind it; the headline is not. */

@media (prefers-reduced-motion: no-preference) {
	html.ci-js .ci-header { animation: ci-drop-in var(--ci-m-md) var(--ci-enter) backwards; }

	html.ci-js .ci-hero__title { animation: ci-unmask var(--ci-m-lg) var(--ci-enter) backwards; }

	html.ci-js .ci-hero__eyebrow,
	html.ci-js .ci-hero .ci-crumbs { animation: ci-rise-in var(--ci-m-md) var(--ci-enter) backwards; }

	html.ci-js .ci-hero__visual,
	html.ci-js .ci-hero__panel { animation: ci-rise-in var(--ci-m-lg) var(--ci-enter) 160ms backwards; }

	html.ci-js .ci-hero__lead { animation: ci-rise-in var(--ci-m-lg) var(--ci-enter) 110ms backwards; }
	html.ci-js .ci-hero__text { animation: ci-rise-in var(--ci-m-lg) var(--ci-enter) 170ms backwards; }
	html.ci-js .ci-hero__actions { animation: ci-rise-in var(--ci-m-lg) var(--ci-enter) 230ms backwards; }

	html.ci-js .ci-hero__chips,
	html.ci-js .ci-hero__search { animation: ci-rise-in var(--ci-m-lg) var(--ci-enter) 280ms backwards; }

	/* Last, and a fade only: it is a moving strip, so sliding it in as well
	   reads as two motions fighting. */
	html.ci-js .ci-hero__marquee { animation: ci-fade-in var(--ci-m-lg) var(--ci-enter) 400ms backwards; }
}

/* Every entrance keyframe below declares a `from` and NOTHING else, which is
   load-bearing rather than terse. An omitted property takes its 100% keyframe
   from the element's own computed style, so the animation lands on whatever
   the design says and cannot overwrite it.

   Writing `to { opacity: 1 }` here instead looks harmless and is not: the hero
   lead is deliberately set to opacity .96 — white type softened against the
   gradient — and a `to` of 1, held by a forwards fill, quietly republished it
   at full strength for the life of the page. Any element with a resting
   opacity, transform or scale of its own would have been overwritten the same
   way, silently, with nothing in the animation to point at.

   The fill is `backwards` for the same reason: it supplies the from-state
   during the delay, then gets out of the way instead of pinning a snapshot of
   the last frame over the element's real styles. */
@keyframes ci-rise-in {
	from { opacity: 0; transform: translate3d(0, var(--ci-rise), 0); }
}
@keyframes ci-fade-in {
	from { opacity: 0; }
}
@keyframes ci-drop-in {
	from { opacity: 0; transform: translate3d(0, -12px, 0); }
}
/* clip-path is the one exception: it has to name its end state, because the
   element's own resting value is `none` and inset() cannot interpolate to
   `none` — it would snap at the first frame. Same four components at both
   ends, so the two states interpolate instead of jumping. */
@keyframes ci-unmask {
	from { opacity: 0; clip-path: inset(-.3em -.3em 100% -.3em); transform: translate3d(0, 10px, 0); }
	to { clip-path: inset(-.3em -.3em -.3em -.3em); }
}

/* 15c. The diagrams draw themselves
   --------------------------------------------------------------------------
   The twelve figures are the richest thing on the site to animate, and the
   only artwork here whose own structure carries the meaning: a connector
   drawing from one node to the next says "this follows from that" in a way no
   fade can.

   pathLength="1" is what makes it free. It renormalises the path's own length
   to 1 user unit, so a dasharray of 1 covers exactly one full path whatever
   its real geometry -- no measuring any path in JS, no per-figure constants,
   and it keeps working if a diagram is redrawn.

   Dashed connectors are excluded: they carry their own stroke-dasharray to
   mean "indirect", and the draw would overwrite it. They fade with the boxes
   instead.

   Nothing below is hidden by a base style, and that is deliberate. The obvious
   build -- opacity:0 on every box and label, animated back up to 1 -- puts
   twelve diagrams' worth of content behind a class, and then needs the print
   block and the reduced-motion block each to remember to undo it. None of that
   is necessary: the <figure> wrapping them is itself a [data-reveal], so the
   whole SVG is already invisible until `is-in` lands. The parts only need
   their from-states DURING the animation, which is exactly what a `backwards`
   fill supplies and nothing else. Drop the class and every diagram is just a
   finished diagram. */

@media (prefers-reduced-motion: no-preference) {
	/* Only the dasharray is a base style, because it is not animated and the
	   draw has nothing to divide without it. At rest it is one dash covering
	   one whole path -- a solid line, which is the correct resting state. */
	html.ci-anim [data-reveal="figure"] .f-link:not([class*="dashed"]) {
		stroke-dasharray: 1;
	}
	html.ci-anim [data-reveal="figure"].is-in .f-link:not([class*="dashed"]) {
		animation: ci-draw var(--ci-m-xl) var(--ci-ease) var(--fig-d, 120ms) backwards;
	}

	/* An arrowhead is painted by a marker, which no dashoffset reaches -- it
	   sits at the path's end point from the first frame and would arrive
	   before the line that earns it. Markers are per-figure here, so the
	   figure's own state can gate them. */
	html.ci-anim [data-reveal="figure"].is-in .f-head {
		animation: ci-fade-in var(--ci-m-md) var(--ci-ease) calc(var(--fig-d, 120ms) + var(--ci-m-xl) * .62) backwards;
	}

	/* Nodes and labels. nth-of-type gives a stagger with no per-figure markup:
	   the boxes land in the order they were drawn, which is the order the
	   diagram is read in. Capped at six so a dense figure still finishes. */
	html.ci-anim [data-reveal="figure"].is-in .f-box,
	html.ci-anim [data-reveal="figure"].is-in .f-glyph {
		animation: ci-fig-pop var(--ci-m-md) var(--ci-enter) backwards;
		transform-box: fill-box;
		transform-origin: 50% 50%;
	}
	html.ci-anim [data-reveal="figure"].is-in .f-box:nth-of-type(1) { animation-delay: var(--fig-d, 120ms); }
	html.ci-anim [data-reveal="figure"].is-in .f-box:nth-of-type(2) { animation-delay: calc(var(--fig-d, 120ms) + 60ms); }
	html.ci-anim [data-reveal="figure"].is-in .f-box:nth-of-type(3) { animation-delay: calc(var(--fig-d, 120ms) + 120ms); }
	html.ci-anim [data-reveal="figure"].is-in .f-box:nth-of-type(4) { animation-delay: calc(var(--fig-d, 120ms) + 180ms); }
	html.ci-anim [data-reveal="figure"].is-in .f-box:nth-of-type(5) { animation-delay: calc(var(--fig-d, 120ms) + 240ms); }
	html.ci-anim [data-reveal="figure"].is-in .f-box:nth-of-type(n+6) { animation-delay: calc(var(--fig-d, 120ms) + 300ms); }
	html.ci-anim [data-reveal="figure"].is-in .f-glyph { animation-delay: calc(var(--fig-d, 120ms) + 260ms); }

	/* Labels last: the diagram builds, then it is annotated. */
	html.ci-anim [data-reveal="figure"].is-in .f-label,
	html.ci-anim [data-reveal="figure"].is-in .f-dot {
		animation: ci-fade-in var(--ci-m-md) var(--ci-ease) calc(var(--fig-d, 120ms) + var(--ci-m-xl) * .45) backwards;
	}

	/* The hero's diagram waits for the panel holding it to land first. */
	.ci-hero__figure .ci-figure { --fig-d: 420ms; }
}

@keyframes ci-draw {
	from { stroke-dashoffset: 1; }
	to { stroke-dashoffset: 0; }
}
@keyframes ci-fig-pop {
	from { opacity: 0; transform: scale(.9); }
}

/* 15d. Work that is not done until it is needed
   --------------------------------------------------------------------------
   Two infinite animations run on this site: the hero's gradient orbs and the
   marquee. Both composite on every frame for as long as the page is open, so
   on a 7,900px page they are still burning frames thousands of pixels after
   anyone can see them. app.js marks them idle once they leave the viewport. */
.ci-decor.is-idle .ci-decor__orb,
.ci-hero__marquee.is-idle .ci-marquee__track { animation-play-state: paused; }

/* The embedded map is the one genuinely heavy thing on the site: a third-party
   frame that pulls scripts, tiles and cookies. loading="lazy" is not enough --
   the browser's own threshold is generous, and on contact the frame is barely
   below the fold, so it loads on arrival regardless. This holds the src until
   the reader is actually approaching it, which also means a visitor who never
   scrolls that far is never handed to a third party at all. */
.ci-lazyframe {
	position: relative;
	display: block;
	background:
		linear-gradient(var(--ci-band-line) 1px, transparent 1px) 0 0 / 100% 28px,
		linear-gradient(90deg, var(--ci-band-line) 1px, transparent 1px) 0 0 / 28px 100%,
		var(--ci-paper-2);
}
.ci-lazyframe iframe { display: block; border: 0; }
/* With scripting off, the <noscript> copy of the frame is the one that works,
   so the deferred frame and its placeholder have to get out of its way — left
   visible they would stack an empty 420px box under a working map. */
/* ci-anim, not ci-js -- the same distinction as everywhere else, and this was
   the fifth place that had it wrong. ci-js is set by the boot script whether
   or not app.js ever lands, so with the file blocked the <noscript> copy does
   not render AND these two are not hidden: the contact page showed a 420px box
   containing the words "Loading map" over an iframe with no src, forever. */
html:not(.ci-anim) .ci-lazyframe__note,
html:not(.ci-anim) .ci-lazyframe > iframe[data-src] { display: none; }
.ci-lazyframe__note {
	position: absolute;
	inset-inline: 0;
	top: 50%;
	transform: translateY(-50%);
	margin: 0;
	text-align: center;
	font-size: .875rem;
	/* --ci-text-2, not --ci-text-3, and the grid behind it is the reason. The
	   placeholder sits on a ruled background, so the contrast that counts is
	   against the RULE colour, not the fill: --ci-text-3 on --ci-band-line is
	   4.40:1, which is under AA for 14px text. --ci-text-2 is 6.14:1.
	   axe cannot catch this — it scores text against the computed background
	   colour and a repeating-gradient rule is not one. */
	color: var(--ci-text-2);
}
.ci-lazyframe.is-loaded .ci-lazyframe__note { display: none; }

/* 15e. Between pages
   --------------------------------------------------------------------------
   One rule, and only where the browser supports it: the outgoing page
   dissolves instead of being replaced mid-blink. The incoming page is NOT
   faded in -- it is held opaque so that its own hero choreography above is the
   thing the visitor sees arrive, rather than two crossfades on top of each
   other. Everywhere else this is inert and navigation is exactly as before. */
@media (prefers-reduced-motion: no-preference) {
	@view-transition { navigation: auto; }
}
::view-transition-old(root) { animation: ci-fade-out .16s var(--ci-ease) both; }
::view-transition-new(root) { animation: none; opacity: 1; }
@keyframes ci-fade-out {
	from { opacity: 1; }
	to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*,
	*::before,
	*::after {
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .01ms !important;
		scroll-behavior: auto !important;
		/* Both delays, and they matter more than the durations. An entrance
		   animation here is written `... both`, so its fill mode holds the
		   from-state -- opacity 0 -- for the whole of animation-delay. Cutting
		   the duration to nothing and leaving a 400ms delay in place gives a
		   reduced-motion visitor 400ms of blank hero: not less motion, just a
		   slower page. The same applies to the staggered transition-delay on
		   every reveal group. */
		animation-delay: 0s !important;
		transition-delay: 0s !important;
	}
	[data-reveal] { opacity: 1 !important; transform: none !important; }
	/* The wipe variant hides itself with a clip-path rather than with opacity,
	   so the line above does not reach it. */
	[data-reveal] { clip-path: none !important; }
	.ci-panels__item { transition: none !important; }
	.ci-panels__body { transition: none !important; }
	/* Duration alone is not enough: a .01ms transform is still a 4px jump.
	   The hover feedback stays -- colour, border and shadow are not motion --
	   and only the movement is dropped. */
	a.ci-surface:hover,
	button.ci-surface:hover,
	a.ci-surface:active,
	button.ci-surface:active,
	.ci-btn:hover .ci-btn__icon,
	.ci-svc-row:hover .ci-svc-row__arrow,
	.ci-theme-card__link:hover .ci-theme-card__arrow,
	.ci-industry__link:hover .ci-industry__link-arrow,
	/* These eight were escaping the list, which is worse than not suppressing
	   motion at all: the duration override above cuts each to .01ms, so a
	   reduced-motion visitor got the full travel as a single-frame SNAP. The
	   blog cover images popped 5% larger under the cursor and popped back; the
	   button itself jumped 2px while the arrow inside it, two declarations
	   away, correctly did not.
	   The rule when adding a hover: if it moves, it belongs here. Colour,
	   border and shadow do not, and are deliberately left alone. */
	.ci-btn:hover,
	.ci-btn:active,
	.ci-totop:hover,
	/* Ancestor-keyed, because the rules that move this arrow are:
	   a.ci-card:hover / .ci-teaser:hover / .ci-post__link:hover. Keyed to
	   .ci-card__more:hover it matched nothing -- the card IS the link, so the
	   pointer is almost never on the small "Read more" span itself. */
	a.ci-card:hover .ci-card__more .ci-icon,
	.ci-teaser:hover .ci-card__more .ci-icon,
	.ci-post__link:hover .ci-card__more .ci-icon,
	/* The card's underline glow is a transform too -- scaleX(0) to scaleX(1).
	   It was never in this list at all. */
	a.ci-card:hover .ci-card__glow,
	a.ci-row:hover .ci-row__go,
	.ci-post__link:hover .ci-post__media img,
	a.ci-card--media:hover .ci-card__img,
	a.ci-card--media:hover .ci-card__glyph { transform: none; }
	.ci-marquee__track { animation: none; }
	.ci-decor__orb { animation: none; }
}

/* ---- Service art: cover images, not diagrams ---------------------------- */

/* A different kind of picture from .ci-figure, so a separate namespace. The
   figure is a labelled diagram with a title and a desc; this is geometry that
   fills a frame, carries no text and is aria-hidden. It reuses the --ci-fig-*
   tokens, which is what makes one asset re-tint with the brand and invert in
   dark mode without a second file. */
.ci-art {
	position: relative;
	overflow: hidden;
	border-radius: var(--ci-r-lg);
	background: var(--ci-fig-wash);
}
.ci-art__svg { display: block; width: 100%; height: 100%; }

/* The gradient field behind every panel. Stops are set in CSS rather than on
   the <stop> elements so they follow the mode switch with everything else. */
.ci-art .a-stop--from { stop-color: color-mix(in srgb, var(--ci-brand) 16%, transparent); }
.ci-art .a-stop--to   { stop-color: color-mix(in srgb, var(--ci-accent) 12%, transparent); }
.ci-art .a-field      { opacity: 1; }

.ci-art .a-grid {
	fill: none;
	stroke: var(--ci-fig-line);
	stroke-width: 1;
	opacity: .5;
}
/* --ci-raised, NOT --ci-surface. There is no --ci-surface token in this
   theme -- only --ci-surface-bd-hover, which is what made the typo survive a
   grep. An undefined custom property makes the declaration invalid at
   computed-value time, and `fill` is inherited, so every untoned plate painted
   pure BLACK: four rack bodies on datacenter, a slab across a third of the
   compliance panel, and the site plates on connectivity. The screenshots taken
   at the time happened to be of the three compositions that use no untoned
   plate, which is how it got past a visual check. */
.ci-art .a-plate {
	fill: var(--ci-raised);
	stroke: var(--ci-fig-line);
	stroke-width: var(--ci-fig-stroke);
}
.ci-art .a-plate--wash   { fill: var(--ci-fig-wash); }
.ci-art .a-plate--soft   { fill: color-mix(in srgb, var(--ci-fig-accent) 16%, transparent); stroke: none; }
.ci-art .a-plate--accent { fill: var(--ci-fig-accent); stroke: none; }
.ci-art .a-ring,
.ci-art .a-arc {
	fill: none;
	stroke: var(--ci-fig-line);
	stroke-width: var(--ci-fig-stroke);
	stroke-linecap: round;
	stroke-linejoin: round;
}
.ci-art .a-ring--accent,
.ci-art .a-arc--accent { stroke: var(--ci-fig-accent); stroke-width: 2.25px; }
.ci-art .a-ring--soft,
.ci-art .a-arc--soft   { stroke: color-mix(in srgb, var(--ci-fig-accent) 45%, transparent); }
.ci-art .a-arc--dashed { stroke-dasharray: 5 6; }
.ci-art .a-node        { fill: var(--ci-fig-ink); stroke: none; }
.ci-art .a-node--accent { fill: var(--ci-fig-accent); }

/* The band's art frame already supplies a surface and padding, which would
   read as a mount around a picture that is meant to fill it. */
.ci-figband__art--cover {
	padding: 0;
	background: none;
	border: 0;
	overflow: hidden;
}
.ci-figband__art--cover .ci-art { aspect-ratio: 11 / 7; }
.ci-figband__caption {
	margin: .85rem 0 0;
	font-size: .875rem;
	color: var(--ci-text-3);
}

/* ---- The diagram in the hero ------------------------------------------- */

/* The hero panel is ink in both modes, so the figure has to take the
   on-dark tokens there rather than the page's. Without this the diagram
   drew dark-on-dark and was very nearly invisible. */
.ci-hero__panel-inner--figure {
	--ci-fig-ink: var(--ci-on-dark-2);
	--ci-fig-line: var(--ci-on-dark-line);
	--ci-fig-wash: rgba(255, 255, 255, .05);
	--ci-fig-accent: var(--ci-accent);
	--ci-fig-on-accent: var(--ci-ink-900);
	--ci-fig-label: var(--ci-on-dark-3);
	--ci-fig-label-strong: #fff;
	display: grid;
	gap: 1.1rem;
}
/* Capped, because the slot is not one size.
   Above 1100px the hero's aside is about 290px wide. Below it the grid stacks
   and the aside takes the full wrap -- 826px, measured at 950 -- and a figure
   drawn at 440 user units then renders at 1.9x, which took the labels to 39px
   and made the diagram shout. Capping the figure keeps one scale either side
   of the breakpoint instead of needing a second label value to undo the
   first. */
.ci-figure--hero { margin: 0; max-width: 340px; margin-inline: auto; }
.ci-figure--hero .ci-figure__svg { max-height: 268px; }
/* --fig-label is a MULTIPLIER, not a divisor: the rule it feeds is
   `font-size: calc(var(--fig-label) * 1px)` in SVG user units, so a larger
   number is larger text. The first attempt at this set 11.5 against
   --tight's 15 on the stated belief that it was a divisor, and so made the
   hero labels about 23% SMALLER than the default it was written to fix --
   roughly 7.6 rendered pixels once the 440-unit viewBox is drawn at the
   aside's ~290px.
   The system's own ladder has the polarity the right way round and says so:
   it RAISES the value as the slot narrows (--tight is 15, and the 880px and
   520px queries go to 16, 17, 18). The hero's slot is narrower than any of
   those, so it takes a higher value again. 20 lands the labels near 13px at
   1440. */
.ci-figure--hero.ci-figure--tight { --fig-label: 20; }

/* ---- Platform panels (Partners) ---------------------------------------- */

.ci-platforms {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
	gap: var(--ci-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}
.ci-platform { display: flex; flex-direction: column; gap: .75rem; }
.ci-platform__art {
	border-radius: var(--ci-r);
	overflow: hidden;
	background: var(--ci-fig-wash);
	aspect-ratio: 43 / 28;
}
.ci-platform__svg { display: block; width: 100%; height: 100%; }
/* Same primitives as the service art, same tokens, so the two families read as
   one hand -- which is the point of not using vendor marks: three logos from
   three design systems never would. */
.ci-platform__art .a-grid { fill: none; stroke: var(--ci-fig-line); stroke-width: 1; opacity: .5; }
.ci-platform__art .a-plate { fill: var(--ci-raised); stroke: var(--ci-fig-line); stroke-width: var(--ci-fig-stroke); }
.ci-platform__art .a-plate--wash { fill: var(--ci-fig-wash); }
.ci-platform__art .a-plate--soft { fill: color-mix(in srgb, var(--ci-fig-accent) 16%, transparent); stroke: none; }
.ci-platform__art .a-plate--accent { fill: var(--ci-fig-accent); stroke: none; }
.ci-platform__art .a-ring,
.ci-platform__art .a-arc {
	fill: none;
	stroke: var(--ci-fig-line);
	stroke-width: var(--ci-fig-stroke);
	stroke-linecap: round;
	stroke-linejoin: round;
}
.ci-platform__art .a-ring--accent,
.ci-platform__art .a-arc--accent { stroke: var(--ci-fig-accent); stroke-width: 2.25px; }
.ci-platform__art .a-ring--soft,
.ci-platform__art .a-arc--soft { stroke: color-mix(in srgb, var(--ci-fig-accent) 45%, transparent); }
.ci-platform__art .a-node { fill: var(--ci-fig-ink); stroke: none; }
.ci-platform__art .a-node--accent { fill: var(--ci-fig-accent); }
.ci-platform__name { font-size: var(--ci-fs-h3); margin: 0; }
.ci-platform__text { margin: 0; color: var(--ci-text-2); font-size: .9375rem; }

@media print {
	/* Reset the whole token layer, not just body. Printing while in dark mode
	   otherwise puts near-black surfaces and light text on white paper. Both
	   branches are needed: the [data-theme="dark"] one to outrank the switch
	   block at equal specificity, the bare :root for the light case. Which
	   also means this block must stay LAST in the file: it beats the switch
	   block on source order alone, so appending another [data-theme] block
	   after it silently reverts printing from dark mode.

	   This list is a deliberate subset, not an oversight. The dark blocks set
	   54 tokens and this resets 31. The other 23 are all reached another way:
	   --ci-header-*, --ci-drawer-*, --ci-scrim, --ci-decor-*, --ci-closing-bg,
	   --ci-switch-thumb, --ci-control-accent and --ci-chip-on* belong to
	   components this block sets to display:none; --ci-hero-bg and
	   --ci-ink-700/800/900 are overridden directly by the `background: none
	   !important` rules below; --ci-hover-surface and --ci-btn-fill-hover only
	   exist in a hover state. The shadows are left as-is because a dark shadow
	   on white paper is a soft grey, not a defect.

	   So the rule for anyone adding a token to the dark blocks: if its
	   component still renders in print, it belongs here too. --ci-band and
	   --ci-band-line are the reason that rule is written down -- they were
	   added without a reset, and .ci-section--alt is not display:none, so an
	   alt band printed from dark mode came out near-black. */
	:root,
	:root[data-theme="dark"] {
		color-scheme: light;
		--ci-text: #10192c;
		--ci-text-2: #44536c;
		--ci-text-3: #596880;
		--ci-paper: #fff;
		--ci-paper-2: #fff;
		--ci-paper-3: #fff;
		--ci-raised: #fff;
		--ci-line: #ccc;
		--ci-line-2: #ddd;
		--ci-elev-bd: #ccc;
		--ci-elev-bd-soft: #ccc;
		--ci-band-edge: #ccc;
		/* Section bands and surface hairlines. These matter more than most of
		   the list: their components are NOT display:none in print, so without
		   a reset an alt band printed from dark mode comes out near-black. */
		--ci-band: #fff;
		--ci-band-line: #ccc;
		/* Section 13's own band and the diagram palette, for the same reason:
		   a --tint band and every figure on the page still render in print. */
		--ci-tint: #fff;
		--ci-tint-line: #ccc;
		--ci-fig-ink: #44536c;
		--ci-fig-line: #ccc;
		--ci-fig-wash: #f4f4f4;
		--ci-fig-accent: var(--ci-brand-dark);
		--ci-fig-on-accent: #fff;
		--ci-fig-label: #596880;
		--ci-fig-label-strong: #10192c;
		--ci-surface-bd-hover: #ccc;
		--ci-chip-bd: #ccc;
		/* --ci-brand-dark never flips and still honours the Customizer, so
		   links keep the brand hue instead of snapping to the stock blue. */
		--ci-brand-fg: var(--ci-brand-dark);
		--ci-brand-700: var(--ci-brand-dark);
		--ci-quote-rule: var(--ci-brand-dark);
		--ci-brand-100: #fff;
		--ci-brand-050: #fff;
		--ci-field-bg: #fff;
		--ci-field-bg-focus: #fff;
		--ci-danger: #c2353b;
		--ci-danger-bg: #fff;
		--ci-success: #127049;
		--ci-success-fg: #0d5c3c;
		--ci-success-bg: #fff;
		--ci-success-line: #ccc;
		/* the permanently dark bands become paper on paper */
		--ci-on-dark: #10192c;
		--ci-on-dark-2: #44536c;
		--ci-on-dark-3: #596880;
		--ci-on-dark-line: #ccc;
		--ci-on-brand: #000;
		--ci-media-filter: none;
		--ci-map-filter: grayscale(.25) contrast(1.05);
	}

	.ci-header, .ci-topbar, .ci-footer, .ci-drawer, .ci-closing, .ci-decor,
	.ci-hero__marquee, .ci-mode { display: none !important; }
	body.ci { color: #000; background: #fff; }
	.ci-hero { background: none !important; color: #000; }
	.ci-hero__title, .ci-hero__lead { color: #000 !important; }

	/* Readable even with "Background graphics" switched on. */
	.ci-section--ink,
	.ci-surface--ink,
	.ci-panels__item,
	.ci-mega__aside { background: none !important; }

	/* Hardcoded light-on-dark foregrounds that no token reset reaches. */
	.ci-section--ink h2, .ci-section--ink h3,
	.ci-surface--ink, .ci-surface--ink p,
	.ci-sec-head--light .ci-sec-head__title,
	.ci-crumbs [aria-current], .ci-crumbs a:hover,
	.ci-btn, .ci-eyebrow--light,
	.ci-steps--light .ci-step__num,
	.ci-hero__chips li,
	/* The panel row paints itself from --ci-ink-*, which "Background graphics"
	   drops by default -- so its four labels and the open lead, both
	   hardcoded #fff, printed white on white in BOTH modes. */
	.ci-panels__label,
	.ci-panels__lead,
	.ci-note--light { color: #000 !important; }

	/* The travelling tab indicator is a background, which "Background
	   graphics" drops by default -- and .has-ink has already set the selected
	   tab's own border to transparent, so a printed tab set had no selected
	   state at all. Same two lines as the forced-colors fallback. */
	.ci-tabs__list.has-ink::after { display: none; }
	.ci-tabs__list .ci-tabs__tab[aria-selected="true"],
	.ci-folder .ci-tabs__tab[aria-selected="true"] { border-bottom: 2px solid currentColor; }

	/* Motion, on paper.
	   Printing does not run the page's entrance animations, and dropping an
	   animation returns the element to its base style -- which for everything
	   in section 15 is the INVISIBLE start state. So neutralising the motion is
	   not enough on its own; each start state has to be undone as well, or a
	   printed service page comes out as a stack of headings over blank space
	   with every diagram missing its lines.

	   The figure parts need nothing here. With the base hiding gone from 15c,
	   dropping their animation leaves them at their own styles, which is
	   visible — so [data-reveal] itself is the only thing left that hides with
	   a base style, and the only thing that has to be undone. */
	*,
	*::before,
	*::after {
		animation: none !important;
		transition: none !important;
	}
	[data-reveal] {
		opacity: 1 !important;
		transform: none !important;
		clip-path: none !important;
	}
}
