/* The staff area's own base: tokens, fonts, elements, chrome and the handful of drawing idioms the
   rest of the staff CSS builds on. staff.css adds the Data tables and forms, workspace.css adds the
   friendly screens, and both read the custom properties declared here.

   This file exists so the staff area can be redesigned without touching the customer's screens.
   /staff used to load /portal.css, /branding.css and /home.css alongside its own two stylesheets,
   and all three are shared with the client portal at /, so every edit meant for staff landed on a
   customer's page as well. Those three files are still there, still loaded by index.html, and are
   deliberately untouched: the split was made by staff.html letting go of them, not by editing them.
   If a rule here looks like a near-copy of one over there, that is the point — they are allowed to
   drift now.

   The design is the marketing site's, translated rather than reinterpreted. northpoint-website is
   Astro and Tailwind v4 and keeps its system in an @theme block; there is no build step here, so the
   same ramps, type scale and curves are written out as plain custom properties. Values are copied
   from src/styles/global.css, never re-derived, so the two stay comparable by reading them.

   Three things the site does not answer, decided here.

   The whole surface goes dark. On the website only the chrome and the immersive bands flip; article
   pages keep a white sheet, which is a reasonable thing for a brochure and an impossible one for a
   tool someone works in after dark. So the site's dark vocabulary is extended across every surface:
   the ink-950 ground, the ink-800 hairline, brand-300 where brand-700 was, white and ink-200 and
   ink-400 for the three weights of text.

   Panels sit on the page in both schemes. Light already does this — a paper page with white panels
   on it — and dark mirrors it rather than going flat, because a screen of tables needs its rows to
   belong to something. The gap is one step, not a shadow.

   Eyebrows carry no number. SectionLabel numbers its sections because a marketing page is a sequence
   of them; a staff page is not, and "01 / CUSTOMERS" would be a number counting nothing. Numbering
   survives in one place only, the project stage rail, which is a real sequence. */

/* ==========================================================================
   FONTS
   Self-hosted, latin only, copied out of the website's node_modules at Fontsource 5.3.0. See
   fonts/README.md for what was left out and why. The @font-face rules are written here rather than
   imported because style-src 'self' forbids anything that is not a served stylesheet, and there is
   no bundler to resolve a package specifier.
   ========================================================================== */
