/* NIX — the design system
   ══════════════════════════════════════════════════════════════════════════
   Dieter Rams / Braun classic. An 8px grid, one accent, two type weights.

   THIS FILE IS THE ONLY PLACE A VALUE IS INVENTED. Everything after it —
   base, components, shell, the page sheets — spends what is declared here.
   A literal further down the load order is either drawing (the miniatures
   inside the index cards, where a 5px corner is a picture of a corner, not a
   UI radius) or a number that is arithmetic about a specific layout, and each
   of those says so where it stands.

   The five sheets, in load order, and what each one owns:

     tokens.css      this. Palette, both themes, the scales.
     base.css        the document: reset, the scroller, the ground it paints.
     components.css  the parts every page is built from.
     shell.css       the rail and the panel — the two states the site has.
     pages/<page>.css   what only that page has.

   views/partials/head.xml links them in that order and nothing else does.

   BREAKPOINTS ARE THREE, and they are not tokens because a media query
   cannot read a custom property:

     480px   one hand, portrait — where two columns stop being two columns
     640px   a phone held long ways — where a drawing has to be redrawn rather
             than scaled, and a two-field row has to stack
     720px   small tablet — where the rail starts covering instead of pushing,
             and a story page drops to one column
     900px   tablet — where two panels side by side stop fitting

   Anything else is a number somebody guessed once. If a layout needs a fifth,
   it needs a reason written next to it — /products has one, at 1100, and says
   so where it stands.

   640 AND 720 ARE NOT THE SAME LINE and the difference is worth the fifth
   number: 720 is about the SHELL (does the rail push or cover) and 640 is
   about CONTENT (does this drawing still read). Folding the second into the
   first put a 250px illustration in a 700px room.
   ══════════════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Light.woff2") format("woff2"),
       url("../fonts/PPNeueMontreal-Light.woff") format("woff");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "PP Neue Montreal";
  src: url("../fonts/PPNeueMontreal-Book.woff2") format("woff2"),
       url("../fonts/PPNeueMontreal-Book.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* THE NATIVE CONTROLS FOLLOW THE ATTRIBUTE TOO. `color-scheme: light dark`
   on :root hands the decision to the operating system, which was harmless
   only for as long as a media query painted the page the same way. Now that
   data-theme is the one mechanism, a reader on a dark laptop who chooses
   light got a light page with black scrollbars, black-filled inputs and dark
   <select> menus. Declared per theme, the two agree; with no script at all
   there is no attribute, no color-scheme, and the UA default matches the
   light page it would paint anyway. */
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }

