/* =========================================================================
   nusoft.com.co — design system

   Built from the one thing the brand actually owns: the isotype. It is three
   straight-edged shapes whose every edge is vertical, horizontal or at exactly
   45 degrees, in a single deep petrol. So the site is built on the same three
   facts —

     the diagonal   every accent runs at 45deg: the beams behind the hero, the
                    sweep across a card on hover, the cut corner of a panel,
                    the rule that closes a section. Nothing on this site is
                    decorated with a curve the mark would not have drawn.
     the lattice    the mark is a figure on a 5x5 grid, so the page has a grid
                    under it too (data-texture), and the hero is a lattice of
                    nodes and links — a nucleus, which is what "núcleo" means.
     one signal     petrol is the ground, and exactly one bright colour is
                    allowed to mean something on it. Which one is an axis
                    (data-accent), because it is the cheapest way to show a
                    client four different companies in four seconds.

   THE RULE OF THIS FILE. Nothing below the token block contains a raw colour.
   Everything is a custom property, so a theme is one block of redefinitions
   and the twenty-odd other axes compose freely with it — any theme, any
   accent, any corner shape, any typeface, all at once, all from one attribute
   each on <html>. What the axes are, and what the site opens as, is NOT
   written here: assets/config/appearance.json is the single source.
   ========================================================================= */

/* ---------------------------------------------------------------- fonts ---- */
/* Self-hosted, every one of them: the site loads no font from a CDN. Two
   subsets each with the ranges Google publishes, so a Latin page never fetches
   the extended cut. */