@font-face {
  font-family: 'Schibsted Grotesk Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 400 900;
  src: url(/fonts/schibsted-grotesk-latin-wght-normal.woff2) format('woff2-variations');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(/fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* The crop marks are drawn as background layers, and a gradient cannot be interpolated, so the
   colour they are built from is registered as a real custom property and the transition runs on that
   instead. inherits:false keeps a hovered card from tinting the marks of a card nested inside it. */
@property --np-crop-c {
  syntax: '<color>';
  inherits: false;
  initial-value: #8793a1;
}

/* ==========================================================================
   RAMPS
   The website's palette, verbatim from global.css:77-143. These never change between schemes; only
   the semantic layer below does. Timber is deliberately absent: the site rations it to drawings of
   the product, and nothing here draws one. Safety used to be absent for the same reason, and is not
   any more — a website order carries a drawing of the racking somebody bought, and the guards on it
   are painted in the yellow they are actually painted in. Redline is present but kept to what the
   site allows it: revision marks, the active nav indicator, errors, and a drawing's dimensions.
   ========================================================================== */
:root {
  --brand-50: #f3f7fc;
  --brand-100: #e2ebf8;
  --brand-200: #c3d4ef;
  --brand-300: #98b5e1;
  --brand-400: #648dce;
  --brand-500: #3668ba;
  --brand-600: #28559f;
  --brand-700: #1d4688;
  --brand-800: #19396b;
  --brand-900: #152c51;
  --brand-950: #0e1a2f;

  --ink-50: #f9fafb;
  --ink-100: #f0f2f5;
  --ink-200: #dce0e5;
  --ink-300: #b9c1ca;
  --ink-400: #8793a1;
  --ink-500: #657181;
  --ink-600: #505b68;
  --ink-700: #3d4651;
  --ink-800: #2e353e;
  --ink-900: #22282f;
  --ink-950: #1a1f25;

  --paper: #f2f2f2;

  --redline-100: #fbe6e1;
  --redline-300: #eaa08d;
  --redline-400: #dc6f52;
  --redline-500: #c2452d;
  --redline-600: #a73a25;

  /* The one green. Redline says a thing wants attention; this says a thing arrived and it is good
     news — a document that was not in the last version, a quotation the customer accepted. Two
     weights, because a colour that reads on paper does not read on a dark panel. */
  --green-100: #e2f1e9;
  --green-400: #5cb98a;
  --green-600: #1f6b45;
  --redline-700: #872e1c;
  --redline-900: #521d13;

  /* Racking protection, in the yellow it is actually painted. It is the one hue here that is not the
     palette's, and it is the only thing it is for: a guard on a storage drawing has to read as the
     thing you are not meant to hit. Copied from the marketing site's @theme block, like the rest. */
  --safety-200: #fdf1b3;
  --safety-300: #fbe27a;
  --safety-400: #f7cf3d;
  --safety-500: #f0b90b;
  --safety-600: #c99700;
  --safety-700: #8f6b00;

  /* ---- Type ------------------------------------------------------------ */
  --np-sans: 'Schibsted Grotesk Variable', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --np-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', 'Cascadia Code', monospace;

  /* The site's fluid display sizes, used for the two headings big enough to want them. Everything
     below page-title size is fixed: a table does not benefit from type that moves with the window,
     and a staff screen is read at a desk, not across a room. */
  --t-hero: clamp(1.875rem, 1.4rem + 2vw, 2.75rem);
  --t-page: clamp(1.5rem, 1.25rem + 1.1vw, 2rem);
  --t-section: 1.125rem;
  --t-body: 0.9375rem;
  --t-small: 0.875rem;
  --t-spec: 0.8125rem;
  --t-meta: 0.75rem;

  /* ---- Motion ---------------------------------------------------------- */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);

  /* ---- Structure ------------------------------------------------------- */
  /* One radius, on things a finger or a pointer acts on. Cards, panels, tables, tiles, dialogs and
     the rail are square, which is what the site does and what a drawing does. */
  --r: 4px;
  --rail: 180px;
  --measure: 1540px;
}

/* ==========================================================================
   SCHEME
   Three states, the same model the website uses: no attribute follows the operating system, which is
   also the whole story with JavaScript off; data-theme="dark" and data-theme="light" override it.
   The attribute only ever sits on <html>, restored from localStorage by /theme.js before first
   paint and written by the toggle in the top bar.

   The website resolves this once in an @custom-variant and lets every dark: utility key off it. With
   no Tailwind here the same logic has to be written by hand, so it is written exactly twice, on the
   semantic layer, and nothing further down the file repeats it.
   ========================================================================== */
:root {
  color-scheme: light;

  --np-page: var(--paper);
  --np-panel: #ffffff;
  --np-sunken: var(--ink-50);
  --np-rail: var(--paper);

  --np-rule: var(--ink-200);
  --np-rule-strong: var(--ink-300);

  --np-head: var(--ink-950);
  --np-text: var(--ink-700);
  /* ink-500 is the site's muted text, measured 4.96:1 on white — but this page is paper, not white,
     and there it falls to 4.43:1. One step down clears it on both. */
  --np-muted: var(--ink-600);
  --np-faint: var(--ink-400);

  --np-accent: var(--brand-700);
  --np-accent-hover: var(--brand-800);
  --np-accent-quiet: var(--brand-50);
  --np-on-accent: #ffffff;

  /* The revision colour. On a drawing it marks what has changed and has not yet been agreed, which
     is precisely what a draft ahead of the customer is. It is also the error colour, as it is on the
     site, and it is allowed nowhere else.

     redline-500 is the site's own choice and measures 5.02:1 on white — but a sentence carrying this
     colour sits on the paper page as often as on a panel, and there it is 4.48:1, which is under the
     bar by two hundredths. One step down the ramp is 5.73:1 on paper and still unmistakably redline. */
  --np-mark: var(--redline-600);
  --np-mark-quiet: var(--redline-100);

  --np-good: var(--green-600);
  --np-good-quiet: var(--green-100);

  --np-focus: var(--brand-700);
  /* The edge of a field, which WCAG asks 3:1 of because it is what tells you where to click.
     --np-rule-strong is 1.82:1 and is right for a hairline between rows, wrong for a boundary. */
  --np-field-edge: var(--ink-400);
  --np-crop: var(--ink-400);
  --np-grid: rgb(29 70 136 / 0.07);
  --np-scrim: rgb(26 31 37 / 0.55);
  --np-ghost: var(--ink-400);

  /* The one surface that does not follow the scheme, declared once so nobody has to write white
     into a stylesheet to get it. A customer's own logo is supplied artwork drawn for a white page,
     and a dark wordmark on a dark panel is simply gone. It belongs with the semantic layer rather
     than the ramps because it is a decision about a surface, not a colour anyone may pick from. */
  --np-plate: #ffffff;
}

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

    --np-page: #16191e;
    --np-panel: var(--ink-950);
    --np-sunken: #12151a;
    --np-rail: var(--ink-950);

    --np-rule: var(--ink-800);
    --np-rule-strong: var(--ink-700);

    --np-head: #ffffff;
    --np-text: var(--ink-200);
    --np-muted: var(--ink-400);
    --np-faint: var(--ink-500);

    --np-accent: var(--brand-300);
    --np-accent-hover: var(--brand-200);
    --np-accent-quiet: rgb(152 181 225 / 0.12);
    --np-on-accent: var(--ink-950);

    --np-mark: var(--redline-400);
    --np-mark-quiet: rgb(220 111 82 / 0.16);

    --np-good: var(--green-400);
    --np-good-quiet: rgb(92 185 138 / 0.16);

    --np-focus: var(--brand-300);
    --np-field-edge: var(--ink-500);
    --np-crop: var(--ink-600);
    --np-grid: rgb(152 181 225 / 0.09);
    --np-scrim: rgb(0 0 0 / 0.7);
    --np-ghost: var(--ink-500);
  }
}

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

  --np-page: #16191e;
  --np-panel: var(--ink-950);
  --np-sunken: #12151a;
  --np-rail: var(--ink-950);

  --np-rule: var(--ink-800);
  --np-rule-strong: var(--ink-700);

  --np-head: #ffffff;
  --np-text: var(--ink-200);
  --np-muted: var(--ink-400);
  --np-faint: var(--ink-500);

  --np-accent: var(--brand-300);
  --np-accent-hover: var(--brand-200);
  --np-accent-quiet: rgb(152 181 225 / 0.12);
  --np-on-accent: var(--ink-950);

  --np-mark: var(--redline-400);
  --np-mark-quiet: rgb(220 111 82 / 0.16);

  --np-good: var(--green-400);
  --np-good-quiet: rgb(92 185 138 / 0.16);

  --np-focus: var(--brand-300);
  --np-field-edge: var(--ink-500);
  --np-crop: var(--ink-600);
  --np-grid: rgb(152 181 225 / 0.09);
  --np-scrim: rgb(0 0 0 / 0.7);
  --np-ghost: var(--ink-500);
}