:root {

  /* ── Ground, in three steps ───────────────────────────────────────────────
     paper is the page, panel is the box the page sits in when the rail is
     out, surface is anything standing on either of them. The two themes
     stack them in OPPOSITE directions: in dark the panel is below the page
     and the rail above it; in light the page is the darkest of the three and
     the rail the only true white. One token each is the only way to say both
     without a per-theme rule on every card. */
  --nix-paper: #F5F5F5;
  --nix-panel: #FAFAFA;
  --nix-surface: #FFFFFF;
  --nix-rail: #FFFFFF;
  /* the chip a control sits on INSIDE a surface.
     IT IS THE SAME VALUE AS --nix-surface IN LIGHT, and that is not an
     oversight to fix here: the chips that carry it on the index's orbit are
     lifted by a shadow, which is their edge on a light page. What must not
     rely on it is anything flat — a hover ground, a picture's placeholder —
     and those take --chip-hover or a mix of the text colour instead. In dark
     the two are a real step apart, which is the case this token was added
     for: the app icons floated with nothing under them. */
  --nix-chip: #FFFFFF;

  /* ── Ink ──────────────────────────────────────────────────────────────── */
  --nix-ink: #1A1A1A;      /* the inverted surface: buttons, the dark card */
  --nix-on-ink: #FFFFFF;   /* type on it */
  /* The second voice ON an ink surface. An opacity cannot serve here either:
     the same opacity over the two inks lands most of a step apart, which is the
     exact defect --nix-ink-soft was moved off #8A8886 to end. */
  --nix-on-ink-soft: #C9C8C6;
  /* THE SECOND VOICE ON AN INK-LIT SURFACE, and it is the twin of
     --nix-text-soft: same job, same rank, and it has to answer the same
     arithmetic. It was #8A8886, which is under the floor against white — and it
     carries the description under every card in the site's menu. */
  --nix-ink-soft: #5A5854;
  --nix-text: #1A1A1A;
  /* THE SECOND VOICE, AND IT HAS TO RANK THE SAME IN BOTH THEMES. It was
     #6B6965 here and #B3B1AD in dark — the same role reading as a quiet aside
     on one theme and as very nearly primary text on the other. The pair is
     asserted against all three grounds in both themes now, and the numbers
     are printed by check_contrast rather than written down here, because a
     number written down here is the defect this token's own history is. */
  --nix-text-soft: #5A5854;
  /* THE THIRD VOICE: a timestamp, a status, the inactive half of a switch.
     It was a dozen hard-coded opacities on --nix-text-soft — 0.5, 0.55, 0.6,
     0.65 — which land well under the floor in light and half a step higher
     in dark, so the quietest thing on the page was both illegible and a
     different illegible in each theme.

     The first pair chosen here was #85837F / #7C7A76, under a comment that
     stated a ratio nobody had computed and that was in fact under the floor
     on paper in light. Every site that spends this token is 11 to 13px text
     and none of it is large, so it is held to under the floor on all three grounds, in
     both themes, by check_contrast. */
  --nix-text-quiet: #6E6C68;
  /* The accent runs black while the site is monochrome. It stays a token so
     the day a colour arrives is a one-line day. */
  /* The ring's ink. It was --nix-orange, a name for a colour it has never
     held: it resolves to the text colour in both themes. */
  --focus-ink: #1A1A1A;
  --nix-rule: #E4E2DE;
  /* THE ONE COLOUR ON A MONOCHROME SITE, AND IT IS BORROWED, NOT INVENTED.
     agent-base's --accent-2 is --p-violet-500 in light and --p-violet-400 in
     dark (design-system.css), and it is what the app's own resizer turns when
     a pointer is on it. The seam between this site's two panels is the same
     control doing the same job, so it is the same colour rather than a second
     opinion about what "the handle is live" looks like. It appears on hover
     and while dragging only — at rest the seam is a neutral hairline, exactly
     as the app's is. */
  --nix-seam: #5568EA;

  --font-sans: "PP Neue Montreal", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ── Space: the 8px grid ──────────────────────────────────────────────────
     --space-N is N times 8, and only the steps something actually stands on
     are declared. 7 was read with a `3.5rem` fallback for as long as it was
     not declared, which is a token that works by accident.

     THE GRID GOVERNS LAYOUT, NOT DRAWING. What sits between two sections,
     between a heading and its list, or between a panel and the window is a
     step on this scale. What sits between a pill's label and its edge is not:
     a control's padding is drawn against its own type size, and rounding it
     to 8 makes a 13px label sit in a 16px room. --shell-pad (12) is the one
     in-between measure with a name, because the shell spends it everywhere. */
  --space-1: 8px;
  --space-2: 16px;
  --space-3: 24px;
  --space-4: 32px;
  --space-6: 48px;
  --space-7: 56px;
  --space-8: 64px;

  /* ── The shell's own measure ──────────────────────────────────────────────
     Borrowed by name from Agent Base's design system so the two products
     measure alike: its --space-3 is 12, its gutter 8, its --radius-xl 20, and
     the mark in its rail is 24 square. */
  /* NO HEADER UNTIL A PAGE SAYS OTHERWISE. Three documents measure their own
     bar and write this as an inline style on <html>, which outranks anything
     here. Every other page has no bar at all — and every reader of this token
     carried its own `, 82px` fallback, so /marketplace, /blog and the
     placeholder opened their menu overlay with a hundred pixels of dead band
     above the first card. The fallback is the estimate for a document that HAS
     a bar and has not measured it yet (components.css), not the default for
     one that has none. */
  --head-h: 0px;
  --shell-pad: 12px;
  --shell-gap: 8px;
  --rail-w: 260px;
  /* THE RAIL WHEN IT IS NOT OUT, AND THE MEASURES ARE THE APP'S, READ OFF ITS
     OWN SHEET rather than guessed at from a screenshot: agent-base's
     design-system.css declares --rail-width: 58 and --rail-item: 34, and its
     .rail pads 12 top and bottom with 8 between items. 64 and 40 were this
     module's own numbers and they are what made the strip read as a different
     object from the app's. */
  --rail-mini: 58px;
  --rail-item: 34px;
  --rail-logo: 24px;

  /* WHICH OF THE THREE THE RAIL CURRENTLY IS, and the gutter beside it. Every
     rule that has to measure the panel reads these rather than testing the
     body's class for itself — the grid, the seam, the orbit, the bleeding
     bands, the notifications panel on the account leg. shell.css overrides
     them per state; the default here is the strip, which is what a reader who
     has never touched the seam gets.

     Declared with the other rail measures rather than in shell.css, because
     portal.css spends them too and a token read from a sheet that happens to
     be linked first is a token that resolves to nothing the day the order
     changes. check_css asks the same question and answers it here. */
  --rail-track: var(--rail-mini);
  --rail-gap: var(--shell-gap);

  /* ── Radius ───────────────────────────────────────────────────────────────
     Four, and each one is a kind of object rather than a size: a pill is a
     control, xl is a card you can read, lg is a panel, sm is a chip. */
  --radius-pill: 999px;
  --radius-2xl: 24px;  /* a card you read: the menu cards, the drop's card */
  --radius-xl: 20px;    /* a panel: the rail, the content panel, a post */
  --radius-lg: 16px;    /* a box inside a panel: the sign-in card, a notice */
  --radius-chip: 12px;  /* a chip, a field, a tile */
  --radius-sm: 8px;     /* a row, a small control */

  /* ── Lift ────────────────────────────────────────────────────────────────
     A CARD is lifted, not outlined. Three things are outlined and each says
     why where it stands: the account popover in dark, which floats over
     arbitrary content and so cannot borrow an edge from a ground it does not
     sit on; the two text fields that sit directly on paper, which have
     nothing to be lifted off; and the controls that ARE a
     drawn ring — the menu trigger, the theme dot, the guide switch. Three steps, and each is a pair: a tight contact shadow
     that says the object touches the page, and a wide ambient one that says
     how far above it it sits.

     ON A DARK GROUND THERE IS NONE. A shadow is light being blocked and on
     #101010 there is no light to block: what the browser draws instead is a
     black smear that reads as dirt around the card. The step between --nix-
     panel and --nix-surface is the whole edge in dark and it is enough. That
     is what --lift-none is for, and why it is a token rather than a rule —
     ten sheets used to carry their own copy of the kill list, and two of them
     did not have one at all. */
  --lift-1: 0 1px 2px rgba(26, 26, 26, 0.05),
            0 12px 28px -12px rgba(26, 26, 26, 0.22);
  --lift-2: 0 1px 2px rgba(26, 26, 26, 0.06),
            0 14px 36px -14px rgba(26, 26, 26, 0.16);
  --lift-3: 0 2px 4px rgba(26, 26, 26, 0.06),
            0 24px 52px -16px rgba(26, 26, 26, 0.24);
  /* A CONTROL IS NOT A CARD. A pill sits ON the page rather than above it, so
     it takes a contact shadow and no ambient one — a button with a card's
     throw reads as a card somebody rounded. */
  --lift-control: 0 1px 3px rgba(26, 26, 26, 0.14);
  --lift-none: none;

  /* A SHADOW THAT IS NOT ONE OF THESE FOUR IS A DRAWING, and every one says so
     where it stands: the 90px throw under AB01's mock, which is a photograph
     of a window and not a card; the stamp under the sealed step on /services;
     the glow under the industry dial, which is coloured. Those keep their own
     numbers and their own dark treatment. */

  /* ── The focus ring ──────────────────────────────────────────────────────
     One ring, everywhere, so the day the accent stops being black is one
     line. Nine rules drew it as `2px solid var(--focus-ink)` and one drew it
     in --nix-text; both resolve to #1A1A1A today, which is exactly why the
     drift was invisible. */
  /* ONE RING, and it is the violet — the same hue the app rings with and the
     same value this file already carries as --nix-seam. The ink version was
     ink-on-ink in dark, which is a ring you can only see where the page
     happens to be pale. 3px, like the app's, because a 2px ring on a 20px
     radius disappears into the corner. */
  --focus-ring: 3px solid var(--nix-seam);
  /* 2, like the app's. The document called it one ring and then carried two
     offsets, which is the same drift one rung down. */
  --focus-offset: 2px;

  /* ── The edge of a field ──────────────────────────────────────────────────
     A CONTROL ON THE PAGE'S OWN GROUND CANNOT BE LIFTED OFF IT, so the two
     text fields that sit directly on paper are outlined instead. --nix-rule
     is the wrong token for it and was used anyway: it is a hairline BETWEEN
     things, and against paper it is barely a step — the border that replaced
     an invisible fill was very nearly as invisible, under a comment claiming
     it measured 3:1. This one is mixed to clear the 3:1 WCAG asks of a
     control's boundary, and check_contrast.py computes it rather than
     believing this sentence. */

  /* ── Motion ───────────────────────────────────────────────────────────────
     Six speeds, and which one to reach for is a question about what moves:

       --dur-1  a colour or an opacity under the pointer
       --dur-2  a chip, a link, anything that only has to acknowledge a click
       --dur-3  a control changing shape — a card lifting, a knob sliding
       --dur-4  a panel, a sheet, an overlay: something the size of the screen
       --dur-5  an entrance, one element
       --dur-6  an entrance, staggered — the reader is meant to watch it

     THE SCALE COVERS FEEDBACK, NOT CHOREOGRAPHY, and that is the honest
     line rather than an excuse. A control answering a pointer takes one of
     these six. A sequence — the preloader handing over, the hero arriving
     word by word, the orbit filling ring by ring — is tuned as a sequence:
     its steps are literal milliseconds because what matters is the interval
     between them, several of them are counted again in the script that
     starts them, and a token spent on one step of a stagger and not the next
     makes the pair unreadable. Those are written out, in ms, next to the
     delay they answer.

     A transition is always in ms. A loop that runs for ten seconds is in s,
     because that is how long it reads. */
  --dur-1: 90ms;
  --dur-2: 160ms;
  --dur-3: 250ms;
  --dur-4: 380ms;
  --dur-5: 640ms;
  --dur-6: 900ms;

  --ease-out-expo: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-pop: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* ── The stack ────────────────────────────────────────────────────────────
     Load-bearing in both directions, and the order is the whole point:

       --z-over    a panel laid over the page but under everything modal: the
                   guided-tour dock, the phone rail, the index's own veil
       --z-gate    the chooser, under the menu that can cover it
       --z-assist  the guide's screen, one step over the gate that opened it
       --z-menu    the menu sheet — opens BENEATH the mark
       --z-head    the bar
       --z-veil    the preloader: must cover the bar, or the menu button shows
                   through the intro
       --z-grain   the paper texture: must wash OVER the bar, or the bar reads
                   a shade darker than the page it sits on
       --z-wall    the app wall
       --z-doc     the app viewer, which opens FROM the wall and so is the one
                   thing above it

     Raise the bar past the veil and the preloader leaks. Past the grain and
     the bar goes dark. Both happened. */
  --z-over: 60;
  --z-gate: 78;
  --z-assist: 79;
  --z-menu: 80;
  --z-head: 85;
  --z-veil: 88;
  --z-grain: 90;
  --z-wall: 95;
  --z-doc: 96;

  /* TWO STEPS OFF THE CHIP, because the first one stopped being a hover.
     --nix-chip is #FFFFFF in light and so is the panel behind it, so anything
     flat that spent it had no edge — the store's whole catalogue. Those cards
     took --chip-tint to get one, and a card whose rest state is the hover
     tone has no hover left. 8% is a card at rest, 16% is a card under the
     pointer, and neither is the full-ink SELECTED state the rail uses. */
  /* THE CHIP LADDER, all four rungs in one place. --chip-sunk was living in
     pages/store.css as a hand-written color-mix in four selectors — a fourth
     surface invented in a page sheet, which is how a palette stops being one.
     --chip-float is what a control that LEAVES the plane sits on: the filter
     row and the back button used to take --nix-panel, which in dark is darker
     than the card they float on, so they read as holes punched through it. */
  --chip-sunk:  color-mix(in srgb, var(--nix-chip) 70%, var(--nix-paper));
  --chip-tint:  color-mix(in srgb, var(--nix-chip) 92%, var(--nix-text));
  --chip-hover: color-mix(in srgb, var(--nix-chip) 84%, var(--nix-text));
  --chip-float: color-mix(in srgb, var(--nix-chip) 74%, var(--nix-text));
  /* A row's own button, which used to be --chip-hover — the same value the row
     takes on hover, so the control vanished at the exact moment the pointer
     reached it. */
  --chip-control: color-mix(in srgb, var(--nix-chip) 78%, var(--nix-text));

  /* THE WATERMARK'S GROUND, SIGNED TOWARD THE TEXT. Drawn as a mix toward
     --nix-paper it stepped one way in light and the other in dark: the same
     mark was embossed on one theme and debossed on the other,. This always steps toward the ink, so it is a shadow of the same
     depth in both. */
  --mark-ground: color-mix(in srgb, var(--chip-tint) 86%, var(--nix-text));

  /* The uppercase eyebrow's tracking, which three selectors wrote as .08em
     by hand while the app's token said 0.09. One number, one place. */
  /* ── TYPE ─────────────────────────────────────────────────────────────
     The app's ramp, to the pixel, so the same component is the same size in
     the two places this product is sold. It was 32 selectors of `15px` and a
     dozen other literals here: same numbers, no name, so a change to the
     scale was a find-and-replace, which is how a scale stops being one.

     --text-display is the ONE sanctioned size above the ramp: the store's
     window, which is a full-bleed card and the only place a 34 reads small.
     Everything else that wants a bigger number is page artwork, not type. */
  --text-2xs:  11px;
  --text-xs:   12px;
  --text-sm:   13px;
  --text-base: 14px;
  --text-md:   16px;
  --text-lg:   20px;
  --text-xl:   26px;
  --text-2xl:  34px;
  --text-display: 40px;

  --tracking-wide: 0.09em;
  --tracking-tight: -0.02em;  /* display sizes, which set loose at 26px+ */
  --radius-xs: 4px;   /* a mark inside a card — the rung under --radius-sm */
}