@font-face {
	font-family: 'Figtree';
	font-style: normal;
	font-weight: 300 900;
	font-display: swap;
	src: url('../fonts/figtree-latin-ext.woff2') format('woff2');
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
		U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
		U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: 'Figtree';
	font-style: normal;
	font-weight: 300 900;
	font-display: swap;
	src: url('../fonts/figtree-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
		U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Newsreader';
	font-style: normal;
	font-weight: 200 800;
	font-display: swap;
	src: url('../fonts/newsreader-latin-ext.woff2') format('woff2');
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
		U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
		U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: 'Newsreader';
	font-style: normal;
	font-weight: 200 800;
	font-display: swap;
	src: url('../fonts/newsreader-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
		U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Instrument Serif';
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url('../fonts/instrumentserif-latin-ext.woff2') format('woff2');
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
		U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
		U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: 'Instrument Serif';
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url('../fonts/instrumentserif-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
		U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'Prata';
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url('../fonts/prata-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
		U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: 'EB Garamond';
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url('../fonts/ebgaramond-latin-ext.woff2') format('woff2');
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
		U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
		U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: 'EB Garamond';
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url('../fonts/ebgaramond-latin.woff2') format('woff2');
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
		U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
		U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------- palette --- */
:root {
	/* PETROL — the trademark colour and the ramp built around it. #00232f is
	   the client value; everything else is that hue opened up and darkened so
	   a theme can be built without ever inventing a colour. */
	--petrol-50:  #eef6f8;
	--petrol-100: #d6e8ed;
	--petrol-200: #a9ccd7;
	--petrol-300: #6ea3b4;
	--petrol-400: #3d7488;
	--petrol-500: #1f5468;
	--petrol-600: #0f3d4e;
	--petrol-700: #062f3d;
	--petrol-800: #00232f;   /* client value — the trademark */
	--petrol-900: #001820;
	--petrol-950: #000f14;

	/* SOLAR — the company's secondary, inherited from the educa.city brand.
	   #F7AC11 is the client value; the rest is that hue opened and deepened so
	   the `solar` theme and the `amber` accent can both be built from it
	   without anyone inventing a second yellow. */
	--solar-100: #fdf0d2;
	--solar-200: #fbdc98;
	--solar-300: #f9c452;
	--solar-400: #f7ac11;   /* client value — the secondary */
	--solar-500: #d18d05;
	--solar-600: #a06b00;
	--solar-700: #6d4900;

	/* SLATE — the neutral. Deliberately cool, so it sits under petrol rather
	   than beside it. */
	--slate-50:  #f4f6f9;
	--slate-100: #e6eaf1;
	--slate-200: #ccd4e0;
	--slate-300: #a2aebf;
	--slate-400: #6e7c92;
	--slate-500: #566379;
	--slate-600: #414d60;
	--slate-700: #2f3948;
	--slate-800: #1f2733;
	--slate-900: #141a23;

	/* type + shape, theme-independent.
	   The site's own faces are subset to latin and latin-ext, so the languages
	   it ships in do not all fit in them. Font fallback is per glyph: Latin
	   copy still renders in Figtree while other scripts fall through to the
	   reader's system faces — named here rather than left to chance, because
	   the default for a missing script is usually the worst face installed. */
	--stack-sans: 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
		Helvetica, Arial,
		'Yu Gothic UI', 'Hiragino Sans', Meiryo,                  /* ja */
		'Microsoft YaHei', 'PingFang SC', 'Noto Sans CJK SC',     /* zh */
		'Malgun Gothic', 'Apple SD Gothic Neo', 'Noto Sans KR',   /* ko */
		'Nirmala UI', 'Kohinoor Devanagari', 'Noto Sans Devanagari', /* hi */
		sans-serif;
	--serif-tail: Georgia, 'Times New Roman',
		'Yu Mincho', 'Hiragino Mincho ProN',                      /* ja */
		'Songti SC', SimSun, 'Noto Serif CJK SC',                 /* zh */
		Batang, 'Apple SD Gothic Neo', 'Noto Serif KR',           /* ko */
		'Noto Serif Devanagari', Kokila, 'Nirmala UI',            /* hi */
		serif;
	--stack-serif:      'Newsreader',       var(--serif-tail);
	--stack-instrument: 'Instrument Serif', var(--serif-tail);
	--stack-prata:      'Prata',            var(--serif-tail);
	--stack-garamond:   'EB Garamond',      var(--serif-tail);
	--sans: var(--stack-sans);
	/* The technical voice: version numbers, stack names, endpoint samples, the
	   figures in a statistic. System faces only — a mono web font would be a
	   fourth download for text measured in dozens of characters. */
	--mono: ui-monospace, 'SFMono-Regular', 'JetBrains Mono', 'Cascadia Mono',
		Menlo, Consolas, 'Liberation Mono', monospace;

	/* --surface-3 is dark in every theme, so what sits on it is always light.
	   That is not a per-theme decision, so it does not live in a theme. */
	--on-dark: #ffffff;

	--radius:     14px;
	--radius-sm:  9px;
	--radius-lg:  22px;
	/* Pictures round on their own axis: a client may want square photographs
	   inside rounded panels, or the reverse. Its own token, deliberately —
	   deriving it from --radius means squaring the cards squares the pictures
	   with them, and the two axes stop being independent. */
	--radius-img: 14px;
	--radius-card: 14px;

	--wrap:        1240px;
	--wrap-narrow: 760px;
	--ease:        cubic-bezier(.22,.61,.36,1);
	--ease-out:    cubic-bezier(.16,1,.3,1);

	/* vertical rhythm — the density axis moves both */
	--sec-pad:  104px;
	--sec-gap:  34px;

	/* the mark's own angle, used by every diagonal in the site */
	--tilt: 45deg;

	/* paper grain — one inline turbulence tile, reused by the noise texture */
	--grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------- accent ---
   The one bright colour on the page. Each option publishes two values — one
   that works on a light ground and one that works on a dark one — and the
   theme decides which of the two --accent resolves to. That is what lets the
   two axes compose: four accents times seven themes is twenty-eight looks, and
   not one of them needs a rule of its own.

   `--a-on` is what goes ON the accent when it is used as a fill, and it is
   never white for the light accents — amber with white text is the single most
   common contrast failure in this kind of palette.

   Each `--a-deep` is dark enough to clear 4.5:1 against the lightest and the
   softest grounds any light theme uses. That is a real constraint, not a
   nicety: these colours carry the eyebrows, which are small, uppercase and
   tracked out — the least legible text on the site. */
[data-accent='cyan'] {
	--a-bright: #2ad2e8;
	--a-deep:   #076576;
	--a-soft-l: #e4f7fb;
	--a-soft-d: rgba(42,210,232,.13);
	--a-on:     #00232f;
	--a-glow:   rgba(42,210,232,.35);
}
[data-accent='violet'] {
	--a-bright: #a99bff;
	--a-deep:   #5238c9;
	--a-soft-l: #efedff;
	--a-soft-d: rgba(169,155,255,.15);
	--a-on:     #16113a;
	--a-glow:   rgba(169,155,255,.34);
}
/* the company's own secondary, straight from the brand files */
[data-accent='amber'] {
	--a-bright: var(--solar-400);
	--a-deep:   #7d5300;
	--a-soft-l: #fdf0d2;
	--a-soft-d: rgba(247,172,17,.16);
	--a-on:     #2a1c00;
	--a-glow:   rgba(247,172,17,.34);
}
[data-accent='lime'] {
	--a-bright: #8ee86a;
	--a-deep:   #35691c;
	--a-soft-l: #eefbe6;
	--a-soft-d: rgba(142,232,106,.14);
	--a-on:     #10240a;
	--a-glow:   rgba(142,232,106,.30);
}

/* ============================== THEMES ====================================
   Seven grounds. Three are light and four are dark, and the difference between
   those two families is the only thing a component ever needs to know — which
   is why --accent, --scrim, --on-accent and the --on-media-* set are declared
   per theme, and nothing else in this file ever asks "is it dark?".

   THE --on-media-* SET is the ink the see-through header wears while it floats
   over the hero. A light theme draws a light hero, so there it is the theme's
   own ink; a dark theme draws a dark one, so there it is white. Getting this
   wrong is invisible in the theme you are working in and total in the other. */
:root {
	/* the light-theme answer, and the default: the hero is the page */
	--on-media:         var(--ink);
	--on-media-2:       var(--ink-2);
	--on-media-muted:   var(--muted);
	--on-media-line:    var(--line);
	--on-media-line-2:  var(--line-strong);
	--on-media-surface: var(--surface-2);
}
[data-theme='deep'],
[data-theme='core'],
[data-theme='solar'],
[data-theme='black'] {
	--on-media:         var(--on-dark);
	--on-media-2:       rgba(255,255,255,.86);
	--on-media-muted:   rgba(255,255,255,.66);
	--on-media-line:    rgba(255,255,255,.24);
	--on-media-line-2:  rgba(255,255,255,.42);
	--on-media-surface: rgba(255,255,255,.12);
}

/* ------------------------------------------------------------- theme: ice -- */
:root,
[data-theme='ice'] {
	--bg:          #f6fafb;
	--bg-2:        #eaf1f4;
	--surface:     #ffffff;
	--surface-2:   #f1f6f8;
	--surface-3:   var(--petrol-800);
	--ink:         #062431;
	--ink-2:       var(--petrol-600);
	--muted:       #4e6270;
	--line:        #dde7ec;
	--line-strong: #c2d3da;

	--brand:        var(--petrol-800);
	--brand-strong: var(--petrol-900);
	--brand-soft:   var(--petrol-50);
	--on-brand:     #ffffff;

	--accent:       var(--a-deep);
	--accent-lit:   var(--a-bright);   /* the same colour where it may glow */
	--accent-soft:  var(--a-soft-l);
	--on-accent:    #ffffff;
	--glow:         var(--a-glow);

	--code:        var(--petrol-600);
	--scrim:       linear-gradient(180deg, rgba(0,20,27,.72) 0%, rgba(0,20,27,.42) 46%, rgba(0,20,27,.84) 100%);
	--shadow:      0 26px 60px -30px rgba(0,35,47,.36);
	--shadow-sm:   0 10px 26px -16px rgba(0,35,47,.34);
	--texture-op:  .05;
	--grain-op:    .022;
	color-scheme: light;
}

/* ----------------------------------------------------------- theme: paper -- */
[data-theme='paper'] {
	--bg:          #f8f6f1;
	--bg-2:        #efebe3;
	--surface:     #fffefb;
	--surface-2:   #f3efe8;
	--surface-3:   var(--petrol-800);
	--ink:         #1c2429;
	--ink-2:       #3d4b52;
	--muted:       #5f6569;
	--line:        #e5ded3;
	--line-strong: #cfc5b6;

	--brand:        var(--petrol-800);
	--brand-strong: var(--petrol-900);
	--brand-soft:   #eef2f1;
	--on-brand:     #ffffff;

	--accent:       var(--a-deep);
	--accent-lit:   var(--a-bright);
	--accent-soft:  var(--a-soft-l);
	--on-accent:    #ffffff;
	--glow:         var(--a-glow);

	--code:        var(--petrol-600);
	--scrim:       linear-gradient(180deg, rgba(24,20,14,.70) 0%, rgba(24,20,14,.40) 46%, rgba(24,20,14,.84) 100%);
	--shadow:      0 26px 60px -30px rgba(38,32,22,.30);
	--shadow-sm:   0 10px 26px -16px rgba(38,32,22,.28);
	--texture-op:  .05;
	--grain-op:    .04;
	color-scheme: light;
}

/* ----------------------------------------------------------- theme: steel -- */
[data-theme='steel'] {
	--bg:          #eef2f7;
	--bg-2:        #e3e9f1;
	--surface:     #ffffff;
	--surface-2:   #e9eef5;
	--surface-3:   var(--slate-800);
	--ink:         #1b2431;
	--ink-2:       var(--slate-600);
	--muted:       #596475;
	--line:        #d8e0ea;
	--line-strong: #bcc7d6;

	--brand:        var(--slate-700);
	--brand-strong: var(--slate-800);
	--brand-soft:   var(--slate-50);
	--on-brand:     #ffffff;

	--accent:       var(--a-deep);
	--accent-lit:   var(--a-bright);
	--accent-soft:  var(--a-soft-l);
	--on-accent:    #ffffff;
	--glow:         var(--a-glow);

	--code:        var(--slate-600);
	--scrim:       linear-gradient(180deg, rgba(15,20,28,.70) 0%, rgba(15,20,28,.40) 46%, rgba(15,20,28,.84) 100%);
	--shadow:      0 26px 60px -30px rgba(20,26,35,.32);
	--shadow-sm:   0 10px 26px -16px rgba(20,26,35,.30);
	--texture-op:  .06;
	--grain-op:    .02;
	color-scheme: light;
}

/* ------------------------------------------------------------ theme: deep --
   The trademark colour as the ground. This is what the site opens as: the mark
   was drawn for this, and a company that builds systems people run their
   operation on should not look like a brochure. */
[data-theme='deep'] {
	--bg:          #00232f;
	--bg-2:        #001b25;
	--surface:     #04303e;
	--surface-2:   #073c4d;
	--surface-3:   #001820;
	--ink:         #e8f3f6;
	--ink-2:       #b4ccd4;
	--muted:       #7b9aa5;
	--line:        rgba(255,255,255,.11);
	--line-strong: rgba(255,255,255,.22);

	--brand:        var(--petrol-100);
	--brand-strong: #ffffff;
	--brand-soft:   rgba(214,232,237,.10);
	--on-brand:     var(--petrol-900);

	--accent:       var(--a-bright);
	--accent-lit:   var(--a-bright);
	--accent-soft:  var(--a-soft-d);
	--on-accent:    var(--a-on);
	--glow:         var(--a-glow);

	--code:        var(--petrol-200);
	--scrim:       linear-gradient(180deg, rgba(0,12,17,.58) 0%, rgba(0,12,17,.30) 46%, rgba(0,12,17,.88) 100%);
	--shadow:      0 30px 70px -34px rgba(0,0,0,.8);
	--shadow-sm:   0 12px 30px -18px rgba(0,0,0,.72);
	--texture-op:  .07;
	--grain-op:    .05;
	color-scheme: dark;
}

/* ------------------------------------------------------------ theme: core --
   The trademark colour taken down as far as it goes and still be a colour:
   every ground here is a shade of #00232f rather than a neutral. Where `deep`
   is the brand at working strength, this is the brand at rest — the darkest
   the site gets without turning to black, which is what `black` is for. */
[data-theme='core'] {
	--bg:          #00141b;
	--bg-2:        #000d12;
	--surface:     #001e28;
	--surface-2:   #002a37;
	--surface-3:   #00080b;
	--ink:         #dcecf1;
	--ink-2:       #9fc0ca;
	--muted:       #6b8d99;
	--line:        rgba(146,201,216,.13);
	--line-strong: rgba(146,201,216,.26);

	--brand:        var(--petrol-200);
	--brand-strong: #ffffff;
	--brand-soft:   rgba(169,204,215,.10);
	--on-brand:     var(--petrol-950);

	--accent:       var(--a-bright);
	--accent-lit:   var(--a-bright);
	--accent-soft:  var(--a-soft-d);
	--on-accent:    var(--a-on);
	--glow:         var(--a-glow);

	--code:        var(--petrol-300);
	--scrim:       linear-gradient(180deg, rgba(0,8,11,.62) 0%, rgba(0,8,11,.32) 46%, rgba(0,8,11,.90) 100%);
	--shadow:      0 30px 70px -34px rgba(0,0,0,.85);
	--shadow-sm:   0 12px 30px -18px rgba(0,0,0,.78);
	--texture-op:  .09;
	--grain-op:    .05;
	color-scheme: dark;
}

/* ----------------------------------------------------------- theme: solar --
   Petrol warmed through, built around #F7AC11 — the secondary that comes from
   the educa.city brand. The grounds carry a little of it, the inks are warm
   rather than cool, and --brand IS the amber, so the theme reads as itself
   even under another accent. Its intended pairing is the `amber` accent, which
   is that exact colour; the other three still compose with it. */
[data-theme='solar'] {
	--bg:          #06212b;
	--bg-2:        #04191f;
	--surface:     #0b2d39;
	--surface-2:   #123a48;
	--surface-3:   #030f14;
	--ink:         #fcf4e6;
	--ink-2:       #e2cfae;
	--muted:       #a2907a;
	--line:        rgba(247,172,17,.16);
	--line-strong: rgba(247,172,17,.34);

	--brand:        var(--solar-400);
	--brand-strong: var(--solar-300);
	--brand-soft:   rgba(247,172,17,.12);
	--on-brand:     #2a1c00;

	--accent:       var(--a-bright);
	--accent-lit:   var(--a-bright);
	--accent-soft:  var(--a-soft-d);
	--on-accent:    var(--a-on);
	--glow:         var(--a-glow);

	--code:        var(--solar-300);
	--scrim:       linear-gradient(180deg, rgba(6,20,26,.62) 0%, rgba(6,20,26,.32) 46%, rgba(6,20,26,.90) 100%);
	--shadow:      0 30px 70px -34px rgba(0,0,0,.8);
	--shadow-sm:   0 12px 30px -18px rgba(0,0,0,.72);
	--texture-op:  .08;
	--grain-op:    .05;
	color-scheme: dark;
}

/* ----------------------------------------------------------- theme: black -- */
[data-theme='black'] {
	--bg:          #07090b;
	--bg-2:        #030405;
	--surface:     #101418;
	--surface-2:   #171d23;
	--surface-3:   #000000;
	--ink:         #edf1f3;
	--ink-2:       #bcc6cd;
	--muted:       #7d878f;
	--line:        rgba(255,255,255,.10);
	--line-strong: rgba(255,255,255,.20);

	--brand:        #ffffff;
	--brand-strong: #ffffff;
	--brand-soft:   rgba(255,255,255,.07);
	--on-brand:     #07090b;

	--accent:       var(--a-bright);
	--accent-lit:   var(--a-bright);
	--accent-soft:  var(--a-soft-d);
	--on-accent:    var(--a-on);
	--glow:         var(--a-glow);

	--code:        #9aa6ae;
	--scrim:       linear-gradient(180deg, rgba(0,0,0,.60) 0%, rgba(0,0,0,.32) 46%, rgba(0,0,0,.90) 100%);
	--shadow:      0 30px 70px -34px rgba(0,0,0,.9);
	--shadow-sm:   0 12px 30px -18px rgba(0,0,0,.8);
	--texture-op:  .08;
	--grain-op:    .06;
	color-scheme: dark;
}

/* Copies of the theme's own colours, taken where nothing has shadowed them
   yet. The see-through header redefines --ink and friends ON ITSELF so its
   children follow with no extra rules — but its dropdown panels are solid, not
   see-through, and need the theme back. These let them ask for it. They
   resolve per theme because the substitution happens on :root. */
:root {
	--ink-root:       var(--ink);
	--ink-2-root:     var(--ink-2);
	--muted-root:     var(--muted);
	--surface-root:   var(--surface);
	--surface-2-root: var(--surface-2);
	--line-root:      var(--line);
	--accent-root:    var(--accent);
	--accent-soft-root: var(--accent-soft);
	--on-accent-root: var(--on-accent);
}

/* ============================ COMPOSABLE AXES ============================= */

/* ------------------------------------------------------------ data-font ---- */
[data-font='sans']       { --sans: var(--stack-sans); --head: var(--stack-sans); }
[data-font='serif']      { --sans: var(--stack-sans); --head: var(--stack-serif); }
[data-font='instrument'] { --sans: var(--stack-sans); --head: var(--stack-instrument); }
[data-font='prata']      { --sans: var(--stack-sans); --head: var(--stack-prata); }
[data-font='garamond']   { --sans: var(--stack-sans); --head: var(--stack-garamond); }
:root { --head: var(--stack-sans); }
/* The display faces carry their weight in their drawing, not in a bold, and
   they set tighter than a grotesque of the same size. */
[data-font='instrument'] h1, [data-font='instrument'] h2,
[data-font='prata'] h1, [data-font='prata'] h2 { font-weight: 400; letter-spacing: -.01em; }
[data-font='serif'] h1, [data-font='serif'] h2,
[data-font='garamond'] h1, [data-font='garamond'] h2 { letter-spacing: -.012em; }

/* ----------------------------------------------------------- data-shape ---- */
[data-shape='pill']   { --radius: 999px; --radius-sm: 999px; --radius-lg: 999px; }
[data-shape='soft']   { --radius: 14px;  --radius-sm: 9px;   --radius-lg: 22px; }
[data-shape='square'] { --radius: 0px;   --radius-sm: 0px;   --radius-lg: 0px; }

[data-card='card-round'] { --radius-card: 14px; }
[data-card='card-sharp'] { --radius-card: 0px; }
[data-pic='pic-round']   { --radius-img: 14px; }
[data-pic='pic-sharp']   { --radius-img: 0px; }

/* ----------------------------------------------------------- data-photo ----
   Colour photography, or greyscale that comes back to colour on hover. The
   team portraits on #/about carry that treatment as their own baseline rather
   than as an option — see `.person-pic` — so this axis reaches the product
   screenshots, which are still marked placeholders, and anything else that
   arrives later. */
[data-photo='mono'] img,
[data-photo='mono'] .photo,
[data-photo='mono'] .ph {
	filter: grayscale(1);
	transition: filter .45s var(--ease);
}
[data-photo='mono'] img:hover,
[data-photo='mono'] .photo:hover,
[data-photo='mono'] .ph:hover { filter: grayscale(0); }

/* ------------------------------------------------------------- data-pad ---- */
[data-pad='normal'] { --btn-pad: 13px 24px; --btn-pad-sm: 9px 17px; }
[data-pad='small']  { --btn-pad: 10px 19px; --btn-pad-sm: 7px 14px; }
[data-pad='xs']     { --btn-pad: 8px 15px;  --btn-pad-sm: 6px 12px; }

/* --------------------------------------------------------- data-density ---- */
[data-density='roomy'] { --sec-pad: 104px; --sec-gap: 34px; }
[data-density='tight'] { --sec-pad: 62px;  --sec-gap: 22px; }

/* ------------------------------------------------------------ data-rule ---- */
/* A hairline closing every section — at the mark's angle, so even the divider
   is the logo. */
[data-rule='line'] .band + .band::before,
[data-rule='line'] section + section::before {
	content: '';
	position: absolute;
	inset: 0 auto auto 0;
	width: 100%;
	height: 1px;
	background: repeating-linear-gradient(var(--tilt),
		var(--line) 0 6px, transparent 6px 12px);
}
[data-rule='line'] section { position: relative; }

/* --------------------------------------------------------- data-headw ------ */
[data-headw='narrow'] .section-head { max-width: 660px; }
[data-headw='wide']   .section-head { max-width: none; }

/* ------------------------------------------------------------- data-num ---- */
[data-num='hide'] .step-n,
[data-num='hide'] .cap-n { display: none; }
/* .step is a two-column grid with the number in the first track. Take the
   number out and the body would drop into that 92px track and wrap one word
   at a time, so the track goes with it. */
[data-num='hide'] .step { grid-template-columns: minmax(0, 1fr); }

/* ============================== BASE ====================================== */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after {
		animation-duration: .001ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .001ms !important;
	}
}

body {
	margin: 0;
	background: var(--bg);
	color: var(--ink);
	font-family: var(--sans);
	font-size: 17px;
	line-height: 1.62;
	font-synthesis-weight: none;
	-webkit-font-smoothing: antialiased;
	overflow-x: hidden;
}

/* ---------------------------------------------------------- data-texture ---
   The ground the whole site stands on. One fixed layer behind everything, at
   the mark's own angle for `grid` — so scrolling the page moves the content
   across a lattice that stays put, which is the quietest way to say
   "engineered" without printing a circuit board on the background. */
body::before {
	content: '';
	position: fixed;
	inset: 0;
	z-index: 0;
	pointer-events: none;
	opacity: var(--texture-op);
}
[data-texture='flat'] body::before { display: none; }
[data-texture='grid'] body::before {
	background-image:
		repeating-linear-gradient(var(--tilt), var(--ink) 0 1px, transparent 1px 46px),
		repeating-linear-gradient(calc(var(--tilt) * -1), var(--ink) 0 1px, transparent 1px 46px);
}
[data-texture='dots'] body::before {
	background-image: radial-gradient(var(--ink) .8px, transparent .9px);
	background-size: 22px 22px;
}
[data-texture='noise'] body::before {
	background-image: var(--grain);
	opacity: var(--grain-op);
}

h1, h2, h3, h4 {
	font-family: var(--head);
	margin: 0 0 .5em;
	line-height: 1.08;
	letter-spacing: -.022em;
	font-weight: 800;
	text-wrap: balance;
}
h1 { font-size: clamp(2.5rem, 5.4vw, 4.15rem); }
h2 { font-size: clamp(1.9rem, 3.5vw, 2.85rem); }
h3 { font-size: 1.24rem; letter-spacing: -.012em; }
h4 { font-size: .95rem; letter-spacing: -.006em; }
p  { margin: 0 0 1em; }
a  { color: inherit; }

::selection { background: var(--accent); color: var(--on-accent); }

img, svg, video { max-width: 100%; }
img { height: auto; border-radius: var(--radius-img); }

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

hr { border: 0; height: 1px; background: var(--line); margin: 40px 0; }

code, .mono, kbd {
	font-family: var(--mono);
	font-size: .88em;
	letter-spacing: -.01em;
}

.wrap {
	width: min(var(--wrap), 100% - 44px);
	margin-inline: auto;
	position: relative;
	z-index: 1;
}
.narrow { max-width: var(--wrap-narrow); }

main { position: relative; z-index: 1; }

section { padding: var(--sec-pad) 0; position: relative; }
section[hidden] { display: none; }

/* the two ground shifts a section may ask for */
.band-soft { background: var(--bg-2); }
.band-dark {
	background: var(--surface-3);
	--ink: var(--on-dark);
	--ink-2: rgba(255,255,255,.80);
	--muted: rgba(255,255,255,.60);
	--line: rgba(255,255,255,.13);
	--line-strong: rgba(255,255,255,.24);
	--surface: rgba(255,255,255,.05);
	--surface-2: rgba(255,255,255,.08);
	--accent: var(--a-bright);
	--on-accent: var(--a-on);
	--accent-soft: var(--a-soft-d);
	--brand: #ffffff;
	--on-brand: var(--petrol-900);
	color: var(--ink);
}

.eyebrow {
	font-family: var(--sans);
	font-size: .74rem;
	font-weight: 700;
	letter-spacing: .22em;
	text-transform: uppercase;
	color: var(--accent);
	margin: 0 0 16px;
	display: flex;
	align-items: center;
	gap: 11px;
}
/* the diagonal tick that opens every eyebrow — a single stroke of the mark */
.eyebrow::before {
	content: '';
	width: 16px;
	height: 16px;
	flex: none;
	background: currentColor;
	clip-path: polygon(0 100%, 72% 0, 100% 0, 28% 100%);
}
.section-head { margin-bottom: 48px; }
.section-head .lead { max-width: 62ch; }
.section-head.center { text-align: center; margin-inline: auto; max-width: 780px; }
.section-head.center .eyebrow { justify-content: center; }
.section-head.center .lead { margin-inline: auto; }

.lead {
	font-size: 1.14rem;
	line-height: 1.62;
	color: var(--ink-2);
}
.muted { color: var(--muted); }
.small { font-size: .9rem; }

/* the band that splits into a heading column and a content column */
.band-split {
	display: grid;
	grid-template-columns: minmax(260px, 1fr) minmax(0, 1.35fr);
	gap: 56px var(--sec-gap);
	align-items: start;
}
.band-split .band-head { margin: 0; position: sticky; top: 108px; }

/* ============================== BUTTONS =================================== */
.btn {
	--b-bg: transparent;
	--b-ink: var(--ink);
	--b-line: var(--line-strong);
	display: inline-flex;
	align-items: center;
	gap: 10px;
	padding: var(--btn-pad, 13px 24px);
	border-radius: var(--radius);
	border: 1px solid var(--b-line);
	background: var(--b-bg);
	color: var(--b-ink);
	font: inherit;
	font-size: .95rem;
	font-weight: 650;
	letter-spacing: -.005em;
	text-decoration: none;
	cursor: pointer;
	position: relative;
	overflow: hidden;
	transition: transform .28s var(--ease), box-shadow .28s var(--ease),
		background-color .2s linear, color .2s linear, border-color .2s linear;
}
.btn-sm { padding: var(--btn-pad-sm, 9px 17px); font-size: .87rem; }

/* the sweep: a band of light crossing at the mark's angle, once, on hover.
   Not a glow and not a colour change — the logo's own diagonal, moving. */
.btn::after {
	content: '';
	position: absolute;
	inset: -60% -120%;
	background: linear-gradient(var(--tilt), transparent 42%,
		rgba(255,255,255,.34) 50%, transparent 58%);
	transform: translateX(-120%);
	transition: transform .62s var(--ease-out);
	pointer-events: none;
}
.btn:hover { transform: translateY(-2px); }
.btn:hover::after { transform: translateX(120%); }
.btn:active { transform: translateY(0); }

.btn-primary { --b-bg: var(--accent); --b-ink: var(--on-accent); --b-line: var(--accent); }
.btn-primary:hover { box-shadow: 0 12px 34px -14px var(--glow); }
.btn-dark  { --b-bg: var(--surface-3); --b-ink: var(--on-dark); --b-line: var(--surface-3); }
.btn-brand { --b-bg: var(--brand); --b-ink: var(--on-brand); --b-line: var(--brand); }
.btn-ghost { --b-bg: transparent; --b-ink: var(--ink); --b-line: var(--line-strong); }
.btn-ghost:hover { --b-line: var(--accent); --b-ink: var(--accent); }
.btn-on-photo {
	--b-bg: rgba(255,255,255,.10);
	--b-ink: var(--on-dark);
	--b-line: rgba(255,255,255,.42);
	backdrop-filter: blur(6px);
}
.btn-on-photo:hover { --b-bg: rgba(255,255,255,.18); }

/* -------------------------------------------------------------- data-fill --
   Hollow turns every filled button into an outline of itself. It has to reach
   the modifiers, not just .btn, or the primary button stays filled. */
[data-fill='hollow'] .btn-primary,
[data-fill='hollow'] .btn-dark,
[data-fill='hollow'] .btn-brand { background: transparent; }
[data-fill='hollow'] .btn-primary { color: var(--accent); }
[data-fill='hollow'] .btn-dark,
[data-fill='hollow'] .btn-brand { color: var(--ink); border-color: var(--line-strong); }
[data-fill='hollow'] .btn::after { background: linear-gradient(var(--tilt),
	transparent 42%, var(--accent-soft) 50%, transparent 58%); }

/* ----------------------------------------------------------- data-border ---
   A rule under the control instead of an outline around it. */
[data-border='underline'] .btn {
	border-width: 0 0 2px;
	border-radius: 0;
	padding-inline: 4px;
}
[data-border='underline'] .btn-primary { color: var(--accent); background: transparent; }
[data-border='underline'] .btn-dark,
[data-border='underline'] .btn-brand { color: var(--ink); background: transparent; }
[data-border='underline'] .btn:hover { transform: none; border-color: var(--accent); }

.ico { width: 1.05em; height: 1.05em; fill: none; stroke: currentColor; flex: none; }
.btn .ico { transition: transform .3s var(--ease); }
.btn:hover .ico { transform: translateX(3px); }

/* =========================== SITE HEADER ================================== */
.site-header {
	position: sticky;
	top: 0;
	z-index: 60;
	background: color-mix(in srgb, var(--bg) 86%, transparent);
	backdrop-filter: blur(14px) saturate(1.3);
	border-bottom: 1px solid transparent;
	transition: background-color .3s linear, border-color .3s linear;
}
.site-header.scrolled { border-bottom-color: var(--line); }

.header-inner {
	display: flex;
	align-items: center;
	gap: 22px;
	height: 74px;
}

/* ------------------------------------------------------------- the lockup --
   The official artwork, out of the sprite so it inherits `color` — which is
   what makes data-logo-color an axis over the real trademark rather than over
   a lookalike. Two symbols, one shown at a time:

     .brand-full  #i-logotype, isotype + wordmark, aspect 4.1393 (measured
                  from the brand file by tools/build_logotype.py)
     .brand-mark  #i-iso, the mark alone, square

   Sizing is by HEIGHT in both cases, so switching lockup changes the width the
   logo occupies and never its optical size. */
.brand {
	display: inline-flex;
	align-items: center;
	text-decoration: none;
	color: var(--ink);
	flex: none;
	--logo-h: 38px;
	line-height: 0;
}
[data-logo-size='sm'] .brand { --logo-h: 30px; }
[data-logo-size='md'] .brand { --logo-h: 38px; }
[data-logo-size='lg'] .brand { --logo-h: 46px; }

/* --logo-h is the height of the MARK, in both lockups — not the height of the
   artwork. The full lockup's box is taller than its mark, because the acute on
   the Ú rises above it (87.65 units of box for 74 of mark, both measured from
   the brand file). Sizing by the box instead would shrink the mark by 16% the
   moment the wordmark was switched on, and the two lockups would no longer
   look like the same logo at the same size. */
.brand-full,
.brand-mark {
	display: block;
	fill: currentColor;
	stroke: none;
}
.brand-full {
	height: calc(var(--logo-h) * 1.1845);              /* 87.65 / 74 */
	width: calc(var(--logo-h) * 1.1845 * 4.1393);
}
.brand-mark { height: var(--logo-h); width: var(--logo-h); }

/* exactly one of the two, decided by the axis */
[data-logo-lock='lock-full'] .brand-mark,
[data-logo-lock='lock-mark'] .brand-full { display: none; }

[data-logo-color='ink']      .brand { color: var(--ink); }
[data-logo-color='brand']    .brand { color: var(--brand); }
[data-logo-color='accent']   .brand { color: var(--accent); }
[data-logo-color='contrast'] .brand { color: var(--ink); }
[data-logo-color='contrast'][data-theme='ice'] .brand,
[data-logo-color='contrast'][data-theme='paper'] .brand,
[data-logo-color='contrast'][data-theme='steel'] .brand { color: var(--petrol-800); }


/* the three shapes assemble on first paint, each from its own direction —
   which is how the mark is actually drawn: two counters and a bar */
@keyframes mark-in {
	from { opacity: 0; transform: translate(-14%, 14%) rotate(-6deg); }
	to   { opacity: 1; transform: none; }
}
[data-anim='motion'] .brand-full,
[data-anim='motion'] .brand-mark { animation: mark-in .7s var(--ease-out) both; }

.site-nav {
	display: flex;
	align-items: center;
	gap: 4px;
	margin-left: auto;
}
/* `:not(.btn)` throughout. The contact tab is an <a> and a filled button at the
   same time, and `.site-nav a` (one class, one type) outranks `.btn` (one
   class) — so without the exclusion these rules paint the button's label with
   the menu's ink instead of the ink chosen to sit on the accent fill. On the
   pale accents that is white on amber at 1.5:1. A button carries its own
   colours; the menu must not reach inside it. */
.site-nav a:not(.btn) {
	padding: 9px 13px;
	border-radius: var(--radius-sm);
	text-decoration: none;
	font-size: .93rem;
	font-weight: 550;
	color: var(--ink-2);
	position: relative;
	transition: color .18s linear, background-color .18s linear;
}
.site-nav a:not(.btn):hover { color: var(--ink); background: var(--surface-2); }
.site-nav a:not(.btn).active { color: var(--ink); }
.site-nav a.active:not(.nav-cta)::after {
	content: '';
	position: absolute;
	left: 13px;
	right: 13px;
	bottom: 3px;
	height: 2px;
	background: var(--accent);
}
.nav-cta { margin-left: 8px; }
.nav-cta.active::after { display: none; }
[data-navcta='plain'] .nav-cta {
	background: transparent;
	border-color: transparent;
	color: var(--ink-2);
	font-weight: 550;
	padding: 9px 13px;
}
[data-navcta='plain'] .nav-cta .ico { display: none; }

.header-tools { display: flex; align-items: center; gap: 6px; }
.tool { position: relative; }
.tool-btn {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	height: 38px;
	padding: 0 11px;
	border: 1px solid var(--line);
	background: transparent;
	color: var(--ink-2);
	border-radius: var(--radius-sm);
	font: inherit;
	font-size: .82rem;
	font-weight: 650;
	letter-spacing: .04em;
	cursor: pointer;
	transition: color .18s linear, border-color .18s linear, background-color .18s linear;
}
.tool-btn:hover { color: var(--ink); border-color: var(--line-strong); background: var(--surface-2); }
.tool-btn .ico { width: 17px; height: 17px; }

.tool-menu {
	position: absolute;
	top: calc(100% + 9px);
	right: 0;
	min-width: 210px;
	max-height: min(74vh, 640px);
	overflow: auto;
	overscroll-behavior: contain;
	padding: 10px;
	background: var(--surface-root);
	color: var(--ink-root);
	border: 1px solid var(--line-root);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow);
	display: none;
	z-index: 70;
}
.tool-menu[data-open='1'] { display: block; }
/* Four columns for the twenty-odd groups of a design review. With one group the
   panel should look like what it is: a small dropdown under its button.

   GRID, NOT `columns`. Multi-column laid out inside a box with a max-height
   does not wrap to a scrollbar — it opens a FIFTH column to the side, and a
   container that only scrolls vertically simply clips it. The panel looked
   complete and was quietly hiding its last eight axes. A grid overflows
   downwards, which is the direction the scrollbar is on. */
/* The grid is described on the class; only the OPEN state is allowed to say
   `display`. Saying it on the class as well would beat `.tool-menu`'s
   `display: none` — same one-class specificity, later in the file — and the
   panel would hang open on every page load. Keeping the two apart also lets
   the media queries below stay at one class and simply win by source order. */
.tool-menu-wide {
	min-width: min(94vw, 1060px);
	grid-template-columns: repeat(2, minmax(0, 1fr));
	align-items: start;
	gap: 4px 22px;
}
.tool-menu-wide[data-open='1'] { display: grid; }
@media (min-width: 900px) {
	.tool-menu-wide { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1180px) {
	.tool-menu-wide { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.tool-block { margin-bottom: 14px; min-width: 0; }
.tool-block-full {
	grid-column: 1 / -1;
	border-top: 1px solid var(--line-root);
	padding-top: 12px;
}
/* the twelve section checkboxes read badly in one narrow column */
.tool-block-full { display: grid; grid-template-columns: 1fr; }
.tool-block-full .tool-group { grid-column: 1 / -1; }
@media (min-width: 900px) {
	.tool-block-full { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.tool-group {
	margin: 0 0 5px;
	padding: 0 8px;
	font-size: .66rem;
	font-weight: 750;
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--muted-root);
}
.tool-menu button {
	display: flex;
	align-items: center;
	gap: 9px;
	width: 100%;
	padding: 7px 9px;
	border: 0;
	border-radius: var(--radius-sm);
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: .87rem;
	text-align: left;
	cursor: pointer;
}
.tool-menu button:hover { background: var(--surface-2-root); }
.tool-menu button[aria-checked='true'] { color: var(--accent-root); font-weight: 650; }
.tool-block-full button { columns: initial; }

.swatch {
	width: 15px;
	height: 15px;
	flex: none;
	border-radius: 4px;
	border: 1px solid var(--line-root);
	background: var(--surface-2-root);
	position: relative;
}
button[aria-checked='true'] > .swatch { box-shadow: 0 0 0 2px var(--accent-root); }
/* the themes show themselves */
.swatch-ice   { background: #f6fafb; }
.swatch-paper { background: #f8f6f1; }
.swatch-steel { background: #eef2f7; }
.swatch-deep  { background: #00232f; }
.swatch-core  { background: #00141b; }
.swatch-solar { background: linear-gradient(135deg, #06212b 52%, #f7ac11 52%); }
.swatch-black { background: #07090b; }
.swatch-cyan   { background: #2ad2e8; }
.swatch-violet { background: #a99bff; }
.swatch-amber  { background: #f7ac11; }
.swatch-lime   { background: #8ee86a; }
.swatch-on  { background: var(--accent-root); border-color: var(--accent-root); }
.swatch-off { background: transparent; }

.nav-toggle {
	display: none;
	width: 40px;
	height: 38px;
	border: 1px solid var(--line);
	border-radius: var(--radius-sm);
	background: transparent;
	cursor: pointer;
	padding: 10px 9px;
	flex-direction: column;
	justify-content: space-between;
}
.nav-toggle span { display: block; height: 2px; background: var(--ink-2); }

/* -------------------------------------------------- the see-through header --
   Over the hero the bar has no ground of its own, so it redefines the ink
   tokens ON ITSELF and every child follows with no further rules. Its dropdown
   panels take the theme's colours back from the *-root copies, because they
   are solid panels that merely hang off a see-through bar.

   WHICH INK, THOUGH. The obvious version of this rule forces white — and that
   is right only if whatever is behind the bar is dark. Here the hero is DRAWN,
   in the theme's own colours, so on `ice`, `paper` and `steel` its ground is
   very nearly white and a white wordmark and white menu vanish completely
   (measured: 1.05:1, against a 4.5 minimum). So the ink over media is a token
   the theme sets, and only the themes whose hero really is dark set it to
   white. The one case that is dark in every theme is a film, which carries its
   own scrim — that is the override at the bottom. */
.has-immersive .site-header { position: fixed; left: 0; right: 0; }
.site-header.over-media {
	background: transparent;
	backdrop-filter: none;
	border-bottom-color: transparent;
	--ink: var(--on-media);
	--ink-2: var(--on-media-2);
	--muted: var(--on-media-muted);
	--line: var(--on-media-line);
	--line-strong: var(--on-media-line-2);
	--surface-2: var(--on-media-surface);
	color: var(--ink);
}
/* `:not(.btn)` matters. The contact tab in the menu is an <a> AND a filled
   button, so a blanket rule on `.site-nav a` outranks `.btn-primary`'s own ink
   and paints the label with the header's colour instead of the one chosen to
   sit on the accent — white on amber, or dark ink on violet. Measured at
   1.6:1. The button brings its own ink; leave it alone. */
.site-header.over-media .brand,
.site-header.over-media .site-nav a:not(.btn) { color: var(--ink); }
.site-header.over-media .tool-btn { color: var(--ink-2); }

/* A film is dark under every theme — it is the one hero that brings its own
   ground with it, so the bar over it is white whatever the theme says. */
[data-hero='video'] .site-header.over-media {
	--ink: var(--on-dark);
	--ink-2: rgba(255,255,255,.86);
	--muted: rgba(255,255,255,.66);
	--line: rgba(255,255,255,.24);
	--line-strong: rgba(255,255,255,.42);
	--surface-2: rgba(255,255,255,.12);
}

.skip-link {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 200;
	padding: 12px 20px;
	background: var(--accent);
	color: var(--on-accent);
	border-radius: 0 0 var(--radius-sm) 0;
	text-decoration: none;
	font-weight: 650;
}
.skip-link:focus { left: 0; }

/* ================================ HERO ==================================== */
.hero {
	position: relative;
	isolation: isolate;
	min-height: min(94vh, 900px);
	display: flex;
	align-items: center;
	padding: calc(var(--sec-pad) + 30px) 0 var(--sec-pad);
	overflow: hidden;
	background: var(--bg);
}
.hero.under-header { padding-top: calc(var(--sec-pad) + 74px); }
.hero-inner { max-width: 900px; }
.hero h1 { margin-bottom: .34em; }
/* 56ch, not 62: the lattice's core mark sits in the right third of the band,
   and a longer measure runs the lead underneath it. */
.hero .lead { font-size: clamp(1.08rem, 1.5vw, 1.3rem); max-width: 56ch; }

/* the appearance menu can take any of the three lines away */
[data-hero-eyebrow='off'] .hero .eyebrow,
[data-hero-title='off']   .hero h1,
[data-hero-lead='off']    .hero .lead { display: none; }

/* The accent half of the hero title. A block, not a nowrap inline: the break
   between the white line and the amber one is a composition decision and
   belongs in the layout, and nowrap was making the phrase run off the right
   edge of a phone instead of wrapping. */
.hl {
	display: block;
	color: var(--accent);
	position: relative;
}

.hero-cta {
	display: flex;
	flex-wrap: wrap;
	gap: 13px;
	margin-top: 34px;
}

/* A grid rather than a wrapping flex row. With flex, the fourth figure drops
   to a second line on a narrow-ish window and sits alone under the first,
   which reads as a mistake; a four-column grid that collapses to two keeps
   them in a block at every width. */
.hero-stats {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: 26px 30px;
	margin-top: 58px;
	padding-top: 30px;
	border-top: 1px solid var(--line);
	max-width: 880px;
}
@media (max-width: 760px) {
	.hero-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.hero-stat b {
	display: block;
	font-family: var(--head);
	font-size: 2.15rem;
	font-weight: 800;
	line-height: 1;
	letter-spacing: -.03em;
	font-variant-numeric: tabular-nums;
}
.hero-stat span {
	display: block;
	margin-top: 7px;
	font-size: .8rem;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--muted);
}

/* --------------------------------------------------------------- hero art --
   Three treatments, one axis (data-hero). All of them are drawn, none of them
   is a photograph: a software company photographing an open-plan office is
   what every one of its competitors does, and the mark is more interesting
   than the office. */
.hero-art {
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	overflow: hidden;
}
.hero-art svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* which of the drawn scenes is shown is decided further down, once all of
   them have been described */
[data-hero='video'] .hero-art { background: var(--surface-3); }

.hero-video {
	position: absolute;
	inset: 0;
	z-index: -1;
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: none;
}
[data-hero='video'] .hero-video { display: block; }
[data-hero='video'] .hero::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background: var(--scrim);
}
/* over film the hero is white-on-image in every theme */
[data-hero='video'] .hero {
	--ink: var(--on-dark);
	--ink-2: rgba(255,255,255,.84);
	--muted: rgba(255,255,255,.64);
	--line: rgba(255,255,255,.22);
	color: var(--ink);
}

/* THE LATTICE — a nucleus. A core at the mark's own angle, rings of nodes
   around it, and links between them. Everything here is drawn by app.js into
   an SVG; this file only says how it moves. */
.hero-lattice .node { fill: var(--accent); }
.hero-lattice .link { stroke: var(--ink); stroke-width: 1; fill: none; opacity: .13; }
.hero-lattice .ring { stroke: var(--ink); fill: none; opacity: .09; stroke-dasharray: 3 9; }
.hero-lattice .halo { fill: var(--accent); opacity: .06; }
/* The mark is a <use> of the sprite symbol, whose path is fill="currentColor"
   — so it is COLOUR that drives it here, not fill. Setting fill on the <use>
   would not reach inside the symbol. */
.hero-lattice .core { color: var(--accent); opacity: .85; }

/* transform-box: view-box makes transform-origin resolve against the SVG's own
   coordinate system, which is the one the origin was computed in. Without it a
   browser measures the group's bounding box instead and every ring turns
   around a different point. */
.hero-lattice g[class^='spin-'] { transform-box: view-box; }
.hero-lattice .spin-a { animation: spin 74s linear infinite; }
.hero-lattice .spin-b { animation: spin 104s linear infinite reverse; }
.hero-lattice .spin-c { animation: spin 148s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.hero-lattice .node { animation: node-pulse 5.5s var(--ease) infinite; }
@keyframes node-pulse {
	0%, 100% { opacity: .30; }
	50%      { opacity: 1; }
}
.hero-lattice .beam-run {
	stroke: var(--accent);
	stroke-width: 2;
	fill: none;
	stroke-dasharray: 40 460;
	animation: beam-run 6s linear infinite;
}
@keyframes beam-run { to { stroke-dashoffset: -500; } }

/* THE TRAVELLING NUCLEUS. Three of the drawn heroes can send their emblem to
   the empty column beside the copy and bring it back. app.js decides WHERE and
   HOW SMALL — both depend on how much column there is, which only a measurement
   knows — and this says what the journey looks like. One set of rules for all
   three scenes: each wraps its own emblem in a .nucleus, so the difference
   between them is geometry, and geometry is app.js's half of the job.

   The curve is written out rather than taken from --ease-out, which is a
   settling curve: right for something arriving into place, wrong for something
   crossing a distance. A thing that travels should lean into the move and slow
   into the landing, so it gets an in-out curve of its own. */
.hero-art .nucleus {
	/* the scale rides on the same transform as the move, so the origin has to
	   be the emblem's own centre — the point every scene already publishes as
	   --mx/--my for its rotations. Without transform-box a browser would
	   measure the group's bounding box instead, and the shrink would drag the
	   landing point off by however wide the artwork happens to be. */
	transform-box: view-box;
	transform-origin: var(--mx) var(--my);
	transition: transform 2s cubic-bezier(.62, .02, .34, 1);
}
/* Fading DOWN in the middle of the trip, not at the ends. The emblem passes
   over the headline on its way across, and it is solid where the haloes and
   rings are not — app.js's own note beside CORE_X says the rings may pass under
   the text and the mark may not. This is how it gets to, and the dip is a
   one-shot animation because a transition can only go one way and come to
   rest. */
.hero-art .nucleus.is-travelling { animation: nucleus-cross 2s ease-in-out; }
@keyframes nucleus-cross { 50% { opacity: .3; } }

/* A square over the emblem, invisible, and the one part of the artwork that
   answers the mouse — .hero-art turns pointer events off for everything.
   `all` rather than the default, so it catches over transparent fill. */
.hero-art .core-hit {
	fill: transparent;
	pointer-events: all;
	cursor: pointer;
}
/* …except in mid-flight, when the square is somewhere over the copy and would
   swallow a click meant for the page. It also stops a second click landing
   before the first journey is over. */
.hero-art .nucleus.is-travelling .core-hit { pointer-events: none; }

/* Someone who asked for less motion gets the emblem where it was drawn. app.js
   refuses to send it anywhere, and these make sure a position left over from
   before the switch was flipped snaps back instead of gliding. */
[data-anim='still'] .hero-art .nucleus { transition: none; }
@media (prefers-reduced-motion: reduce) {
	.hero-art .nucleus { transition: none; }
	.hero-art .core-hit { cursor: default; }
}

/* THE BEAMS — the mark's diagonal, repeated across the whole band and drifting
   slowly. Cheaper than the lattice and quieter; it is what the inner pages
   open on. Two classes beat `.hero-art svg`, which would otherwise pin it to
   the band's exact bounds and leave the drift showing an empty edge. */
.hero-art .hero-beams {
	inset: -22%;
	width: 144%;
	height: 144%;
	opacity: .9;
}
.hero-beams .beam { fill: var(--accent); }
.hero-beams .beam-dim { fill: var(--ink); }
[data-anim='motion'] .hero-beams { animation: beam-drift 34s linear infinite alternate; }
@keyframes beam-drift {
	from { transform: translate3d(-3%, 2%, 0); }
	to   { transform: translate3d(3%, -2%, 0); }
}

/* THE MARK — the isotype at the size of the band, assembling itself. One
   16-second loop shared by every element in the scene, each one reading a
   different stretch of it: the pieces arrive (0–14%), the whole mark turns
   half a circle and lands on itself (34–48%), a ghost of piece A proves it is
   also piece C (58–84%), and the bar breathes underneath all of it.

   Every rotation turns about --mx/--my, the centre the part carries in the
   SVG's own units — with transform-box: view-box a rotation would otherwise
   be measured from each piece's own bounding box, and the three would go
   round three different points. */
.hero-mark { color: var(--accent); }
.hm-mark { opacity: .9; }
.hm-halo { fill: var(--accent); opacity: .07; }
.hm-ring { fill: none; stroke: var(--ink); opacity: .10; stroke-dasharray: 3 10; }
.hm-mark,
.hm-pc {
	transform-box: view-box;
	transform-origin: var(--mx) var(--my);
}
.hm-pc { fill: currentColor; }
.hm-a, .hm-c { opacity: .62; }
.hm-ghost { color: var(--ink); opacity: 0; }

[data-anim='motion'] .hm-mark  { animation: hm-turn 16s var(--ease) infinite; }
[data-anim='motion'] .hm-b     { animation: hm-build-b 16s var(--ease) infinite; }
[data-anim='motion'] .hm-a     { animation: hm-build-a 16s var(--ease) infinite; }
[data-anim='motion'] .hm-c     { animation: hm-build-c 16s var(--ease) infinite; }
[data-anim='motion'] .hm-ghost { animation: hm-proof 16s var(--ease) infinite; }
[data-anim='motion'] .hm-ring  { animation: hm-ring 16s linear infinite; }
[data-anim='motion'] .hm-halo  { animation: hm-halo 16s var(--ease) infinite; }

/* the bar lands first and then drifts along its own diagonal. Every distance
   is a fraction of --ms, the mark's own size, so the pieces come from just
   outside it however large the band scales the scene */
@keyframes hm-build-b {
	0%        { transform: translateY(calc(var(--ms) * -.5)) rotate(18deg) scale(.72); opacity: 0; }
	10%, 44%  { transform: none; opacity: 1; }
	62%       { transform: translate(calc(var(--ms) * .04), calc(var(--ms) * -.04)); opacity: 1; }
	80%, 100% { transform: none; opacity: 1; }
}
/* the two counters close on it along the 45-degree axis, out of step */
@keyframes hm-build-a {
	0%, 4%    { transform: translate(calc(var(--ms) * -.55), calc(var(--ms) * .55)) rotate(-34deg); opacity: 0; }
	16%, 100% { transform: none; opacity: .62; }
}
@keyframes hm-build-c {
	0%, 8%    { transform: translate(calc(var(--ms) * .55), calc(var(--ms) * -.55)) rotate(-34deg); opacity: 0; }
	22%, 100% { transform: none; opacity: .62; }
}
/* the half turn the trademark survives: piece A becomes piece C, piece C
   becomes piece A, the bar is symmetric, and the mark is unchanged */
@keyframes hm-turn {
	0%, 34%   { transform: none; }
	48%, 84%  { transform: rotate(180deg); }
	96%, 100% { transform: rotate(360deg); }
}
/* the ghost: lifted out, turned, and set down exactly on the other corner */
@keyframes hm-proof {
	0%, 56%   { transform: none; opacity: 0; }
	60%, 66%  { transform: none; opacity: .9; }
	78%, 82%  { transform: rotate(180deg); opacity: .9; }
	88%, 100% { transform: rotate(180deg); opacity: 0; }
}
@keyframes hm-ring {
	0%        { transform: rotate(0deg); opacity: 0; }
	14%, 88%  { opacity: .10; }
	100%      { transform: rotate(360deg); opacity: 0; }
}
@keyframes hm-halo {
	0%, 8%    { opacity: 0; transform: scale(.86); }
	26%, 88%  { opacity: .07; transform: none; }
	100%      { opacity: 0; transform: scale(.86); }
}

/* THE FAMILY — one core and a trail of smaller marks rising along the mark's
   own diagonal, each arriving after the one before it and fading out at the
   top of the loop while the next round starts below. The core is not part of
   the cycle: it arrives once and stays, breathing, so the band always has the
   emblem in it. */
.hero-family { color: var(--accent); }
.hf-halo { fill: var(--accent); opacity: .06; }
.hf-core,
.hf-core-in,
.hf-node { transform-box: view-box; }
.hf-core,
.hf-core-in { transform-origin: var(--mx) var(--my); }
.hf-core-in { fill: currentColor; opacity: .9; }
.hf-node {
	fill: currentColor;
	transform-origin: var(--cx) var(--cy);
	opacity: 0;
}
/* the core: one arrival, then a slow breath that never stops */
[data-anim='motion'] .hf-core-in { animation: hf-arrive 1.5s var(--ease-out) both; }
[data-anim='motion'] .hf-core    { animation: hf-breathe 13s var(--ease) infinite; }
[data-anim='motion'] .hf-node    { animation: hf-emit 13s var(--ease) infinite var(--d); }
[data-anim='motion'] .hf-halo    { animation: hf-halo 13s var(--ease) infinite; }

@keyframes hf-arrive {
	from { opacity: 0; transform: scale(.7) rotate(-24deg); }
	to   { opacity: .9; transform: none; }
}
@keyframes hf-breathe {
	0%, 100% { transform: none; }
	50%      { transform: scale(1.04); }
}
@keyframes hf-emit {
	0%        { opacity: 0; transform: scale(.24) rotate(-40deg); }
	14%, 58%  { opacity: var(--op); transform: none; }
	78%, 100% { opacity: 0; transform: scale(1.3) rotate(16deg); }
}
@keyframes hf-halo {
	0%, 100% { opacity: .06; transform: none; }
	50%      { opacity: .1; }
}

/* WHICH SCENE IS SHOWN. All of them are in the page; one is switched on. A
   list rather than a rule per pair — with four treatments the pairs were
   already a dozen lines that had to be added to twice for every new one. */
.hero-art .hero-lattice,
.hero-art .hero-beams,
.hero-art .hero-mark,
.hero-art .hero-family { display: none; }
[data-hero='lattice'] .hero-lattice,
[data-hero='beams']   .hero-beams,
[data-hero='mark']    .hero-mark,
[data-hero='family']  .hero-family { display: block; }

[data-anim='still'] .hero-lattice *,
[data-anim='still'] .hero-mark *,
[data-anim='still'] .hero-family *,
[data-anim='still'] .hero-beams { animation: none !important; }
/* stilled, each scene is simply its finished state */
[data-anim='still'] .hm-ghost { opacity: 0; }
[data-anim='still'] .hf-core-in { opacity: .9; }
[data-anim='still'] .hf-node { opacity: var(--op); }

/* ========================= THE MARK, TAKEN APART =========================
   Two animations built from views/chrome/pieces.html — the isotype's three
   pieces, each keeping the full 74x74 box so that stacking them at the same
   origin IS the mark, and "assembled" is simply `transform: none`.

   transform-box: view-box on every piece, so a percentage or a length in a
   transform-origin resolves against the SVG's own coordinate system and not
   against each piece's bounding box — which is different for every piece and
   would send all three around different centres. */
.mark-anim { width: 100%; max-width: 260px; color: var(--accent); }
.mark-anim svg { display: block; width: 100%; height: auto; overflow: visible; }
.mark-anim .pc {
	fill: currentColor;
	transform-box: view-box;
	transform-origin: 37px 37px;      /* the centre of the 74-unit box */
}

/* ---- #/about : the pieces converge and lock ---- */
.mark-assembly .pc {
	opacity: 0;
	transition: opacity .8s var(--ease-out), transform .9s var(--ease-out);
}
/* Out along the mark's own 45-degree axis, and turned. The distances are in
   the SVG's 74-unit space, not pixels, so they scale with the mark — and they
   are kept under half a unit-box so a piece in flight never reaches out of its
   column and across the copy beside it. */
.mark-assembly .pc-a { transform: translate(-30px, 30px) rotate(-32deg); }
.mark-assembly .pc-b { transform: translate(0, -40px) rotate(20deg) scale(.72); }
.mark-assembly .pc-c { transform: translate(30px, -30px) rotate(-32deg); }
.mark-assembly.in .pc { opacity: 1; transform: none; }
/* the bar lands first, then the two counters close on it */
.mark-assembly.in .pc-b { transition-delay: .05s; }
.mark-assembly.in .pc-a { transition-delay: .34s; }
.mark-assembly.in .pc-c { transition-delay: .50s; }

/* the accent glows through the mark as it completes */
.mark-assembly .pc-b { color: var(--accent); }
.mark-assembly .pc-a,
.mark-assembly .pc-c { opacity: 0; }
.mark-assembly.in .pc-a,
.mark-assembly.in .pc-c { opacity: .55; }

/* ---- #/method : the ghost proves the two counters are one shape ---- */
.mark-twin { margin: 0; }
.mark-twin .pc-still { fill: currentColor; }
.mark-twin .pc-dim { opacity: .22; }
.mark-twin .pc-ghost {
	opacity: 0;
	color: var(--ink);
	fill: currentColor;
}
[data-anim='motion'] .mark-twin.in .pc-ghost {
	animation: twin 7s var(--ease) infinite;
}
@keyframes twin {
	0%,   6%  { transform: rotate(0deg);   opacity: 0; }
	12%,  26% { transform: rotate(0deg);   opacity: .95; }
	58%,  74% { transform: rotate(180deg); opacity: .95; }
	88%, 100% { transform: rotate(180deg); opacity: 0; }
}
/* the bar breathes along its own diagonal while the ghost travels */
[data-anim='motion'] .mark-twin.in .pc-still:first-child {
	animation: bar-breathe 7s var(--ease) infinite;
}
@keyframes bar-breathe {
	0%, 100% { transform: none; }
	50%      { transform: translate(2.5px, -2.5px); }
}

[data-anim='still'] .mark-anim .pc { animation: none !important; transform: none; opacity: 1; }
[data-anim='still'] .mark-twin .pc-ghost { opacity: 0; }
[data-anim='still'] .mark-assembly .pc-a,
[data-anim='still'] .mark-assembly .pc-c { opacity: .55; }
@media (prefers-reduced-motion: reduce) {
	.mark-anim .pc { animation: none !important; transform: none; }
	.mark-twin .pc-ghost { opacity: 0; }
}

.mark-holder { display: grid; gap: 20px; justify-items: start; }
.mark-caption {
	margin: 0;
	font-family: var(--mono);
	font-size: .82rem;
	line-height: 1.6;
	color: var(--muted);
	max-width: 44ch;
	border-left: 2px solid var(--accent);
	padding-left: 14px;
}

/* ============================== MARQUEE =================================== */
/* The capability ticker under the hero. Two copies of the track, so the
   translation can loop seamlessly at exactly -50%. */
.marquee {
	overflow: hidden;
	padding: 26px 0;
	border-block: 1px solid var(--line);
	background: var(--bg-2);
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.marquee-track {
	display: flex;
	align-items: center;
	gap: 0;
	width: max-content;
	animation: marquee 46s linear infinite;
}
[data-anim='still'] .marquee-track { animation: none; }
.marquee:hover .marquee-track { animation-play-state: paused; }
@keyframes marquee { to { transform: translateX(-50%); } }
.marquee-item {
	display: inline-flex;
	align-items: center;
	gap: 18px;
	padding: 0 26px;
	font-family: var(--head);
	font-size: clamp(1rem, 1.5vw, 1.28rem);
	font-weight: 700;
	letter-spacing: -.015em;
	white-space: nowrap;
	color: var(--ink-2);
}
.marquee-item::after {
	content: '';
	width: 9px;
	height: 9px;
	background: var(--accent);
	clip-path: polygon(0 100%, 72% 0, 100% 0, 28% 100%);
	flex: none;
}

/* ================================ CARDS =================================== */
.card {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	position: relative;
}

.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sec-gap); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sec-gap); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sec-gap); }

/* ------------------------------------------------------------ capability --- */
.cap-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
	gap: var(--sec-gap);
}
.cap {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	padding: 32px 30px 30px;
	position: relative;
	overflow: hidden;
	transition: transform .34s var(--ease), border-color .28s linear, box-shadow .34s var(--ease);
}
/* the corner the mark cuts: a diagonal facet that fills in on hover */
.cap::before {
	content: '';
	position: absolute;
	top: 0;
	right: 0;
	width: 62px;
	height: 62px;
	background: var(--accent-soft);
	clip-path: polygon(100% 0, 100% 100%, 0 0);
	transition: width .4s var(--ease-out), height .4s var(--ease-out);
}
.cap:hover { transform: translateY(-4px); border-color: var(--line-strong); box-shadow: var(--shadow); }
.cap:hover::before { width: 92px; height: 92px; }

.cap-n {
	font-family: var(--mono);
	font-size: .74rem;
	font-weight: 600;
	letter-spacing: .1em;
	color: var(--accent);
	display: block;
	margin-bottom: 20px;
}
.cap-ico {
	width: 34px;
	height: 34px;
	margin-bottom: 18px;
	color: var(--accent);
	display: block;
}
.cap-ico .ico { width: 100%; height: 100%; stroke-width: 1.5; }
.cap h3 { margin-bottom: .5em; }
.cap p { color: var(--ink-2); margin-bottom: 18px; font-size: .97rem; }

.cap-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.cap-list li {
	position: relative;
	padding-left: 19px;
	font-size: .9rem;
	color: var(--muted);
}
.cap-list li::before {
	content: '';
	position: absolute;
	left: 0;
	top: .58em;
	width: 8px;
	height: 8px;
	background: var(--accent);
	clip-path: polygon(0 100%, 72% 0, 100% 0, 28% 100%);
}

/* ---------------------------------------------------------------- steps ---- */
/* How the work runs. A single line down the left with the stations on it —
   which is the diagram every one of these projects actually is. */
.steps { display: grid; gap: 0; position: relative; }
.step {
	display: grid;
	grid-template-columns: 92px 1fr;
	gap: 0 30px;
	padding: 30px 0;
	position: relative;
	border-top: 1px solid var(--line);
}
.step:last-child { border-bottom: 1px solid var(--line); }
.step-n {
	font-family: var(--mono);
	font-size: 1.5rem;
	font-weight: 500;
	color: var(--accent);
	line-height: 1;
	letter-spacing: -.02em;
}
.step h3 { margin-bottom: .35em; }
.step p { color: var(--ink-2); margin: 0 0 14px; max-width: 68ch; }
.step-tags { display: flex; flex-wrap: wrap; gap: 7px; }

/* the diagonal that lights up along the row */
.step::after {
	content: '';
	position: absolute;
	left: 0;
	top: -1px;
	height: 1px;
	width: 0;
	background: var(--accent);
	transition: width .55s var(--ease-out);
}
.step:hover::after { width: 100%; }

/* ---------------------------------------------------------------- chips ---- */
.chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 4px 11px;
	border-radius: var(--radius-sm);
	border: 1px solid var(--line);
	background: var(--surface-2);
	font-family: var(--mono);
	font-size: .76rem;
	letter-spacing: -.01em;
	color: var(--ink-2);
	white-space: nowrap;
}
.chip-accent { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

.chips { display: flex; flex-wrap: wrap; gap: 8px; }

/* --------------------------------------------------------------- stat row -- */
.stat-row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
	gap: 1px;
	background: var(--line);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	overflow: hidden;
}
.stat {
	background: var(--bg);
	padding: 34px 26px;
	text-align: center;
}
.band-dark .stat { background: var(--surface-3); }
.stat b {
	display: block;
	font-family: var(--head);
	font-size: clamp(2.2rem, 4vw, 3.2rem);
	font-weight: 800;
	line-height: 1;
	letter-spacing: -.035em;
	color: var(--accent);
	font-variant-numeric: tabular-nums;
}
.stat span {
	display: block;
	margin-top: 12px;
	font-size: .85rem;
	color: var(--muted);
	line-height: 1.4;
}

/* -------------------------------------------------------------- products --- */
.prod-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
	gap: var(--sec-gap);
}
.prod {
	display: flex;
	flex-direction: column;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	padding: 28px;
	position: relative;
	overflow: hidden;
	text-decoration: none;
	color: inherit;
	transition: transform .34s var(--ease), border-color .28s linear, box-shadow .34s var(--ease);
}
.prod::after {
	content: '';
	position: absolute;
	inset: auto 0 0 0;
	height: 3px;
	background: var(--accent);
	transform: scaleX(0);
	transform-origin: 0 50%;
	transition: transform .5s var(--ease-out);
}
.prod:hover { transform: translateY(-4px); box-shadow: var(--shadow); border-color: var(--line-strong); }
.prod:hover::after { transform: scaleX(1); }
.prod-domain {
	font-family: var(--mono);
	font-size: .78rem;
	color: var(--accent);
	letter-spacing: -.01em;
	margin-bottom: 14px;
	display: flex;
	align-items: center;
	gap: 8px;
}
.prod h3 { margin-bottom: .4em; }
.prod p { color: var(--ink-2); font-size: .95rem; flex: 1; }
.prod .chips { margin-top: 16px; }
.prod-go {
	margin-top: 20px;
	font-size: .87rem;
	font-weight: 650;
	color: var(--accent);
	display: inline-flex;
	align-items: center;
	gap: 8px;
}

/* ---------------------------------------------------- the products, listed --
   The home band. A hairline grid rather than seven more cards: the cards are
   on #/products, and the two readings of the same seven products should not
   look alike. */
/* The hairlines are outlines on the tiles, not the container showing through
   the gaps: seven products never divide evenly into the columns, and a grid
   coloured behind its cells turns the leftover cell into a tile that is not
   there. Two neighbours draw into the same 1px gap and land on each other, so
   it stays one line; the outer edge is clipped and the container's border
   draws it instead. */
.prod-mini-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
	gap: 1px;
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	overflow: hidden;
}
.band-soft .prod-mini-grid { background: var(--bg-2); }
.prod-mini {
	display: flex;
	align-items: center;
	gap: 14px;
	padding: 20px 22px;
	background: inherit;
	outline: 1px solid var(--line);
	text-decoration: none;
	color: inherit;
	transition: background-color .25s linear;
}
.prod-mini:hover { background: var(--surface); }
.prod-mini-ico { width: 20px; height: 20px; color: var(--accent); flex: none; }
.prod-mini-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.prod-mini-name b { font-family: var(--head); font-size: 1.04rem; letter-spacing: -.012em; }
.prod-mini-name span {
	font-family: var(--mono);
	font-size: .76rem;
	color: var(--muted);
	overflow: hidden;
	text-overflow: ellipsis;
}
/* the arrow only leans in on hover — seven of them announcing themselves at
   rest would be seven arrows and no list */
.prod-mini-go {
	width: 16px;
	height: 16px;
	margin-left: auto;
	flex: none;
	opacity: 0;
	transform: translateX(-6px);
	color: var(--accent);
	transition: opacity .22s linear, transform .28s var(--ease-out);
}
.prod-mini:hover .prod-mini-go { opacity: 1; transform: none; }

/* The two flagships carry no badge — the order of the grid is the claim. What
   is left is a shade more contrast than the rest of it. */
.prod.is-flagship { border-color: var(--line-strong); }
.prod.is-flagship::after { transform: scaleX(1); opacity: .5; }
.prod.is-flagship:hover::after { opacity: 1; }
.prod-domain { flex-wrap: wrap; }

/* ------------------------------------------------------------ industries --- */
.ind-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	gap: 1px;
	background: var(--line);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	overflow: hidden;
}
.ind {
	background: var(--bg);
	padding: 28px 24px;
	display: flex;
	flex-direction: column;
	gap: 12px;
	transition: background-color .25s linear;
}
.band-soft .ind { background: var(--bg-2); }
.band-dark .ind { background: var(--surface-3); }
.ind:hover { background: var(--surface-2); }
.ind .ico { width: 24px; height: 24px; color: var(--accent); stroke-width: 1.5; }
.ind b { font-size: 1rem; font-weight: 700; letter-spacing: -.01em; }
.ind span { font-size: .88rem; color: var(--muted); line-height: 1.45; }

/* ------------------------------------------------------------ the team ----
   Seventeen faces under the six disciplines they work in. Square, because the
   photographs are square and cropping someone out of their own portrait to fit
   a shape is a poor trade.

   auto-fill rather than auto-fit: with seventeen items and a column that
   narrows, auto-fit collapses the empty tracks and stretches the last row's
   photographs to double the width of the ones above them. auto-fill keeps the
   track and the grid stays a grid. */
.people-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(122px, 1fr));
	gap: 18px 14px;
}
/* HOVERING ONE FACE. The picture grows by three fifths and lifts over the ones
   around it — and nothing else on the wall moves a pixel, because a transform
   is painted and not laid out. The grid keeps the cell exactly where it was;
   only what is drawn inside it gets bigger, and spills over its neighbours.

   The PICTURE grows, not the figure: a name blown up by three fifths is not a
   bigger name, it is a caption that has lost its manners. So the name stays the
   size it was and gets out of the way instead — the picture is about to cover
   the space it sits in anyway.

   It leaves faster than the picture arrives. The growth is slow on purpose, and
   a name fading at the same rate would still be legible under the photograph
   halfway through, which reads as a mistake rather than as a transition.

   position and z-index are what put the growing square on top. Without them a
   square would grow underneath the ones after it in the grid, which paints
   later, and half the effect would be hidden behind the next face along. */
.person {
	margin: 0;
	position: relative;
}
.person:hover,
.person:focus-within { z-index: 5; }

/* The photograph's own box. It exists so the marks below have something to be
   positioned against — the figure is taller than the picture by the height of
   the caption, and a corner measured from the figure would sit beside the name
   rather than on the image. Clipping is its second job: the picture grows a
   little under the pointer, and inside a frame that reads as a zoom rather than
   as a photograph spilling over its neighbours.

   The pointer is a promise the page does not keep yet — these become clickable
   when the pictures can be swapped. Only the photographs carry it: the bot and
   the mark at the end are not pictures and there is nothing to swap them for. */
.person-frame {
	position: relative;
	display: block;
	border-radius: var(--radius-img);
	overflow: hidden;
	cursor: pointer;
	transition: transform .9s var(--ease);
}
.person:hover .person-frame,
.person:focus-within .person-frame { transform: scale(1.6); }

/* THREE MARKS IN THE CORNER. The isotype's own counter, three times: one
   appears, the other two follow it in, the three of them stand together for a
   moment, and then they go. app.js hands each corner its cycle and its head
   start, and no two photographs in the grid agree.

   The corner is still empty far more often than not — roughly a quarter of the
   cycle has anything in it at all. That is the whole effect. A corner that
   blinked steadily would be a status light, and the picture would be reporting
   something; a corner that is almost always empty is just the mark passing
   through. */
.person-ticks {
	position: absolute;
	right: 7%;
	bottom: 7%;
	display: flex;
	gap: 4px;
	/* the marks must never be what receives the click the frame is promising */
	pointer-events: none;
}
.tick {
	width: 8px;
	height: 8px;
	color: var(--accent);
	opacity: 0;
	animation: tick-1 var(--t, 8s) linear var(--d, 0s) infinite;
}
/* The three arrive one after another and leave together.

   All three share one clock — app.js gives the whole corner a single cycle and
   a single head start — and the stagger is here, in three keyframe sets that
   differ only in when each one lights. The first at 75% of the cycle, the
   second at 82%, the third at 89%, and from 89% to 94% all three are lit at
   once, which is the moment the corner is actually saying something. Then one
   fade, together, and the corner is empty again for three quarters of the
   cycle.

   Written as three keyframe sets rather than three delays because a delay
   would stagger the ending as well as the beginning, and they would go out in
   the order they came in. Coming in one by one is a build; going out one by
   one is a leak. */
@keyframes tick-1 {
	0%, 72%  { opacity: 0; }
	75%, 94% { opacity: .92; }
	98%, 100% { opacity: 0; }
}
@keyframes tick-2 {
	0%, 79%  { opacity: 0; }
	82%, 94% { opacity: .92; }
	98%, 100% { opacity: 0; }
}
@keyframes tick-3 {
	0%, 86%  { opacity: 0; }
	89%, 94% { opacity: .92; }
	98%, 100% { opacity: 0; }
}
.tick:nth-child(2) { animation-name: tick-2; }
.tick:nth-child(3) { animation-name: tick-3; }

/* Without motion the marks stop blinking rather than disappear: they are part
   of how the picture is drawn, and a visitor who asked for less movement asked
   for less movement, not for less of the design. */
[data-anim='still'] .tick { animation: none; opacity: .45; }
@media (prefers-reduced-motion: reduce) {
	.tick { animation: none; opacity: .45; }
}
.person-pic {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 1;
	object-fit: cover;
	border-radius: var(--radius-img);
	background: var(--surface-2);
	/* Greyscale until you look at one of them. The photographs were taken by
	   seventeen different people in seventeen different places — kitchens,
	   riverbanks, offices — and in colour the wall of them reads as a collage
	   of backgrounds rather than as one team. Grey removes the backgrounds
	   from the argument and leaves the faces; the colour is still there for
	   whoever wants it, one person at a time.

	   This is the same treatment the data-photo axis offers site-wide, applied
	   here as the baseline rather than as an option, because for this grid it
	   is the design and not a preference. */
	filter: grayscale(1);
	transition: filter .45s var(--ease);
}
.person:hover .person-pic,
.person:focus-within .person-pic { filter: grayscale(0); }
.person figcaption {
	margin-top: 9px;
	font-size: .82rem;
	line-height: 1.3;
	color: var(--ink-2);
	transition: opacity .25s var(--ease);
}
.person:hover figcaption,
.person:focus-within figcaption { opacity: 0; }

/* The two squares that are drawn rather than photographed — the bot, and the
   mark signing the block off. They take the same greyscale as the faces from
   .person-pic above, which is what keeps them part of the wall instead of two
   ornaments stuck on the end: grey with everyone else, and the accent colour
   arriving under the pointer exactly as a face does. */
.person-emblem {
	display: grid;
	place-items: center;
	background: var(--surface-2);
	color: var(--accent);
}
.person-emblem .ico { width: 42%; height: 42%; }
/* The isotype is filled where the line icons are stroked, so it reads heavier
   at the same size — a solid shape against a square rather than a drawing
   inside one. Smaller, so the two squares weigh the same. */
.person-drawn:last-child .person-emblem .ico { width: 34%; height: 34%; }

/* Someone who asked for less motion still gets the colour — it is information
   about the photograph, not decoration — but not the lift, and not the fade. */
/* Without motion the picture does not grow — and so the name has no reason to
   step aside, and stays where it is. */
[data-anim='still'] .person-pic,
[data-anim='still'] .person-frame,
[data-anim='still'] .person figcaption { transition: none; }
[data-anim='still'] .person:hover .person-frame,
[data-anim='still'] .person:focus-within .person-frame { transform: none; }
[data-anim='still'] .person:hover figcaption,
[data-anim='still'] .person:focus-within figcaption { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
	.person-pic,
	.person-frame,
	.person figcaption { transition: none; }
	.person:hover .person-frame,
	.person:focus-within .person-frame { transform: none; }
	.person:hover figcaption,
	.person:focus-within figcaption { opacity: 1; }
}

/* ----------------------------------------------------------------- stack --- */
.stack-groups {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
	gap: var(--sec-gap);
}
.stack-group h4 {
	font-family: var(--sans);
	font-size: .72rem;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--muted);
	margin-bottom: 16px;
	padding-bottom: 12px;
	border-bottom: 1px solid var(--line);
}

/* ---------------------------------------------------------------- quotes --- */
.quote-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: var(--sec-gap);
}
.quote {
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	padding: 30px;
	position: relative;
}
.quote blockquote {
	margin: 0 0 20px;
	font-family: var(--head);
	font-size: 1.14rem;
	line-height: 1.48;
	letter-spacing: -.015em;
}
.quote figcaption { font-size: .86rem; color: var(--muted); }
.quote figcaption b { display: block; color: var(--ink-2); font-weight: 650; }

/* ------------------------------------------------------------- CTA band ---- */
.cta-band {
	background: var(--surface-3);
	color: var(--on-dark);
	border-radius: var(--radius-lg);
	padding: clamp(40px, 6vw, 74px);
	position: relative;
	overflow: hidden;
	--ink: var(--on-dark);
	--ink-2: rgba(255,255,255,.82);
	--muted: rgba(255,255,255,.62);
	--line: rgba(255,255,255,.16);
	--line-strong: rgba(255,255,255,.34);
	--accent: var(--a-bright);
	--on-accent: var(--a-on);
}
/* the mark, enormous and bled off the corner */
.cta-band::before {
	content: '';
	position: absolute;
	right: -60px;
	bottom: -80px;
	width: 340px;
	height: 340px;
	background: currentColor;
	opacity: .05;
	-webkit-mask: url('../brand/svg/isotype.svg') no-repeat center / contain;
	mask: url('../brand/svg/isotype.svg') no-repeat center / contain;
}
.cta-band h2 { max-width: 18ch; }
.cta-band .lead { max-width: 56ch; }
.cta-actions { display: flex; flex-wrap: wrap; gap: 13px; margin-top: 30px; }

/* ============================ CONTACT ===================================== */
.contact-split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
	gap: 62px var(--sec-gap);
	align-items: start;
}
.contact-lines { display: grid; gap: 4px; margin-top: 30px; }
.contact-line {
	display: flex;
	gap: 15px;
	align-items: flex-start;
	padding: 15px 0;
	border-bottom: 1px solid var(--line);
	text-decoration: none;
	color: inherit;
	transition: padding-left .28s var(--ease);
}
a.contact-line:hover { padding-left: 8px; }
.contact-line .ico { width: 19px; height: 19px; color: var(--accent); margin-top: 3px; }
.contact-line b { display: block; font-size: .72rem; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.contact-line span { font-size: 1.02rem; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.field { display: flex; flex-direction: column; gap: 7px; }
.field-wide { grid-column: 1 / -1; }
.field label {
	font-size: .74rem;
	letter-spacing: .13em;
	text-transform: uppercase;
	font-weight: 700;
	color: var(--muted);
}
.field input,
.field textarea,
.field select {
	font: inherit;
	font-size: .97rem;
	color: var(--ink);
	background: var(--surface);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius-sm);
	padding: 12px 14px;
	transition: border-color .2s linear, box-shadow .2s linear;
	width: 100%;
}
.field textarea { min-height: 132px; resize: vertical; }
.field input:focus,
.field textarea:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-soft);
}
.field-error input, .field-error textarea { border-color: #e2564a; }
.field-msg { font-size: .8rem; color: #e2564a; }
/* the honeypot: off-screen, not display:none — a bot that reads styles skips
   hidden fields, and one that does not, fills this in and is turned away */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-note { font-size: .85rem; color: var(--muted); margin-top: 14px; }
.form-status {
	margin-top: 18px;
	padding: 14px 16px;
	border-radius: var(--radius-sm);
	border: 1px solid var(--line);
	background: var(--surface-2);
	font-size: .93rem;
	display: none;
}
.form-status[data-state] { display: block; }
.form-status[data-state='ok'] { border-color: var(--accent); background: var(--accent-soft); }
.form-status[data-state='error'] { border-color: #e2564a; }

/* ------------------------------------------------------------ nw_picker ----
   The site's variation of the RCULT85 nw_select / nw_mselect dropdowns. A
   native <select> stays the source of truth underneath and still fires
   `change`, so nothing that listens to it had to change. The control keeps the
   silhouette of the .field input it replaces, so the form does not jump when
   the script upgrades it; everything else is a token, which is how the five
   themes and three corner shapes carry through with no rule of their own. */
.pick { position: relative; display: block; text-align: left; max-width: 100%; }
.pick-source { display: none !important; }
/* set by nw_picker while one of the band's menus is open — every section is a
   stacking context of its own, so the band has to be lifted above the ones
   that follow it or the menu is painted over. Still below the header (60). */
section.has-open-pick { z-index: 40; }

.pick-control {
	display: flex;
	align-items: center;
	gap: 8px;
	width: 100%;
	min-height: 46px;
	padding: 7px 14px;
	background: var(--surface);
	color: var(--ink);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius-sm);
	font: inherit;
	font-size: .97rem;
	text-align: left;
	cursor: pointer;
	transition: border-color .2s linear, box-shadow .2s linear;
}
.pick-control:hover { border-color: var(--accent); }
.pick.is-open .pick-control,
.pick-control:focus-visible {
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-soft);
	outline: none;
}
.pick-value { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-caret { width: 15px; height: 15px; flex: none; color: var(--muted); transition: transform .2s var(--ease); }
.pick.is-open .pick-caret { transform: rotate(180deg); }

/* the multiple control is a row of chips that wraps, with the search input
   sharing the last line — it grows downward as choices are added */
.pick-control-multi { flex-wrap: wrap; cursor: text; padding: 5px 14px 5px 8px; }
.pick-many { min-width: 220px; }
.pick-tags { display: contents; }
.pick-tag {
	display: inline-flex;
	align-items: center;
	gap: 2px;
	max-width: 15rem;
	padding: 4px 4px 4px 11px;
	border-radius: var(--radius-sm);
	background: var(--accent-soft);
	color: var(--accent);
	font-size: .78rem;
	font-weight: 700;
	animation: pickIn .18s var(--ease);
}
@keyframes pickIn { from { opacity: 0; transform: translateY(-3px); } }
.pick-tag-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-tag-x {
	flex: none;
	border: 0;
	padding: 0;
	width: 19px;
	height: 19px;
	cursor: pointer;
	border-radius: 50%;
	background: transparent;
	color: inherit;
	font-size: 1.05rem;
	line-height: 1;
	opacity: .65;
}
.pick-tag-x:hover { opacity: 1; background: color-mix(in srgb, var(--accent) 22%, transparent); }
.pick-input {
	flex: 1 1 7ch;
	min-width: 6ch;
	border: 0;
	outline: 0;
	padding: 6px 2px;
	background: transparent;
	color: inherit;
	font: inherit;
	font-size: .95rem;
}
.pick-input::placeholder { color: var(--muted); opacity: 1; }

.pick-menu {
	position: absolute;
	left: 0;
	top: calc(100% + 6px);
	z-index: 90;
	width: max-content;
	min-width: 100%;
	max-width: calc(100vw - 16px);
	max-height: 320px;
	overflow-y: auto;
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow);
	padding: 6px;
	animation: pickDrop .18s var(--ease) both;
}
.pick-up .pick-menu { top: auto; bottom: calc(100% + 6px); }
@keyframes pickDrop { from { opacity: 0; transform: translateY(-6px); } }

.pick-search {
	display: flex;
	align-items: center;
	gap: 9px;
	position: sticky;
	top: -6px;
	margin: -6px -6px 6px;
	padding: 12px 14px;
	background: var(--surface);
	border-bottom: 1px solid var(--line);
}
.pick-search svg { width: 16px; height: 16px; color: var(--muted); flex: none; }
.pick-search-input {
	flex: 1;
	border: 0;
	outline: 0;
	background: transparent;
	font: inherit;
	font-size: .92rem;
	color: var(--ink);
	min-width: 0;
}

.pick-option {
	display: flex;
	align-items: center;
	gap: 10px;
	cursor: pointer;
	padding: 9px 12px;
	border-radius: var(--radius-sm);
	font-size: .92rem;
	color: var(--ink-2);
}
.pick-option:hover, .pick-option.is-active { background: var(--surface-2); color: var(--ink); }
.pick-option[aria-selected='true'] { color: var(--accent); font-weight: 700; }
.pick-option-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-check { width: 15px; height: 15px; flex: none; color: var(--accent); opacity: 0; }
.pick-option[aria-selected='true'] .pick-check { opacity: 1; }
.pick-empty { margin: 0; padding: 14px 12px; color: var(--muted); font-size: .88rem; }

/* ============================ THE OPENING FILM ============================
   Off by default (data-film). It covers everything until it ends, is skippable
   from the button, from Escape, and by itself on any failure — nothing may
   leave a visitor stuck behind a black rectangle. The layer is only in the
   flow while data-intro is set, so with the axis off it costs one hidden div
   and no paint at all. */
.intro { display: none; }
[data-intro='1'] .intro {
	display: block;
	position: fixed;
	inset: 0;
	z-index: 300;
	background: #00232f;
	opacity: 1;
	transition: opacity .6s var(--ease);
}
[data-intro='1'] .intro.gone { opacity: 0; pointer-events: none; }
.intro-video { width: 100%; height: 100%; object-fit: cover; display: block; }
.intro-skip {
	position: absolute;
	right: 22px;
	bottom: 22px;
	padding: 9px 18px;
	border: 1px solid rgba(255,255,255,.42);
	border-radius: var(--radius-sm);
	background: rgba(0,0,0,.28);
	color: #fff;
	font: inherit;
	font-size: .88rem;
	font-weight: 650;
	cursor: pointer;
	backdrop-filter: blur(6px);
}
.intro-skip:hover { background: rgba(0,0,0,.5); }
/* the site must not scroll behind the film */
[data-intro='1'] body { overflow: hidden; }

/* ============================== THE DECK ================================== */
/* reveal.js lives inside this frame and nowhere else. Everything below is the
   site's own vocabulary applied to reveal's structure — no reveal theme is
   loaded, because a theme would bring its own fonts and colours and this deck
   has to look like the page it is standing in. */
/* A grid of two rows rather than a plain block: the bar takes the first, and
   the deck and the play button share the second, so the invitation covers the
   slides and not the frame's own toolbar. Grid items in one cell stack in
   document order, which puts the button on top without positioning it. */
.deck-frame {
	position: relative;
	display: grid;
	grid-template-rows: auto 1fr;
	border: 1px solid var(--line);
	border-radius: var(--radius-card);
	background: var(--surface);
	overflow: hidden;
	box-shadow: var(--shadow);
}
.deck-frame .deck,
.deck-frame .deck-start { grid-area: 2 / 1; }

/* THE INVITATION. A deck that sits still looks like a screenshot of one, and
   nobody presses a screenshot. The veil is the frame's own surface at partial
   opacity, so the first slide still reads underneath it in every theme, and
   it goes at the first move — this button, the deck's arrows, or the
   keyboard. */
.deck-start {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 18px;
	border: 0;
	background: transparent;
	color: inherit;
	font: inherit;
	cursor: pointer;
	z-index: 3;
	transition: opacity .32s linear, visibility .32s linear;
}
.deck-start[hidden] { display: none; }
.deck-start.is-gone { opacity: 0; visibility: hidden; }
.deck-start::before {
	content: '';
	position: absolute;
	inset: 0;
	background: var(--surface);
	opacity: .66;
}
/* The icon alone, and its name on the button: the words under it landed on
   the cover slide's own sentence, and the line above the frame already says
   what the deck is. */
.deck-start-ring {
	position: relative;
	width: 88px;
	height: 88px;
	border-radius: 50%;
	display: grid;
	place-items: center;
	background: var(--accent);
	color: var(--on-accent);
	transition: transform .3s var(--ease);
}
.deck-start-ring .ico { width: 30px; height: 30px; margin-left: 4px; }
.deck-start:hover .deck-start-ring { transform: scale(1.07); }
.deck-start:focus-visible .deck-start-ring { outline: 2px solid var(--accent); outline-offset: 5px; }
/* one ring travelling outwards, the way a pulse reads without moving anything */
[data-anim='motion'] .deck-start-ring { animation: deck-pulse 2.8s var(--ease-out) infinite; }
@keyframes deck-pulse {
	0%      { box-shadow: 0 0 0 0 var(--accent-soft); }
	70%     { box-shadow: 0 0 0 26px transparent; }
	100%    { box-shadow: 0 0 0 0 transparent; }
}
@media (max-width: 640px) {
	.deck-start-ring { width: 68px; height: 68px; }
	.deck-start-ring .ico { width: 24px; height: 24px; }
}
.deck-bar {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 12px 16px;
	border-bottom: 1px solid var(--line);
	background: var(--surface-2);
	font-size: .78rem;
	color: var(--muted);
	font-family: var(--mono);
	letter-spacing: -.01em;
}
.deck-bar .dots { display: flex; gap: 6px; }
.deck-bar .dots i {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--line-strong);
	display: block;
}
.deck-bar .dots i:first-child { background: var(--accent); }
.deck-bar .spacer { margin-left: auto; }
.deck-btn {
	border: 1px solid var(--line);
	background: transparent;
	color: var(--ink-2);
	border-radius: var(--radius-sm);
	font: inherit;
	font-size: .74rem;
	padding: 5px 10px;
	cursor: pointer;
	display: inline-flex;
	align-items: center;
	gap: 6px;
}
.deck-btn:hover { color: var(--accent); border-color: var(--accent); }

/* EVERY RULE BELOW IS SCOPED UNDER .deck-frame, AND THAT IS NOT COSMETIC.
   reveal.css is fetched at runtime by nw_deck, so it lands in the document
   AFTER this stylesheet. Between two rules of equal specificity the later one
   wins — which means a bare `.deck { height: … }` loses to reveal's own
   `.reveal { height: 100% }`, the deck resolves to 100% of an auto-height
   parent, and the whole thing collapses to nothing. One extra class on each
   selector puts these back in front, for good, without a single !important. */
.deck-frame .deck {
	height: clamp(420px, 56vh, 620px);
	width: 100%;
	background: var(--surface);
}
.deck-frame .deck:fullscreen { height: 100vh; }

/* Before reveal.js arrives — and if it never does — the slides are just a
   readable stack. The deck degrades to a list of headed blocks rather than to
   a blank rectangle. */
.deck-frame .deck:not([data-deck-ready]) .slides {
	display: grid;
	gap: 20px;
	padding: 26px;
	overflow-y: auto;
	height: 100%;
}
.deck-frame .deck:not([data-deck-ready]) .slides section { padding: 0; }
.deck-frame .deck:not([data-deck-ready]) .slides section section { margin-top: 16px; }

/* reveal's own layout wins once it is up; these are the looks. */
.deck-frame .reveal { font-family: var(--sans); font-size: 27px; color: var(--ink); }
.deck-frame .reveal .slides { text-align: left; }
.deck-frame .reveal .slides section { padding: 6px 44px; height: auto; }
.deck-frame .reveal h2,
.deck-frame .reveal h3,
.deck-frame .reveal h4 {
	font-family: var(--head);
	color: var(--ink);
	text-transform: none;
	letter-spacing: -.025em;
	margin-bottom: .4em;
}
.deck-frame .reveal h2 { font-size: 1.72em; }
.deck-frame .reveal h3 { font-size: 1.16em; }
.deck-frame .reveal p { color: var(--ink-2); font-size: .74em; line-height: 1.55; }
.deck-frame .reveal ul { font-size: .68em; line-height: 1.6; margin-left: 1em; }
.deck-frame .reveal li { margin-bottom: .38em; color: var(--ink-2); }
.deck-frame .reveal li::marker { color: var(--accent); }
.deck-frame .reveal .deck-eyebrow {
	font-family: var(--mono);
	font-size: .46em;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--accent);
	margin-bottom: 1.2em;
}
.deck-frame .reveal .deck-big {
	font-family: var(--head);
	font-size: 2.6em;
	font-weight: 800;
	letter-spacing: -.04em;
	color: var(--accent);
	line-height: 1;
	margin: 0 0 .2em;
}
.deck-frame .reveal .deck-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
.deck-frame .reveal .deck-card {
	border: 1px solid var(--line);
	border-left: 3px solid var(--accent);
	border-radius: var(--radius-sm);
	padding: 14px 16px;
	background: var(--surface-2);
}
.deck-frame .reveal .deck-card b { display: block; font-size: .58em; margin-bottom: .35em; color: var(--ink); }
.deck-frame .reveal .deck-card span { display: block; font-size: .48em; color: var(--muted); line-height: 1.5; }
/* the auto-animate pair: the figure is the whole slide, then shrinks beside
   the sentence that explains it. reveal tweens between the two states. */
.deck-frame .reveal .deck-big-xl { font-size: 5.2em; }
.deck-frame .reveal .deck-big-row {
	display: flex;
	align-items: baseline;
	gap: 28px;
	flex-wrap: wrap;
}
.deck-frame .reveal .deck-big-row p:last-child { flex: 1 1 16ch; margin: 0; }

/* reveal draws its fragments with its own colours; these are ours */
.deck-frame .reveal .fragment.highlight-current-accent.current-fragment { color: var(--accent); }
.deck-frame .reveal .controls { color: var(--accent); }
.deck-frame .reveal .progress { color: var(--accent); height: 3px; }
/* Bottom LEFT. reveal puts the counter bottom-right by default, which is
   exactly where it also puts the four navigation arrows — the two land on top
   of each other in an embedded frame this size. */
.deck-frame .reveal .slide-number {
	background: transparent;
	color: var(--muted);
	font-family: var(--mono);
	font-size: 13px;
	right: auto;
	left: 16px;
	bottom: 10px;
}
.deck-frame .reveal .backgrounds { background: var(--surface); }
.deck-hint { white-space: nowrap; }

/* =============================== FOOTER =================================== */
.site-footer {
	background: var(--surface-3);
	color: var(--on-dark);
	padding: 74px 0 0;
	position: relative;
	z-index: 1;
	--ink: var(--on-dark);
	--ink-2: rgba(255,255,255,.78);
	--muted: rgba(255,255,255,.56);
	--line: rgba(255,255,255,.13);
	--accent: var(--a-bright);
}
.footer-grid {
	display: grid;
	grid-template-columns: 1.6fr repeat(3, 1fr);
	gap: 44px var(--sec-gap);
	padding-bottom: 56px;
}
.footer-brand .brand { color: var(--on-dark); margin-bottom: 20px; }
.footer-tag { color: var(--ink-2); max-width: 34ch; font-size: .95rem; }
.footer-col h4 {
	font-family: var(--sans);
	font-size: .7rem;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--muted);
	margin-bottom: 16px;
}
.footer-col a, .footer-col p {
	display: block;
	color: var(--ink-2);
	text-decoration: none;
	font-size: .93rem;
	margin: 0 0 9px;
	transition: color .18s linear;
}
.footer-col a:hover { color: var(--accent); }
.social-row { display: flex; gap: 9px; margin-top: 16px; }
.social-link {
	width: 36px;
	height: 36px;
	display: grid;
	place-items: center;
	border: 1px solid var(--line);
	border-radius: var(--radius-sm);
	color: var(--ink-2);
	transition: color .2s linear, border-color .2s linear, transform .28s var(--ease);
}
.social-link:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-2px); }
.social-link .ico { width: 17px; height: 17px; }

.footer-bottom {
	display: flex;
	flex-wrap: wrap;
	gap: 14px 26px;
	align-items: center;
	justify-content: space-between;
	padding: 22px 0;
	border-top: 1px solid var(--line);
	font-size: .84rem;
	color: var(--muted);
}
.footer-legal { display: flex; gap: 20px; flex-wrap: wrap; }
.footer-legal a { color: var(--muted); text-decoration: none; }
.footer-legal a:hover { color: var(--accent); }

/* ============================ SIDE RAILS ================================== */
/* One dot per section of the page being read. Long pages only. */
.jump-rail {
	position: fixed;
	right: 16px;
	top: 50%;
	transform: translateY(-50%);
	z-index: 40;
	display: grid;
	gap: 11px;
}
.jump-rail[hidden] { display: none; }
.jump-dot {
	width: 9px;
	height: 9px;
	padding: 0;
	border: 0;
	background: var(--line-strong);
	cursor: pointer;
	position: relative;
	clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
	transition: background-color .22s linear, transform .28s var(--ease);
}
.jump-dot:hover, .jump-dot.on { background: var(--accent); transform: scale(1.5); }
.jump-dot span {
	position: absolute;
	right: 18px;
	top: 50%;
	transform: translateY(-50%) translateX(6px);
	white-space: nowrap;
	background: var(--surface-root);
	color: var(--ink-root);
	border: 1px solid var(--line-root);
	border-radius: var(--radius-sm);
	padding: 4px 10px;
	font-size: .76rem;
	opacity: 0;
	pointer-events: none;
	transition: opacity .2s linear, transform .28s var(--ease);
	box-shadow: var(--shadow-sm);
}
.jump-dot:hover span { opacity: 1; transform: translateY(-50%) translateX(0); }
@media (max-width: 1100px) { .jump-rail { display: none; } }

/* =============================== HELPERS ================================== */
/* Scroll reveal. Named .rv rather than .reveal on purpose: `.reveal` is
   reveal.js's own root class, and a site that has both would be styling the
   deck's container with a fade-in that never resolves. */
.rv {
	opacity: 0;
	transform: translateY(22px);
	transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
.rv.in { opacity: 1; transform: none; }
[data-anim='still'] .rv { opacity: 1; transform: none; transition: none; }
/* a grid of them arrives one after another rather than all at once */
.rv-1 { transition-delay: .06s; }
.rv-2 { transition-delay: .12s; }
.rv-3 { transition-delay: .18s; }
.rv-4 { transition-delay: .24s; }
.rv-5 { transition-delay: .30s; }

.note {
	display: flex;
	gap: 12px;
	align-items: flex-start;
	padding: 16px 18px;
	border: 1px solid var(--line);
	border-left: 3px solid var(--accent);
	border-radius: var(--radius-sm);
	background: var(--surface-2);
	font-size: .92rem;
	color: var(--ink-2);
}
.note .ico { color: var(--accent); margin-top: 3px; }

.status { color: var(--muted); font-size: .93rem; display: flex; align-items: center; gap: 10px; }
.spinner {
	width: 15px;
	height: 15px;
	border: 2px solid var(--line-strong);
	border-top-color: var(--accent);
	border-radius: 50%;
	display: inline-block;
	animation: spin 1s linear infinite;
}
.nw-loading { padding: 140px 0; display: grid; place-items: center; }

/* A picture that has not been supplied yet. Says so, and says what belongs
   there — so a placeholder is a brief for the person who has to fill it, not a
   grey rectangle. */
.ph {
	display: grid;
	place-items: center;
	gap: 8px;
	text-align: center;
	padding: 40px 26px;
	min-height: 220px;
	border: 1px dashed var(--line-strong);
	border-radius: var(--radius-img);
	background:
		repeating-linear-gradient(var(--tilt), var(--surface-2) 0 10px, transparent 10px 20px);
	color: var(--muted);
}
.ph b {
	font-family: var(--mono);
	font-size: .72rem;
	letter-spacing: .16em;
	text-transform: uppercase;
	color: var(--accent);
}
.ph span { font-size: .86rem; max-width: 42ch; line-height: 1.5; }

/* --------------------------------------------------------- text animation --
   The looks half of assets/js/nw_anim.js. That file decides WHEN something
   plays and hands over two custom properties — --anim-dur for the pass and --d
   for this letter's delay — plus --c for the colour effects. Everything below
   is driven by those, so a new effect is one keyframe block and one class,
   with no JavaScript change at all.

   .nw-anim wraps the phrase; .nw-anim-c is one letter, present only for the
   effects that need them. Nothing animates until .is-anim lands. */
.nw-anim { display: inline-block; }
/* a word of split letters stays on one line; the spaces between words are
   still ordinary break opportunities */
.nw-anim-w { white-space: nowrap; }
/* A phrase that replays on click has to look like it will. The focus ring is
   the standard one, since click also binds Enter and Space. */
.nw-anim[data-anim-when*="click"] { cursor: pointer; }
.nw-anim[data-anim-when*="click"]:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 4px;
	border-radius: 2px;
}
.nw-anim-c {
	display: inline-block;
	font-style: inherit;
	will-change: transform, opacity, color;
}

/* The letters of a typewriter start absent, not merely transparent — but only
   while the effect is actually running. Hiding them on .nw-anim-typewritter
   alone, as this did, is the one rule in this file that fails INVISIBLE: every
   other effect keeps its hiding inside a keyframe, so an element left without
   .is-anim simply looks unanimated, while this one left the words gone.

   And they do get left without it. The class naming the effect is put on by the
   pass and stays; .is-anim is taken off by stop(), which clear() calls on every
   live phrase — so changing the theme, the accent or the animation setting
   strips it from the whole page, and only the phrases on screen are played
   again. A heading that was not in view at that moment kept the typewriter
   class with nothing to animate it, and stayed blank until it was clicked.
   Scoping the rule to .is-anim is what makes the effect fail visible: the
   words come back by themselves, and the animation is unchanged, since
   `forwards` holds the last keyframe once the pass is over. */
.nw-anim-typewritter.is-anim .nw-anim-c {
	opacity: 0;
	animation: nwType .001s linear var(--d, 0s) forwards;
}
@keyframes nwType { to { opacity: 1; } }

.nw-anim-change-color.is-anim .nw-anim-c {
	animation: nwColor var(--anim-dur, .6s) ease-in-out var(--d, 0s) both;
}
@keyframes nwColor {
	40% { color: var(--c, var(--accent)); }
	60% { color: var(--c, var(--accent)); }
	to  { color: inherit; }
}

.nw-anim-bend.is-anim .nw-anim-c {
	animation: nwBend var(--anim-dur, .6s) ease-in-out var(--d, 0s) both;
}
@keyframes nwBend {
	40% { transform: translateY(-.24em) rotate(-6deg); }
	to  { transform: none; }
}

.nw-anim-flip.is-anim .nw-anim-c {
	animation: nwFlip var(--anim-dur, .6s) ease-in-out var(--d, 0s) both;
}
@keyframes nwFlip {
	50% { transform: rotateX(-90deg); }
	to  { transform: none; }
}

.nw-anim-rise.is-anim .nw-anim-c {
	animation: nwRise var(--anim-dur, .6s) var(--ease) var(--d, 0s) both;
}
@keyframes nwRise {
	from { transform: translateY(.7em); opacity: 0; }
	to   { transform: none; opacity: 1; }
}

.nw-anim-blur.is-anim .nw-anim-c {
	animation: nwBlur var(--anim-dur, .6s) ease-out var(--d, 0s) both;
}
@keyframes nwBlur {
	from { filter: blur(8px); opacity: 0; }
	to   { filter: none; opacity: 1; }
}

/* whole-phrase effects — no letters involved */
.nw-anim-tilt.is-anim {
	animation: nwTilt var(--anim-dur, .6s) ease-in-out both;
	transform-origin: left center;
}
@keyframes nwTilt {
	35% { transform: rotate(-2.2deg) scale(1.03); }
	to  { transform: none; }
}

/* The sweep rides a gradient through the text, so the text has to be painted
   from the background — and the ends of that gradient have to BE the real
   colour, which is what currentColor gives, but only while `color` still holds
   it. Setting `color: transparent` here would make currentColor transparent
   too and the phrase would vanish the moment the sweep starts;
   -webkit-text-fill-color hides the painted glyph without touching what
   currentColor means. */
.nw-anim-shine.is-anim {
	background: linear-gradient(100deg,
		currentColor 0%, currentColor 38%,
		color-mix(in srgb, var(--accent) 55%, #fff) 50%,
		currentColor 62%, currentColor 100%);
	background-size: 300% 100%;
	-webkit-background-clip: text;
	background-clip: text;
	-webkit-text-fill-color: transparent;
	animation: nwShine var(--anim-dur, 1.8s) linear both;
}
@keyframes nwShine {
	from { background-position: 120% 0; }
	to   { background-position: -60% 0; }
}

/* Someone who asked for less motion gets the words and none of the movement.
   nw_anim already refuses to play, but a stray class must not linger. */
@media (prefers-reduced-motion: reduce) {
	.nw-anim, .nw-anim-c { animation: none !important; opacity: 1 !important; }
	.nw-anim-shine.is-anim { background: none; -webkit-text-fill-color: currentColor; }
}

/* ============================= RESPONSIVE ================================= */
@media (max-width: 1080px) {
	.footer-grid { grid-template-columns: 1fr 1fr; }
	.band-split { grid-template-columns: 1fr; }
	.band-split .band-head { position: static; }
	.contact-split { grid-template-columns: 1fr; }
	.grid-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 920px) {
	.site-nav {
		position: fixed;
		inset: 74px 0 auto;
		flex-direction: column;
		align-items: stretch;
		gap: 2px;
		padding: 16px 22px 26px;
		background: var(--surface-root);
		color: var(--ink-root);
		border-bottom: 1px solid var(--line-root);
		box-shadow: var(--shadow);
		display: none;
		max-height: calc(100vh - 74px);
		overflow: auto;
	}
	.site-nav.open { display: flex; }
	.site-nav a { padding: 13px 12px; font-size: 1rem; color: var(--ink-root); }
	.site-nav a.active::after { display: none; }
	.nav-cta { margin: 8px 0 0; justify-content: center; }
	.nav-toggle { display: flex; }
	.header-inner { height: 68px; }
	.has-immersive .site-nav { inset-block-start: 68px; }

	:root { --sec-pad: 68px; }
	.grid-3, .grid-2 { grid-template-columns: 1fr; }
	.step { grid-template-columns: 60px 1fr; gap: 0 18px; }
	/* Room under the figures for the nucleus. On a phone the scale is driven by
	   the band's height, so the core lands wherever the copy already is; the
	   extra bottom padding gives it a strip of its own and the shift drops it
	   into it, instead of sitting behind the buttons. */
	.hero {
		min-height: 0;
		padding-top: calc(var(--sec-pad) + 40px);
		padding-bottom: calc(var(--sec-pad) + 150px);
	}
	.hero-lattice,
	.hero-mark { transform: translate(13%, 19%); }
	.hero-stats { gap: 22px 32px; margin-top: 42px; }
	.hero-stat b { font-size: 1.7rem; }
	.deck-frame .deck { height: clamp(360px, 62vh, 480px); }
	.deck-frame .reveal { font-size: 19px; }
	.deck-frame .reveal .slides section { padding: 6px 22px; }
	.deck-frame .reveal .deck-cols { grid-template-columns: 1fr; gap: 12px; }
	.deck-hint { display: none; }
	.tool-menu-wide { columns: 1; min-width: min(92vw, 380px); }
}

@media (max-width: 640px) {
	body { font-size: 16px; }
	.wrap { width: min(var(--wrap), 100% - 32px); }
	.footer-grid { grid-template-columns: 1fr; gap: 34px; }
	.grid-4 { grid-template-columns: 1fr; }
	.form-grid { grid-template-columns: 1fr; }
	.brand { --logo-h: 28px; }
	[data-logo-size='lg'] .brand { --logo-h: 32px; }
	.cap { padding: 26px 22px 24px; }
	.footer-bottom { justify-content: flex-start; }
}

/* ================================ PRINT =================================== */
@media print {
	.site-header, .jump-rail, .deck-bar, .marquee, .hero-art, .btn { display: none !important; }
	body { background: #fff; color: #000; }
	section { padding: 18px 0; }
}