/* The generated company marks. mark.js emits fill="var(--mark-deep-N,#hex)", so with none of this
   present every mark renders exactly as it did before dark mode existed.

   Only the ground of a mark that is not inverted faces the page; an inverted one is a pale island
   whose insides do not care what is behind it. So only the deeps are in question, and only the six
   whose luminance is genuinely too low to read: navy — the house colour, and the one most
   Northpoint-ish names land on — measures 1.80:1 against the dark page, plum 2.09 and violet 2.03.
   Each of those six is walked towards its own tint until it clears a luminance floor.

   The floor is where it is because the two things a silhouette has to do on a dark page pull against
   each other exactly. Lifting it away from the page pushes it towards the figure drawn on top of it,
   and the two ratios sum to very nearly a constant: 2.68 and 2.98 at one floor, 2.97 and 2.67 at
   another. There is no value where both reach 3:1, so the floor is set at the point where they meet
   in the middle rather than at one that favours one over the other. Neither needs 3:1 in any case —
   a customer's badge is decorative and its name is always printed beside it — but a mark nobody can
   see is still a bug, which is what this fixes. The six were chosen by rendering all fourteen at
   several floors and looking at them, not by arithmetic alone.

   The other eight are already bright enough and are left exactly as they are, which is why they are
   absent here rather than restated.

   Nothing about which mark a customer gets changes. Hue, shape, pattern, rotation, inversion and the
   initials are the same functions of the same id and name; only the ink differs. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --mark-deep-0: #4065a0;   /* navy */
    --mark-deep-1: #3265ad;   /* blue */
    --mark-deep-3: #1a6e83;   /* cyan */
    --mark-deep-11: #a54560;  /* rose */
    --mark-deep-12: #8d4d92;  /* plum */
    --mark-deep-13: #6a59aa;  /* violet */
  }
}

:root[data-theme='dark'] {
  --mark-deep-0: #4065a0;   /* navy */
  --mark-deep-1: #3265ad;   /* blue */
  --mark-deep-3: #1a6e83;   /* cyan */
  --mark-deep-11: #a54560;  /* rose */
  --mark-deep-12: #8d4d92;  /* plum */
  --mark-deep-13: #6a59aa;  /* violet */
}

/* The schema map's six groups. These are the one categorical set in the staff area, and they are
   here rather than in workspace.css because that sheet may not write a colour down: the map needs six
   hues that are told apart by hue, and the brand and ink ramps between them only offer two.

   They are the colours the map already used, kept so the meaning does not move under anyone who
   knows it: brand for the business records, teal for the lists the portal reads, ochre for what
   happened, violet for signing in, and two greys for the per-person settings and the join tables.
   Under a dark scheme each is raised in HSL, holding its hue and saturation, so a node stays the
   colour its legend says it is. The two greys are pushed apart rather than raised together, because
   in light they are told apart by lightness alone and raising both would collapse them into one.

   The legend swatch and the node read the same property, which is the point: they used to be two
   copies of the same hex in two files, and two copies drift. */
:root {
  --node-business: #1d4688;
  --node-reference: #1f6f6e;
  --node-audit: #8a6a12;
  --node-auth: #5b3d8f;
  --node-settings: #4b5563;
  --node-other: #78889b;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --node-business: #417ad7;
    --node-reference: #268887;
    --node-audit: #9a7614;
    --node-auth: #8a6cc0;
    --node-settings: #637083;
    --node-other: #929fae;
  }
}

:root[data-theme='dark'] {
  --node-business: #417ad7;
  --node-reference: #268887;
  --node-audit: #9a7614;
  --node-auth: #8a6cc0;
  --node-settings: #637083;
  --node-other: #929fae;
}

/* ==========================================================================
   THEME SWEEP
   The flip runs inside a view transition and the new scheme is revealed as a circle growing from the
   button that asked for it. theme.js measures the button and sets --vt-x/--vt-y/--vt-r just before
   the transition starts; the animation itself lives here because a script-attached one starts a
   frame or two late in Firefox, which flashes the finished scheme full-screen before the clip takes
   hold. The static clip-path is the guard that makes either timing safe: any frame the animation has
   not reached is clipped to nothing rather than painted whole.
   ========================================================================== */
::view-transition-image-pair(root) {
  isolation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
  display: block;
}

::view-transition-new(root) {
  clip-path: circle(0px at var(--vt-x, 50%) var(--vt-y, 0%));
  animation: np-sweep 620ms ease-in-out both;
}

@keyframes np-sweep {
  from { clip-path: circle(0px at var(--vt-x) var(--vt-y)); }
  to { clip-path: circle(var(--vt-r, 150vmax) at var(--vt-x) var(--vt-y)); }
}

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: 28px;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  background: var(--np-page);
  color: var(--np-text);
  font-family: var(--np-sans);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* The variable font carries every weight this page asks for, so let none of them be faked. */
  font-synthesis-weight: none;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  color: var(--np-head);
  font-weight: 500;
  text-wrap: balance;
}