/* ── Dark ─────────────────────────────────────────────────────────────────
   ONE MECHANISM, AND IT IS THE ATTRIBUTE. There used to be two: this block
   and a `@media (prefers-color-scheme: dark)` copy of it, and of every rule
   that reads the theme rather than a token — the logo's inversion, the
   toggle's fill, the shadow removal. Every one of those was written twice and
   two of them had already drifted.

   The boot script in views/partials/head.xml now writes data-theme on <html>
   before first paint, ALWAYS: the stored choice if there is one, the system's
   preference if there is not, and it follows the system live while there is
   no stored choice. So the attribute is never absent and the media copy had
   nothing left to answer.

   What it costs: with JavaScript off the site is light. That is honest here —
   these pages are a preloader, a gate and three canvases; without script
   there is no site to be dark. */
html[data-theme="dark"] {
  --nix-paper: #101010;
  --nix-panel: #161616;
  --nix-surface: #1A1A1A;
  /* The rail and the cards share a value here; only light pulls them apart. */
  --nix-rail: #1A1A1A;
  --nix-chip: #2E2D2B;

  --nix-ink: #F4F3F1;
  --nix-on-ink: #131312;
  --nix-on-ink-soft: #4A4947;
  --nix-ink-soft: #A5A39F;
  --nix-text: #F4F3F1;
  /* RAISED FROM #A5A39F. On the two chip grounds the store paints — on
     --chip-tint and on --chip-hover the old #A5A39F was under AA for every
     summary and byline in the catalogue. This clears the text floor on both,
     and check_contrast.py now carries the pair so it cannot slip again. */
  --nix-text-soft: #C0BEBA;
  --nix-text-quiet: #9A9894;
  --focus-ink: #F4F3F1;
  --nix-rule: #2E2D2B;
  /* --p-violet-400, which is what agent-base swaps its --accent-2 to on this
     ground: the 500 is measured for ink on white and goes muddy on #101010. */
  --nix-seam: #758AF0;

  /* No shadow on this ground — see --lift-none above. */
  --lift-1: none;
  --lift-2: none;
  --lift-3: none;
  --lift-control: none;
}

/* The ink logo turns white on the dark ground.

   logo.svg is drawn in ink and has no theme of its own — this filter is the
   ONLY thing that makes it visible in dark, and it is keyed to a list of
   classes rather than to the file. Every place the mark appears has to be
   named here; the footer mark was black on black until it was, and so was the
   first sign-in's .ob__mark — the same failure, repeated by the next page to
   draw the file. */
html[data-theme="dark"] .site-head__logo,
html[data-theme="dark"] .index-loader__logo,
html[data-theme="dark"] .index-foot__mark,
html[data-theme="dark"] .index__mark,
html[data-theme="dark"] .ob__mark {
  filter: brightness(0) invert(1);
}