h1 {
  font-size: var(--t-page);
  line-height: 1.15;
  letter-spacing: -0.02em;
}

h2 {
  font-size: var(--t-section);
  line-height: 1.25;
  letter-spacing: -0.015em;
}

h3 {
  font-size: var(--t-small);
  line-height: 1.3;
}

p {
  text-wrap: pretty;
}

a {
  color: inherit;
}

button, input, textarea, select {
  font: inherit;
  color: inherit;
}

button, a, label, summary {
  touch-action: manipulation;
}

button {
  cursor: pointer;
}

hr {
  border: 0;
  border-top: 1px solid var(--np-rule);
  margin: 0;
}

/* Muted, not faint. A placeholder is the only thing written in an empty field and people read it to
   find out what the field wants; faint measures 3.13:1 on white and 3.34:1 on the dark panel. The
   contrast checker cannot see it either — it looks for a text node, and an input has none — so this
   one is held by the comment and by the eye. */
::placeholder {
  color: var(--np-muted);
  opacity: 1;
}

::selection {
  background: var(--brand-200);
  color: var(--brand-950);
}

:root[data-theme='dark'] ::selection {
  background: var(--brand-800);
  color: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) ::selection {
    background: var(--brand-800);
    color: #ffffff;
  }
}

/* One focus ring everywhere, the site's exact rule. The 2px radius is the only rounding a square
   element ever gets, and it is on the ring rather than the element. */
:focus-visible {
  outline: 2px solid var(--np-focus);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Anything that lives inside something that scrolls or clips draws its ring on the inside instead.
   At +2px the ring is outside the element's box, the scroller cuts it, and what is left is one or
   two stray vertical bars that read as a divider rather than as focus. */
#breadcrumb a:focus-visible,
.sidebar nav a:focus-visible {
  outline-offset: -2px;
}

[hidden] {
  display: none !important;
}

/* Anything that reads as measured data lines its digits up. */
.mono,
.np-mono {
  font-family: var(--np-mono);
  font-variant-numeric: tabular-nums;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 8px;
  z-index: 60;
  background: var(--np-panel);
  color: var(--np-accent);
  border: 1px solid var(--np-accent);
  padding: 10px 14px;
  font-weight: 500;
}

.skip-link:focus {
  left: 8px;
}

/* ==========================================================================
   DRAWING IDIOMS
   The five marks the website makes on a page, as classes rather than components.
   ========================================================================== */

/* A section marker: a short rule in the accent, then the label in tracked mono. No number — see the
   note at the top of the file. */
.np-eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--np-mono);
  font-size: var(--t-meta);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--np-muted);
}

.np-eyebrow::before {
  content: '';
  flex: none;
  width: 24px;
  height: 1px;
  background: var(--np-accent);
}

/* The dimension line off an engineering drawing: a hairline with a tick at each end. Decorative, so
   it carries no separator role and never divides content a heading has not already divided. */
.np-rule {
  display: flex;
  align-items: center;
  height: 10px;
  background: linear-gradient(var(--np-rule), var(--np-rule)) 0 50% / 100% 1px no-repeat;
}

.np-rule::before,
.np-rule::after {
  content: '';
  flex: none;
  width: 1px;
  height: 10px;
  background: var(--np-rule-strong);
}

.np-rule::after {
  margin-left: auto;
}

/* Registration ticks at the four corners of a hairline panel, the marks printed at the edge of a
   drawing sheet. Eight background layers rather than four spans, because nothing in the staff area
   emits the markup for spans and a background needs none. background-origin is the default
   padding-box, which puts each arm just inside the frame — the same 1px inset Card.astro uses.

   The surfaces are listed here rather than left to a class the JS would have to add in a dozen
   places, which also keeps the list short enough to argue with. Marks go on the things that stand
   for one record — a customer, a site, a person, a project, a job waiting — and on a dialog, which
   is a sheet laid over the page. They stay off tables, which are busy enough, and off the sign-off
   panel, which already has the redline edge; two devices on one panel fight. */
.np-crop,
.ws-card,
.customer-card,
.site-tile,
.person-card,
.attention-card,
dialog {
  --np-crop-c: var(--np-crop);
  background-image:
    linear-gradient(var(--np-crop-c), var(--np-crop-c)), linear-gradient(var(--np-crop-c), var(--np-crop-c)),
    linear-gradient(var(--np-crop-c), var(--np-crop-c)), linear-gradient(var(--np-crop-c), var(--np-crop-c)),
    linear-gradient(var(--np-crop-c), var(--np-crop-c)), linear-gradient(var(--np-crop-c), var(--np-crop-c)),
    linear-gradient(var(--np-crop-c), var(--np-crop-c)), linear-gradient(var(--np-crop-c), var(--np-crop-c));
  background-repeat: no-repeat;
  background-size: 8px 1px, 1px 8px, 8px 1px, 1px 8px, 8px 1px, 1px 8px, 8px 1px, 1px 8px;
  background-position:
    0 0, 0 0,
    100% 0, 100% 0,
    0 100%, 0 100%,
    100% 100%, 100% 100%;
  transition: --np-crop-c 300ms cubic-bezier(0.4, 0, 0.2, 1), border-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* A frame answers the pointer whether or not it is itself a link, so a card holding a link and a row
   of links beside it picks up as one. */
:is(.np-crop, .ws-card, .customer-card, .site-tile, .person-card, .attention-card):is(:hover, :focus-within) {
  --np-crop-c: var(--np-accent);
}

@media (prefers-reduced-motion: reduce) {
  .np-crop,
  .ws-card,
  .customer-card,
  .site-tile,
  .person-card,
  .attention-card,
  dialog {
    transition: none;
  }
}

/* Spec-sheet rows with dotted leaders, for anywhere the page states measured facts: a quotation's
   figures, a record's provenance, a count. Data reads as data rather than as a sentence. */
.np-specs {
  margin: 0;
  font-family: var(--np-mono);
}

/* Three columns — label, leader, value — so a dl needs no spacer element between them. The leader
   is a cell rather than a dotted background on the row, because a row is one line tall until a long
   value makes it two, and a background painted at half the row's height then sits between the two
   lines leading nowhere. As a cell it stays on the label's line and shrinks to nothing when the
   value wants the room. */
.np-specs > div {
  display: grid;
  /* max-content on the label, not auto: in the project rail the column is about 250px and auto lets
     the browser break "VALID UNTIL" across two lines to buy room for a value that is going to wrap
     anyway. The label holds its line, the leader gives up whatever is left, and only the value
     wraps — which is the order a spec sheet gives things up in. */
  grid-template-columns: max-content 1fr minmax(0, auto);
  align-items: baseline;
  column-gap: 9px;
  padding: 9px 0;
  border-bottom: 1px solid var(--np-rule);
}

.np-specs > div::before {
  content: '';
  /* Both coordinates, and grid-row is the one that matters. A pseudo-element placed only by column
     is still auto-placed by row, and it is the first item in the box: it takes row 1, leaving the
     cursor past column 1, so the dt that asks for column 1 cannot fit and the row advances. Label
     and value land on row 2 and the leader is stranded on a line of its own above them, joining
     nothing. It looks close enough to right that reading a screenshot will not catch it. */
  grid-row: 1;
  grid-column: 2;
  height: 0;
  border-bottom: 1px dotted var(--np-rule-strong);
  transform: translateY(-0.25em);
}

.np-specs > div:last-child {
  border-bottom: 0;
}

.np-specs dt {
  grid-column: 1;
  font-size: var(--t-meta);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--np-muted);
}

.np-specs dd {
  grid-column: 3;
  min-width: 0;
  margin: 0;
  font-size: var(--t-spec);
  letter-spacing: 0.02em;
  color: var(--np-head);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Faint drafting-paper grid. Deliberately rare: the site bans it as an unmasked page background
   because solid panels floating on texture read as patchwork. It earns its place behind the schema
   map, which is the one screen here that really is a drawing. */
.np-grid-paper {
  background-image:
    linear-gradient(to right, var(--np-grid) 1px, transparent 1px),
    linear-gradient(to bottom, var(--np-grid) 1px, transparent 1px);
  background-size: 28px 28px;
}

/* ==========================================================================
   CONTROLS
   ========================================================================== */
.button,
.secondary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 38px;
  padding: 9px 18px;
  border: 1px solid transparent;
  border-radius: var(--r);
  font-size: var(--t-small);
  font-weight: 500;
  line-height: 1.25;
  white-space: nowrap;
  text-decoration: none;
  transition: color 200ms cubic-bezier(0.4, 0, 0.2, 1), background-color 200ms cubic-bezier(0.4, 0, 0.2, 1), border-color 200ms cubic-bezier(0.4, 0, 0.2, 1), transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* The press needs somewhere to go; it is most of what makes a flat rectangle feel like a control. */
.button:active,
.secondary-button:active {
  transform: translateY(1px);
}

@media (prefers-reduced-motion: reduce) {
  .button,
  .secondary-button {
    transition: none;
  }

  .button:active,
  .secondary-button:active {
    transform: none;
  }
}

/* Under a dark scheme the accent is brand-300 and this inverts to a pale button with dark type,
   which is what the site's onDark variant does: brand-700 on ink-950 has nowhere near the punch a
   primary action needs. */
.button {
  background: var(--np-accent);
  color: var(--np-on-accent);
}

.button:hover {
  background: var(--np-accent-hover);
}

/* Outlined controls resolve towards the accent on hover rather than towards black. Grey going darker
   grey reads as a disabled control waking up; picking up the accent reads as a link, which is what
   they are. */
.secondary-button {
  background: var(--np-panel);
  border-color: var(--np-rule-strong);
  color: var(--np-head);
}

.secondary-button:hover {
  border-color: var(--np-accent);
  color: var(--np-accent);
}

.button[disabled],
.secondary-button[disabled],
.button[aria-disabled='true'] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* Flat and wordy, but still a control: inline-flex with a floor so it clears the 32px target the
   phone audit measures. Without it every row action and the sign-out link came in at about 22px. */
.text-button {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  background: none;
  border: 0;
  padding: 0;
  font-size: var(--t-small);
  font-weight: 500;
  text-align: left;
  color: var(--np-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--np-rule-strong);
  transition: text-decoration-color 200ms;
}

.text-button:hover {
  text-decoration-color: var(--np-accent);
}

/* Everything that takes typing, found by exclusion rather than by listing the types someone thought
   of. The draft editor writes an input with no type attribute at all and another with type="url";
   both were dressed as 26px browser defaults while the list looked complete. */
input:not([type='checkbox']):not([type='radio']):not([type='file']):not([type='hidden']):not([type='range']):not([type='color']):not([type='submit']):not([type='button']),
textarea,
select {
  min-height: 38px;
  padding: 8px 11px;
  background: var(--np-panel);
  border: 1px solid var(--np-field-edge);
  border-radius: var(--r);
  color: var(--np-head);
  font-size: var(--t-small);
  /* A select's content box is the 20px its own min-height leaves; an input's is whatever `normal`
     line-height makes of the font, which was 21.69. Side by side in a filter row that aligns on the
     bottom, the input stood 1.69px proud of every select next to it. */
  line-height: 20px;
}

input:focus,
textarea:focus,
select:focus {
  border-color: var(--np-accent);
}

/* The file control keeps the browser's own button otherwise — a mid grey push button that belongs to
   no palette here, sitting inside an otherwise designed field. It is the one control on the draft
   page and the logo dialog that gave the game away. */
input[type='file'] {
  min-height: 38px;
  padding: 7px 10px;
  background: var(--np-panel);
  border: 1px solid var(--np-field-edge);
  border-radius: var(--r);
  color: var(--np-text);
  font-size: var(--t-small);
}

input[type='file']::file-selector-button {
  margin-right: 12px;
  min-height: 28px;
  padding: 4px 12px;
  background: var(--np-panel);
  border: 1px solid var(--np-field-edge);
  border-radius: var(--r);
  color: var(--np-head);
  font-family: inherit;
  font-size: var(--t-small);
  font-weight: 500;
  cursor: pointer;
  transition: color 200ms, border-color 200ms;
}

input[type='file']::file-selector-button:hover {
  border-color: var(--np-accent);
  color: var(--np-accent);
}

/* Drawn rather than left to the user agent. Under a dark scheme the platform renders an unticked
   box as a filled grey square, and next to a ticked one it reads as "on, but disabled" — light and
   dark disagreeing about what off looks like. They were also the only rounded things on a page of
   square cards. Hollow when off, filled in the accent when on, in both schemes. */
input[type='checkbox'],
input[type='radio'] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  display: inline-grid;
  place-content: center;
  width: 18px;
  height: 18px;
  margin: 0;
  background: var(--np-panel);
  border: 1px solid var(--np-field-edge);
  border-radius: 0;
  cursor: pointer;
  transition: background-color 150ms, border-color 150ms;
}

input[type='radio'] {
  border-radius: 50%;
}

input[type='checkbox']:hover,
input[type='radio']:hover {
  border-color: var(--np-accent);
}

input[type='checkbox']:checked,
input[type='radio']:checked {
  background: var(--np-accent);
  border-color: var(--np-accent);
}

/* The tick is two borders on a rotated box, so it needs no image and no data URI. */
input[type='checkbox']:checked::after {
  content: '';
  width: 5px;
  height: 9px;
  margin-top: -2px;
  border: solid var(--np-on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

input[type='radio']:checked::after {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--np-on-accent);
}

input[type='checkbox']:disabled,
input[type='radio']:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  input[type='checkbox'],
  input[type='radio'],
  input[type='file']::file-selector-button {
    transition: none;
  }
}

/* A value the page worked out rather than one a person typed. Grey until it is theirs. */
.ghost {
  color: var(--np-ghost);
}

/* ==========================================================================
   SHELL
   The rail follows the scheme rather than staying dark through both. A permanently dark bar beside a
   paper page is the one shape this redesign is trying to get away from, and the site's own chrome —
   a header divided into cells by hairlines, in the same paper as the page — is the alternative it
   already worked out. So the rail is a title block on the same sheet, and what tells it apart from
   the page is an edge, not a colour.
   ========================================================================== */
.shell {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  min-height: 100vh;
}

.sidebar {
  display: flex;
  flex-direction: column;
  padding: 26px 18px 24px;
  background: var(--np-rail);
  border-right: 1px solid var(--np-rule);
}

.sidebar .brand {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  text-decoration: none;
  color: var(--np-head);
}

.sidebar .brand img {
  display: block;
  width: 40px;
  height: 46px;
  flex: none;
  object-fit: contain;
}

/* Two exports of the same mark, one drawn for a light page and one for a dark one. Showing and
   hiding beats recolouring: the mark is two-tone and a filter would flatten it.

   Every one of these carries `.sidebar .brand` in front of it, because the rule that sizes the
   images does too. Written as a bare class the hide loses the cascade to it on specificity alone,
   and both marks are drawn, side by side. */
.sidebar .brand .brand-mark-dark {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .sidebar .brand .brand-mark-light { display: none; }
  :root:not([data-theme='light']) .sidebar .brand .brand-mark-dark { display: block; }
}

:root[data-theme='dark'] .sidebar .brand .brand-mark-light { display: none; }
:root[data-theme='dark'] .sidebar .brand .brand-mark-dark { display: block; }
:root[data-theme='light'] .sidebar .brand .brand-mark-light { display: block; }
:root[data-theme='light'] .sidebar .brand .brand-mark-dark { display: none; }

.sidebar .brand .brand-wordmark {
  display: block;
  font-size: 19px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.sidebar .brand .brand-wordmark small {
  display: block;
  margin-top: 5px;
  font-family: var(--np-mono);
  font-size: var(--t-meta);
  font-weight: 500;
  letter-spacing: 0.18em;
  color: var(--np-muted);
}

.sidebar nav {
  display: grid;
  gap: 1px;
  margin-top: 26px;
}

.sidebar nav a {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 11px 9px 10px;
  border-left: 2px solid transparent;
  font-size: var(--t-small);
  text-decoration: none;
  color: var(--np-text);
  transition: color 160ms, background-color 160ms, border-color 160ms;
}

.sidebar nav a:hover {
  background: var(--np-sunken);
  color: var(--np-head);
}

/* Redline is the site's own active-nav indicator, and this is the same job. It stays redline under
   both schemes. It used to step to the accent in dark on the theory that a burnt orange against ink
   was too loud — but the sign-off panel's edge stayed redline either way, so dark ended up using one
   colour for "you are here" and another for "this needs you", and the marked-up-drawing character
   the light scheme has went with it. redline-400 measures 5.08:1 on the dark panel, which is not
   loud, it is legible. */
.sidebar nav a.active {
  border-left-color: var(--np-mark);
  background: var(--np-sunken);
  color: var(--np-head);
  font-weight: 500;
}

.sidebar nav .icon {
  flex: none;
  display: block;
  color: var(--np-faint);
}

.sidebar nav a.active .icon,
.sidebar nav a:hover .icon {
  color: var(--np-accent);
}

/* Data is the other half of the staff area rather than another page of this one. */
.sidebar nav a.nav-data {
  margin-top: 14px;
  padding-top: 18px;
  border-top: 1px solid var(--np-rule);
  border-top-left-radius: 0;
}

.surface {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The top bar is divided into cells by hairlines, the way the site's header separates its mark from
   its navigation from its call to action. The divisions carry the meaning: where you are, what you
   are looking for, who you are. */
.topbar {
  display: flex;
  align-items: center;
  min-height: 64px;
  background: var(--np-panel);
  border-bottom: 1px solid var(--np-rule);
}

/* Each cell centres its own contents and stretches to the full height of the bar, so the hairline
   dividing it from its neighbour runs the whole way down. Deliberately not `.topbar > *`: that would
   make the breadcrumb a flex container too, its text would become anonymous flex items, and
   text-overflow would have nothing to apply to — a long trail would clip with no ellipsis to say it
   had been cut. */
.topbar > .search,
.topbar > .client {
  align-self: stretch;
  display: flex;
  align-items: center;
}

.topbar > #breadcrumb {
  flex: 1;
  min-width: 0;
  display: block;
  padding: 0 22px;
  overflow: hidden;
  font-family: var(--np-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--np-muted);
  white-space: nowrap;
  text-overflow: ellipsis;
}

#breadcrumb a {
  color: var(--np-muted);
  text-decoration: none;
}

#breadcrumb a:hover {
  color: var(--np-accent);
}

/* The separator is a glyph a person reads past, so it takes the muted value like the rest of the
   trail rather than the decorative one. */
#breadcrumb i {
  font-style: normal;
  padding: 0 9px;
  color: var(--np-muted);
}

/* The trail is the label register, and the last crumb is the record's own name. A name is a value:
   SARAH WHITAKER and CALDER LOGISTICS LTD were the interface shouting proper nouns. The section links
   before it stay in the register, because those are labels. */
#breadcrumb span {
  text-transform: none;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--np-head);
}

.topbar .search {
  flex: none;
  padding: 0 18px;
  border-left: 1px solid var(--np-rule);
  position: relative;
}

.topbar .client {
  flex: none;
  gap: 14px;
  padding: 0 20px;
  border-left: 1px solid var(--np-rule);
  font-size: var(--t-meta);
  color: var(--np-muted);
}

/* Muted rather than faint: this is a person's role, which is text, and faint is 3.13:1 on paper —
   a value for a hairline or a tick, never for something anyone has to read. */
.topbar .client small {
  display: block;
  font-family: var(--np-mono);
  font-size: var(--t-meta);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--np-muted);
}

.topbar .client #user-name {
  display: block;
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--np-head);
}

/* An icon button, for the scheme toggle. Square like everything else its size, and the only thing in
   the bar that is not a word. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r);
  color: var(--np-muted);
  transition: color 200ms, border-color 200ms;
}

.icon-button:hover {
  color: var(--np-accent);
  border-color: var(--np-rule-strong);
}

/* One glyph per scheme, and only the one that describes what pressing it would do. */
.icon-button .to-dark { display: block; }
.icon-button .to-light { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .icon-button .to-dark { display: none; }
  :root:not([data-theme='light']) .icon-button .to-light { display: block; }
}

:root[data-theme='dark'] .icon-button .to-dark { display: none; }
:root[data-theme='dark'] .icon-button .to-light { display: block; }
:root[data-theme='light'] .icon-button .to-dark { display: block; }
:root[data-theme='light'] .icon-button .to-light { display: none; }

main {
  flex: 1;
  width: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 30px 40px 64px;
}

/* ==========================================================================
   DIALOG
   ========================================================================== */
dialog {
  padding: 0;
  max-width: 600px;
  width: calc(100% - 32px);
  max-height: 90vh;
  /* background-color, never the shorthand: the crop-mark rule above paints this element's
     background-image, and `background:` would reset it and take the corner ticks off every dialog. */
  background-color: var(--np-panel);
  color: var(--np-text);
  border: 1px solid var(--np-field-edge);
  border-radius: 0;
}

dialog::backdrop {
  background: var(--np-scrim);
}

/* A column, so the body takes whatever the head leaves rather than a number kept in step with it by
   hand. On [open] only: a bare `display` here would override the browser's `display:none` for a closed
   dialog and leave an invisible one on every page. */
dialog[open] {
  display: flex;
  flex-direction: column;
}

.dialog-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 24px;
  border-bottom: 1px solid var(--np-rule);
}

.dialog-head strong {
  font-size: var(--t-section);
  font-weight: 500;
  color: var(--np-head);
  letter-spacing: -0.015em;
}

.dialog-head button {
  flex: none;
  width: 34px;
  height: 34px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--r);
  font-size: 24px;
  line-height: 1;
  color: var(--np-muted);
}

.dialog-head button:hover {
  color: var(--np-head);
  background: var(--np-sunken);
}

/* ==========================================================================
   PHONE
   The rail leaves the grid and becomes a fixed bar along the bottom, where a thumb is. Everything
   that follows in staff.css and workspace.css breaks at this same width; the old arrangement broke
   at 640 in one file and 680 in another, so between those two the page belonged to neither.
   ========================================================================== */
@media (max-width: 1200px) {
  main {
    padding: 26px 26px 56px;
  }
}

@media (max-width: 640px) {
  .shell {
    display: block;
    padding-bottom: 74px;
  }

  .sidebar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 40;
    flex-direction: row;
    padding: 0;
    border-right: 0;
    border-top: 1px solid var(--np-rule);
    background: var(--np-panel);
    max-width: 100vw;
    overflow: hidden;
  }

  .sidebar .brand,
  .sidebar .side-bottom {
    display: none;
  }

  .sidebar nav {
    display: flex;
    flex: 1;
    gap: 0;
    margin: 0;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .sidebar nav::-webkit-scrollbar {
    display: none;
  }

  .sidebar nav a {
    flex: none;
    width: 76px;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    min-height: 58px;
    padding: 8px 4px;
    border-left: 0;
    border-top: 2px solid transparent;
    /* 12px: the floor the phone audit holds every width to (R1-114). 11px, and then 11.5, were under the
       floors of their day. */
    font-size: 12px;
    text-align: center;
  }

  .sidebar nav a.active {
    border-left-color: transparent;
    border-top-color: var(--np-mark);
  }

  .sidebar nav a.nav-data {
    margin-top: 0;
    padding-top: 8px;
    border-top: 2px solid transparent;
    border-left: 1px solid var(--np-rule);
  }

  /* The bar scrolls sideways, so its right edge fades to say so. It fades to the bar's own colour,
     which is a token rather than a hex precisely so it stays right in both schemes. */
  .sidebar::after {
    content: '';
    position: absolute;
    inset: 0 0 0 auto;
    width: 26px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--np-panel));
  }

  .topbar {
    flex-wrap: wrap;
    min-height: 0;
  }

  .topbar > #breadcrumb {
    order: 1;
    flex: 1 0 100%;
    padding: 12px 16px;
    border-bottom: 1px solid var(--np-rule);
  }

  .topbar .client {
    order: 2;
    flex: 1;
    justify-content: flex-end;
    padding: 8px 16px;
    border-left: 0;
  }

  .topbar .search {
    order: 3;
    flex: 1 0 100%;
    padding: 0 16px 12px;
    border-left: 0;
  }

  main {
    padding: 18px 16px 40px;
  }

  /* A link that is the whole content of something — a table cell, a heading, a card title, a name
     in bold, a step in the breadcrumb — is about 16 to 23px tall, because that is its line box. On a
     phone those are the main things a thumb goes for, so each gets a real target. Desktop keeps the
     tighter line: a pointer does not need 24px and these screens are dense on purpose. A link inside
     a sentence is left alone, here and in the checker, because making that one a block would break
     the sentence it sits in. */
  .table-wrap td a,
  .kv td a,
  #breadcrumb a,
  .row-title a,
  .section-head a,
  .tree-leaf > a,
  h1 > a,
  h2 > a,
  h3 > a,
  strong > a,
  p.eyebrow > a,
  .company-line,
  .data-link {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
  }

  /* A three-letter id is a 23px-wide target. Widening the box does not move the text, because the
     box still starts where it started. */
  .table-wrap td a,
  .kv td a {
    min-width: 24px;
  }

  /* iOS zooms the page when a field under 16px takes focus. The same exclusion chain as the base
     rule, not a list of types: a list scores (0,1,1) against the base rule's (0,8,1) and loses on
     specificity inside the media query, which is why the search box on every board stayed 14px and
     39.69px tall while the selects beside it went to 16px and 44. `select` and `textarea` are single
     names in both rules, so they were the only two the list was ever winning. */
  input:not([type='checkbox']):not([type='radio']):not([type='file']):not([type='hidden']):not([type='range']):not([type='color']):not([type='submit']):not([type='button']),
  textarea,
  select {
    font-size: 16px;
    min-height: 44px;
  }

  .button,
  .secondary-button {
    min-height: 44px;
  }

  dialog {
    max-height: 92vh;
  }

  /* The close cross is the one control in a dialog a thumb reaches for first. */
  .dialog-head button {
    width: 44px;
    height: 44px;
  }
}
