/* The typefaces the product actually uses.
 *
 * The first version set system-ui, which is why it read as generic: on a Mac
 * that is SF, on Windows Segoe, and on neither is it Bercroft. Outfit is a
 * geometric sans and it is 15KB — the same family of shapes as the references
 * this design was drawn from, where the numbers carry the screen.
 *
 * RupeeFix covers exactly one character, U+20B9, the rupee sign. Outfit's own
 * ₹ is poorly drawn, and a console full of prices is the last place to accept
 * that — so the glyph is borrowed from another face by unicode-range and
 * nothing else changes. Copied from the ERP, which learned this first.
 */
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/Outfit-var.woff2') format('woff2');
}
@font-face {
  font-family: 'RupeeFix';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/rupee.woff2') format('woff2');
  unicode-range: U+20B9;
}

/* ===========================================================================
 * THE BERCROFT CONSOLE
 *
 * Monochrome, with one accent. White or black, nothing in between.
 *
 * The previous version borrowed the ERP's consumer language — an aurora of
 * coloured gradients behind everything, translucent glass panels over it,
 * indigo furniture, cards that lift on hover. On a product people buy, that
 * works. On the instrument the company runs itself with, it reads as a
 * consumer app wearing a suit, and it was rejected in exactly those terms.
 *
 * WHAT REPLACED IT, AND WHY EACH DECISION EARNS ITS PLACE
 *
 * 1. NO GRADIENTS, NO BLUR, NO SHADOW ON ANYTHING PERMANENT. Surfaces are flat
 *    and separated by a hairline. A shadow is reserved for things that really
 *    are floating above the page — a dialog, a toast, the palette — so that
 *    when something lifts, the lift means something.
 *
 * 2. THE NUMBER IS THE CONTENT. Figures are set large and light with a small
 *    caption under them, rather than small and bold beside a large label. On a
 *    register of contracts the reader came for the quantity; the word next to
 *    it is a footnote.
 *
 * 3. ONE ACCENT, USED AS A MARKER. Bercroft's own green, taken from the
 *    website's stylesheet rather than picked: #0f7a5a on white, #34d399 on
 *    black. It marks the current thing and the positive thing. Everything else
 *    is ink on ground. A palette with one colour in it cannot be garish.
 *
 * 4. HAIRLINE RULES INSTEAD OF BOXES WHERE A BOX ADDS NOTHING. Tables are
 *    ruled, not striped and not framed.
 *
 * Colours come from ~/bercroft's own stylesheet: #0e0f11 ink, #0f7a5a green,
 * #34d399 for the brighter mark. Nothing here was invented.
 * ======================================================================== */

/* ================================================================= tokens */

:root {
  color-scheme: light;

  /* A GROUND WITH LIGHT IN IT, AND CARDS THAT FLOAT ON IT.
   *
   * Pure white with hairline-bordered white cards was correct and inert: on a
   * flat ground a card can only be found by its outline, so the screen reads as
   * a wireframe of boxes. The reference does the opposite — a very faint tinted
   * wash across the whole canvas, cool at one corner and warm at the other, with
   * cards that are a slightly brighter translucent white sitting on top of it.
   * The card is found by being LIGHTER than what is behind it, which is how
   * paper actually behaves, and the border can then almost disappear.
   *
   * Done with alpha compositing, not backdrop-filter. The frosted look costs one
   * blend the compositor does anyway; a real blur is recomposited on every
   * scroll and is the most expensive thing a page can ask for. Same picture,
   * none of the battery. */
  --ground: #f2f4f3;
  --wash-a: rgba(126, 158, 190, 0.16);   /* cool, top left */
  --wash-b: rgba(206, 214, 160, 0.16);   /* warm, bottom right */
  /* TRANSLUCENT IS FOR THINGS THAT SIT ON THE PAGE. NOTHING ELSE.
   *
   * A card is found by being brighter than the tinted ground behind it, and
   * that only works because it is see-through. The same token on a DIALOG is a
   * different thing entirely: a modal at 66% shows the page through itself, so
   * the form's labels land on top of the tiles behind them and neither can be
   * read. That shipped, and a screenshot of "Add somebody" over the Staff
   * screen is the clearest possible statement of the bug.
   *
   * The rule, now stated where the tokens are: anything floating ABOVE the page
   * is --surface-solid, and so is anything you type into. Only cards resting on
   * the ground get --surface. A test enforces it. */
  --surface: rgba(255, 255, 255, 0.66);
  --surface-solid: #ffffff;
  --surface-2: rgba(255, 255, 255, 0.42);
  --surface-3: rgba(15, 25, 20, 0.045);

  --ink: #0e0f11;
  --ink-2: #52555c;
  /* 5.27:1 on a white card, 4.77:1 on the tinted rail — both clear AA.
   *
   * It was #8a8d94, which is 3.32:1 and fails outright, and this is the colour
   * of every caption, axis label, unit and .dim in the product, so the failure
   * was everywhere at once.
   *
   * The first correction was #6e727a, chosen against pure white: 4.83:1, which
   * looked fine and was wrong, because half of what uses this sits on the
   * SIDEBAR, and that ground is #f2f4f3. On it the same ink measures 4.37:1 and
   * still fails. A colour has to clear on every ground it is used on, not on
   * the one that was convenient to check. */
  --ink-3: #686c74;

  /* Barely there. On a tinted ground the card's own brightness does the
     separating, so the border is a suggestion rather than a frame. */
  --line: rgba(15, 30, 25, 0.07);
  --line-2: rgba(15, 30, 25, 0.14);

  --accent: #0f7a5a;
  --accent-hover: #0b5f46;
  --accent-text: #0f7a5a;
  --accent-soft: rgba(15, 122, 90, 0.09);
  /* TWO INKS, BECAUSE THERE ARE TWO SURFACES.
   *
   * --on-accent sits on the bright accent: a badge, a chip. --on-hero sits on
   * the deep hero panel. One token served both and it was wrong in dark, where
   * the accent brightens to #34d399 and white on it measures 1.75:1 — a badge
   * nobody could read, shipped by me, found by the contrast check the moment it
   * existed. */
  --on-accent: #ffffff;
  --on-hero: #ffffff;

  /* THE HERO PANEL, SOFTENED AS FAR AS IT CAN HONESTLY GO.
   *
   * The reference's panel is #7b9bc4 with white text, which measures 2.87:1 and
   * fails AA outright — that softness is bought with legibility, and its small
   * print really is hard to read. #47806b is 4.60:1: as close to that dustiness
   * as white text can sit and still be readable by somebody who needs it to be.
   * Bercroft's green rather than the reference's blue, because the hue is the
   * one thing here that belongs to the company. */
  --hero: #47806b;
  --hero-deep: #33604f;

  /* A FAMILY OF PANELS AT ONE LIGHTNESS.
   *
   * Every register gets a tinted lead card so the screens are told apart at a
   * glance instead of being seven identical tables. Six unrelated colours would
   * read as a paint chart, so these are picked to one brief: all between 4.6:1
   * and 5.5:1 against white, which is as soft as AA permits and tight enough
   * that they look like one set. Every one carries white text and the same lime
   * mark, so the family holds together while the pages stay distinct. */
  --p-green:  #47806b;  --p-green-d:  #33604f;
  --p-teal:   #3c7b85;  --p-teal-d:   #2b5c64;
  --p-indigo: #5a6a9e;  --p-indigo-d: #414e78;
  --p-violet: #76608f;  --p-violet-d: #57466b;
  --p-clay:   #8a6247;  --p-clay-d:   #664733;
  --p-rose:   #99566a;  --p-rose-d:   #733e4f;
  --p-slate:  #5d6b75;  --p-slate-d:  #444f57;

  /* ONE HIGHLIGHT, AND IT IS ALWAYS A CHIP WITH DARK INK ON IT.
   *
   * Lime is unusable as text on white and superb as a background: #d4f34a under
   * near-black measures 15.3:1. So it marks — the current bar, the figure being
   * pointed at — and never writes. */
  --mark: #d4f34a;
  --on-mark: #0e0f11;

  /* A DATA PALETTE, NOT A DECORATION.
   *
   * One green everywhere made every card look like the same measurement. The
   * references each carry a bold hero hue plus three or four distinct colours
   * across their series — orange and blue and green in one chart, lime as a
   * highlight in another — and that is what lets the eye tell one quantity from
   * the next without reading a legend.
   *
   * Seven hues, each with a wash. Every one is picked to clear 4.5:1 on white,
   * because a chart colour that fails contrast is a chart nobody can read. They
   * are assigned to meanings in console.js and never used at random: a card
   * keeps its colour between visits, and a product keeps its colour for good. */
  --c-green:  #0f7a5a;  --c-green-soft:  #e7f4ef;
  --c-indigo: #4338ca;  --c-indigo-soft: #ebeafc;
  --c-amber:  #a45a06;  --c-amber-soft:  #fdf1e0;
  --c-teal:   #0e6f86;  --c-teal-soft:   #e4f4f8;
  --c-violet: #6d28d9;  --c-violet-soft: #f2ebfd;
  --c-rose:   #be123c;  --c-rose-soft:   #fdeaef;
  --c-lime:   #4d7c0f;  --c-lime-soft:   #f0f7e2;

  --ok: #0f7a5a;   --ok-soft: #eaf6f1;
  --warn: #8a5a00; --warn-soft: #fdf4e3;
  --bad: #b3261e;  --bad-soft: #fdeceb;

  /* Only for things genuinely above the page. */
  --shadow-float: 0 1px 3px rgba(14, 15, 17, 0.06), 0 12px 32px rgba(14, 15, 17, 0.10);
  --shadow-veil: 0 2px 8px rgba(14, 15, 17, 0.08), 0 24px 60px rgba(14, 15, 17, 0.18);

  /* Softer than it was (14/9). Every reference in the folder runs a much
     larger radius on its feature cards — 24 to 28px — which is what gives
     those dashboards their unhurried feel. Going the whole way looks like a
     toy at this information density, so this meets it partway: enough that
     the tiles read as objects rather than boxes, not so much that a table
     inside one looks like it is escaping. */
  --radius: 18px;
  --radius-sm: 11px;

  /* ONE GAP, EVERYWHERE.
   *
   * The mosaic used 12px, the two-column blocks 16px, the detail split 18px and
   * the notes between them 18px of margin. Four values doing one job, so no two
   * things on the screen were ever quite the same distance apart, and the whole
   * page read as slightly loose without it being obvious why. */
  --gap: 12px;
  /* Every row of the mosaic is exactly this tall, so a card with three lines of
     small print cannot make the two beside it stand in a pool of empty space.
     The hero spans two of them plus the gap between.
   *
   * 200 is not a taste: it is the tallest a tile actually needs, measured. A
   * chart with its axis wants 197 once the type is at a readable size, and the
   * ring card 194. Anything smaller clips; anything larger is slack in every
   * row on the screen.
   *
   * It was 196 until the type grew by a point, and the spill assertion caught
   * the three pixels rather than letting a chart get quietly cropped — which is
   * exactly why that assertion exists. */
  --row: 200px;

  /* ONE TYPEFACE. --font-label held a string character-for-character identical
     to --font-ui, which is not two fonts today and is two tokens that can drift
     into two fonts tomorrow. It is gone; everything is Outfit.
   *
     --font-mono survives for one job and it is not decoration: a licence key, a
     key id, a machine hash and a diagnostics dump are read character by
     character, and a proportional face makes 1 and l and I the same shape.
     Everywhere else — the audit action, a ticket reference — it was styling
     pretending to be meaning, and those are Outfit now. */
  --font-ui: 'RupeeFix', 'Outfit', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, Consolas, monospace;
}

/* Full black, not a dark blue pretending to be black. The navy ground of the
   previous version was the same consumer language in a different key. */
/* WHY EVERY DARK BLOCK APPEARS TWICE.
 *
 * The base :root is the LIGHT palette; dark used to live only inside
 * @media (prefers-color-scheme: dark). That covers a machine set to dark, and
 * nothing else — so on a machine set to LIGHT, choosing dark in the console
 * did nothing at all and you stayed on the light palette. Five of the six
 * combinations of (what the machine prefers) x (what the person chose) were
 * right and that one was silently wrong, which matters because Windows
 * defaults to light and Windows is most of the customer base.
 *
 * A media query cannot be OR-ed with an attribute selector, so the dark rules
 * are stated twice: once for the un-stamped default, which only
 * prefers-color-scheme can decide, and once for the explicit choice, which has
 * to win whatever the machine prefers. The duplication is the cost of the
 * three states being genuinely three. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ground: #000000;
    --wash-a: rgba(96, 130, 165, 0.10);
    --wash-b: rgba(150, 170, 90, 0.07);
    /* Lighter than the ground, exactly as in daylight. */
    /* Worse on black: a dialog at 4.5% is all but invisible. */
    --surface: rgba(255, 255, 255, 0.045);
    --surface-solid: #141417;
    --surface-2: rgba(255, 255, 255, 0.025);
    --surface-3: rgba(255, 255, 255, 0.07);

    --ink: #fafafa;
    --ink-2: #a4a7ad;
    /* 6.47:1 on black, up from 4.68:1. Passing was not the same as readable at
       11px on a dark ground. */
    --ink-3: #8b8f97;

    --line: rgba(255, 255, 255, 0.08);
    --line-2: rgba(255, 255, 255, 0.16);

    /* The deep green goes muddy on black, so the mark brightens. Both are
       Bercroft's; the site uses them together. */
    --accent: #34d399;
    /* On black the gradient runs the other way — the bright mark into a deep
       green — so the panel still reads as lit rather than as a hole. */
    --accent-hover: #6ee7b7;
    --accent-text: #34d399;
    --accent-soft: rgba(52, 211, 153, 0.12);
    /* Near-black on the bright green: 11.4:1. White on it is 1.75:1. */
    --on-accent: #04140e;
    --on-hero: #ffffff;
    --hero: #2f6f58;
    --hero-deep: #1d4a3a;
    /* Deepened for black, keeping the same relationship to the ground. */
    --p-green:  #2f6f58;  --p-green-d:  #1d4a3a;
    --p-teal:   #2b626c;  --p-teal-d:   #1b424a;
    --p-indigo: #454f7c;  --p-indigo-d: #2c3454;
    --p-violet: #5b4a72;  --p-violet-d: #3c304d;
    --p-clay:   #6d4c37;  --p-clay-d:   #4a3325;
    --p-rose:   #7a4354;  --p-rose-d:   #532c39;
    --p-slate:  #47535c;  --p-slate-d:  #30393f;
    --mark: #d4f34a;
    --on-mark: #0e0f11;

    /* Lifted for black. The deep versions go muddy, and a chart hue that reads
       as brown on a dark ground is a hue that has stopped carrying meaning. */
    --c-green:  #34d399;  --c-green-soft:  rgba(52, 211, 153, 0.14);
    --c-indigo: #818cf8;  --c-indigo-soft: rgba(129, 140, 248, 0.14);
    --c-amber:  #fbbf24;  --c-amber-soft:  rgba(251, 191, 36, 0.14);
    --c-teal:   #22d3ee;  --c-teal-soft:   rgba(34, 211, 238, 0.14);
    --c-violet: #c084fc;  --c-violet-soft: rgba(192, 132, 252, 0.14);
    --c-rose:   #fb7185;  --c-rose-soft:   rgba(251, 113, 133, 0.14);
    --c-lime:   #a3e635;  --c-lime-soft:   rgba(163, 230, 53, 0.14);

    --ok: #34d399;   --ok-soft: rgba(52, 211, 153, 0.12);
    --warn: #e8b45e; --warn-soft: rgba(232, 180, 94, 0.12);
    --bad: #ff8b82;  --bad-soft: rgba(255, 139, 130, 0.12);

    --shadow-float: 0 1px 3px rgba(0, 0, 0, 0.6), 0 12px 32px rgba(0, 0, 0, 0.55);
    --shadow-veil: 0 2px 8px rgba(0, 0, 0, 0.7), 0 24px 60px rgba(0, 0, 0, 0.7);
  }
}
  :root[data-theme="dark"] {
    color-scheme: dark;
    --ground: #000000;
    --wash-a: rgba(96, 130, 165, 0.10);
    --wash-b: rgba(150, 170, 90, 0.07);
    /* Lighter than the ground, exactly as in daylight. */
    /* Worse on black: a dialog at 4.5% is all but invisible. */
    --surface: rgba(255, 255, 255, 0.045);
    --surface-solid: #141417;
    --surface-2: rgba(255, 255, 255, 0.025);
    --surface-3: rgba(255, 255, 255, 0.07);

    --ink: #fafafa;
    --ink-2: #a4a7ad;
    /* 6.47:1 on black, up from 4.68:1. Passing was not the same as readable at
       11px on a dark ground. */
    --ink-3: #8b8f97;

    --line: rgba(255, 255, 255, 0.08);
    --line-2: rgba(255, 255, 255, 0.16);

    /* The deep green goes muddy on black, so the mark brightens. Both are
       Bercroft's; the site uses them together. */
    --accent: #34d399;
    /* On black the gradient runs the other way — the bright mark into a deep
       green — so the panel still reads as lit rather than as a hole. */
    --accent-hover: #6ee7b7;
    --accent-text: #34d399;
    --accent-soft: rgba(52, 211, 153, 0.12);
    /* Near-black on the bright green: 11.4:1. White on it is 1.75:1. */
    --on-accent: #04140e;
    --on-hero: #ffffff;
    --hero: #2f6f58;
    --hero-deep: #1d4a3a;
    /* Deepened for black, keeping the same relationship to the ground. */
    --p-green:  #2f6f58;  --p-green-d:  #1d4a3a;
    --p-teal:   #2b626c;  --p-teal-d:   #1b424a;
    --p-indigo: #454f7c;  --p-indigo-d: #2c3454;
    --p-violet: #5b4a72;  --p-violet-d: #3c304d;
    --p-clay:   #6d4c37;  --p-clay-d:   #4a3325;
    --p-rose:   #7a4354;  --p-rose-d:   #532c39;
    --p-slate:  #47535c;  --p-slate-d:  #30393f;
    --mark: #d4f34a;
    --on-mark: #0e0f11;

    /* Lifted for black. The deep versions go muddy, and a chart hue that reads
       as brown on a dark ground is a hue that has stopped carrying meaning. */
    --c-green:  #34d399;  --c-green-soft:  rgba(52, 211, 153, 0.14);
    --c-indigo: #818cf8;  --c-indigo-soft: rgba(129, 140, 248, 0.14);
    --c-amber:  #fbbf24;  --c-amber-soft:  rgba(251, 191, 36, 0.14);
    --c-teal:   #22d3ee;  --c-teal-soft:   rgba(34, 211, 238, 0.14);
    --c-violet: #c084fc;  --c-violet-soft: rgba(192, 132, 252, 0.14);
    --c-rose:   #fb7185;  --c-rose-soft:   rgba(251, 113, 133, 0.14);
    --c-lime:   #a3e635;  --c-lime-soft:   rgba(163, 230, 53, 0.14);

    --ok: #34d399;   --ok-soft: rgba(52, 211, 153, 0.12);
    --warn: #e8b45e; --warn-soft: rgba(232, 180, 94, 0.12);
    --bad: #ff8b82;  --bad-soft: rgba(255, 139, 130, 0.12);

    --shadow-float: 0 1px 3px rgba(0, 0, 0, 0.6), 0 12px 32px rgba(0, 0, 0, 0.55);
    --shadow-veil: 0 2px 8px rgba(0, 0, 0, 0.7), 0 24px 60px rgba(0, 0, 0, 0.7);
  }


/* =================================================================== base */

* { box-sizing: border-box; }

/* THE HIDDEN ATTRIBUTE HAS TO WIN.
 *
 * The browser's own rule is [hidden] { display: none }, which is a plain
 * element-level rule and loses to any class that sets display. `.signin` sets
 * display: grid, so both `#setup` and `#signin` stayed on screen no matter what
 * the code said — stacked down the page, one above the other, with the app
 * below them both.
 *
 * Somebody spent a morning on that. They filled in the setup form, it worked,
 * the account was created and the app rendered — underneath two forms they
 * would have had to scroll past to see. From where they sat, pressing the
 * button did nothing.
 *
 * Worse, it was invisible to the tests: they asked whether element.hidden was
 * true, which it was. The attribute was set correctly the whole time and the
 * element was painted anyway. The suite now measures whether things are
 * actually on screen. */
[hidden] { display: none !important; }

/* The wash. Fixed, behind everything, painted once and never again — the
   compositor rasterises a static gradient layer and then leaves it alone, which
   is why this costs nothing while a backdrop-filter costs everything. */
body::before {
  content: '';
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(120% 90% at 0% 0%, var(--wash-a), transparent 62%),
    radial-gradient(110% 80% at 100% 100%, var(--wash-b), transparent 58%);
}

body {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Every figure on every screen lines up on its digits. Set once here rather
     than remembered in thirty places. */
  font-variant-numeric: tabular-nums;
}

button { font: inherit; color: inherit; background: none; border: none; padding: 0; cursor: pointer; }
a { color: var(--accent-text); }

button:focus-visible, a:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* =============================================================== the mark */

/* THE BERCROFT SYSTEMS LOCKUP, AND NOTHING SET BESIDE IT.
 *
 * This carried the falcon with "Bercroft" and "CONSOLE" set in type next to it.
 * The company has a finished lockup — falcon, name, and SYSTEMS underneath —
 * and putting our own typography beside a designed mark is how a brand ends up
 * with two of them. The word "Console" is gone: the sidebar it sits above makes
 * it obvious enough what this is.
 *
 * Named for its ink: bercroft-systems-dark is the dark drawing, for a light
 * ground. */
.brand-logo {
  display: block; flex: 0 0 auto;
  width: 132px; aspect-ratio: 471 / 168;
  background: url('/brand/bercroft-systems-light.png') center / contain no-repeat;
}
/* THREE STATES, NOT TWO.
 *
 * This swap used to live only inside @media (prefers-color-scheme: light), so
 * it fired when the machine was set to light and never otherwise. Choose light
 * explicitly on a Mac that is set to dark and the media query does not match:
 * the white artwork stayed, on a white sidebar, and the company's own mark
 * vanished. Caught by screenshotting the light theme, not by any assertion —
 * the bars measured fine on the same page.
 *
 * So the ink is chosen twice: once for the un-stamped default, which only
 * prefers-color-scheme can decide, and once for the explicit choice, which
 * must win regardless of what the machine prefers. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .brand-logo { background-image: url('/brand/bercroft-systems-dark.png'); }
  /* The phone's short mark needs the same swap, and it must come after the
     rule above or the sidebar's lockup would win it back. */
  :root:not([data-theme="dark"]) .topbar-brand { background-image: url('/brand/bercroft-dark.png'); }
}
:root[data-theme="light"] .brand-logo { background-image: url('/brand/bercroft-systems-dark.png'); }
:root[data-theme="light"] .topbar-brand { background-image: url('/brand/bercroft-dark.png'); }

/* ================================================================ sign in */

.signin { min-height: 100vh; display: grid; place-items: center; padding: 24px; }

.auth-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface-solid);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 32px 32px 28px;
}

/* Left-aligned, deliberately: the card's heading, its fields and its button
   all start at the same left edge, and a centred mark above a left-aligned
   form is a stray axis. The topbar centres its mark because there is nothing
   else on that line to align to; here there is. */
.auth-brand { display: flex; align-items: center; gap: 13px; margin-bottom: 24px; }
/* Bigger than the base 132px. The lettering inside the lockup is roughly a
   twelfth of its width, so at 132px "BERCROFT SYSTEMS" was set at about 11px
   beside a 21px heading — the company's name reading smaller than the word
   "Sign in" underneath it. */
.auth-brand .brand-logo { width: 172px; }
.auth-brand .brand-sub {
  font-family: var(--font-ui);
  font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-3);
}

.auth-h { font-size: 21px; font-weight: 500; letter-spacing: -0.015em; margin: 0 0 5px; }
.auth-sub { font-size: 13.5px; color: var(--ink-2); margin: 0 0 22px; line-height: 1.6; }
.auth-btn { width: 100%; justify-content: center; margin-top: 10px; padding: 11px; }

/* FACE ID.
 *
 * Quiet, and under the password button rather than above it: the password is
 * the way in that always works, and a device without a passkey must not be
 * looking at a bigger, brighter button it cannot use. Hidden entirely unless
 * this device has one — see offerPasskey().
 *
 * The glyph is the system's own four-cornered frame, drawn rather than
 * imported: a system symbol is not licensable as an image and a screenshot of
 * one would go soft on a 3× screen. */
.auth-passkey {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; margin-top: 10px; padding: 11px 14px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-solid); color: var(--ink);
  font: inherit; font-size: 15px; font-weight: 500; cursor: pointer;
}
.auth-passkey:hover { background: var(--surface-2); }
.auth-passkey:active { transform: translateY(0.5px); }
.auth-passkey[disabled] { opacity: .55; cursor: default; }
.auth-passkey .faceid { width: 21px; height: 21px; flex: none; color: var(--accent); }
/* A hairline above it, so it reads as a second way in rather than another
   field in the form. */
.auth-passkey::before {
  content: ""; position: absolute; left: 0; right: 0;
  border-top: 1px solid var(--line-soft, var(--line));
}
.auth-passkey { position: relative; margin-top: 18px; }
.auth-passkey::before { top: -9px; }
.auth-hint { margin: -2px 0 16px; font-size: 12.5px; color: var(--ink-3); line-height: 1.6; }
.auth-err {
  margin: 14px 0 0; font-size: 13px; line-height: 1.6;
  color: var(--bad); background: var(--bad-soft);
  border-radius: var(--radius-sm); padding: 9px 12px;
}
/* Not every message in that box is a failure. "Your account was created, now
   sign in" is good news and must not be painted as an error. */
.auth-err.calm { color: var(--ink-2); background: var(--surface-2); }
.auth-switch {
  margin: 18px 0 0; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 13px; color: var(--ink-3); text-align: center;
}
.auth-switch button { color: var(--accent-text); font-weight: 500; }
.auth-switch button:hover { text-decoration: underline; }

/* ================================================================= fields */

.field { margin-bottom: 15px; }
.field label {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-ui);
  font-size: 11px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 6px;
}
.field input[type=text], .field input[type=email], .field input[type=password],
.field input[type=number], .field input[type=tel], .field select, .field textarea {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  /* Solid. A box you type into must not show the page through what you typed. */
  background: var(--surface-solid);
  color: var(--ink);
  font: inherit; font-size: 14px;
}
.field textarea { min-height: 64px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.field-note { margin: -9px 0 15px; font-size: 12.5px; color: var(--ink-3); line-height: 1.55; }
/* SHOW / HIDE HAS TO BE HITTABLE.
 *
 * This was bare text: 33x18px, which works perfectly with a mouse and is most
 * of half the 44px a thumb needs. Reported as the toggle "not working" — it
 * was working, every time it was actually hit, which on a phone was not often.
 *
 * The padding grows the target and the matching negative margin keeps the
 * label row exactly the height it was, so nothing else moves. */
.reveal {
  margin-left: auto; font-family: var(--font-ui);
  font-size: 11.5px; letter-spacing: 0.04em; color: var(--accent-text); text-transform: none;
  padding: 13px 10px;
  margin-top: -13px; margin-bottom: -13px; margin-right: -10px;
  min-width: 44px;
}
.reveal:hover { text-decoration: underline; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
@media (max-width: 560px) { .grid2 { grid-template-columns: 1fr; } }

/* Checkbox rows read as sentences, not as uppercase labels. */
.field label:has(input[type=checkbox]) {
  text-transform: none; letter-spacing: 0; font-size: 13.5px;
  font-weight: 400; color: var(--ink); gap: 9px;
}

.mods {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 5px 12px; max-height: 220px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 11px 13px;
}
.mods label {
  display: flex; align-items: center; gap: 8px; margin: 0;
  font-family: var(--font-ui); font-size: 13px; font-weight: 400;
  text-transform: none; letter-spacing: 0; color: var(--ink-2);
}

/* ================================================================= chrome */

/* A SIDEBAR, NOT A ROW OF TABS.
 *
 * Three tabs across the top was the honest shape of a three-screen product, and
 * it is why the console read as an afterthought. Grouping is the part that
 * carries: a person learns where things live once and then navigates without
 * reading. Tabs cannot group, so every new screen made the row longer and the
 * product shallower. */
#app {
  display: grid;
  grid-template-columns: 228px minmax(0, 1fr);
  min-height: 100vh;
}

.skip {
  position: absolute; left: 12px; top: -60px; z-index: 90;
  background: var(--ink); color: var(--ground);
  padding: 9px 16px; border-radius: var(--radius-sm);
  font-size: 13.5px; font-weight: 500; text-decoration: none;
  transition: top 0.16s ease;
}
.skip:focus { top: 12px; }

.side {
  display: flex; flex-direction: column;
  border-right: 1px solid var(--line);
  background: var(--ground);
  position: sticky; top: 0; height: 100vh;
  /* The rail itself no longer scrolls: only the nav inside it does. It used to
     scroll as one piece, which carried the signed-in name and the sign-out
     button off the bottom of a phone — the two things you most need to reach
     when you have opened the menu to leave. */
  overflow: hidden;
}

.brand {
  display: flex; align-items: center; justify-content: space-between;
  padding: 20px 18px 18px;
}
/* No page shows beside a full-screen drawer, so there is nothing to tap to
   dismiss it. Present only where that is true. */
/* The ring reads right at 34px, but 34px is under the 44px a thumb is
   entitled to — and this is the "get me out of here" control, so it is the
   last one that should need aiming at. The BUTTON is 44 and the ring is
   drawn inside it, rather than the button being 34 with an invisible
   overhang: an overhang only extended the target on the side with nothing
   next to it, which a hit test at each corner showed plainly. */
.side-close {
  display: none;
  position: relative;
  flex: none; width: 44px; height: 44px; padding: 0; margin-right: -5px;
  align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--ink-2); cursor: pointer;
}
.side-close::before {
  content: ''; position: absolute; inset: 5px;
  border: 1px solid var(--line); border-radius: 50%;
}
.side-close:hover { color: var(--ink-1); }
.side-close:hover::before { border-color: var(--line-2); }

/* The same lockup in the rail, just smaller. It was ELEVATION's mark here for a
   while — the product's identity on the company's own back office, wrong the way
   a customer's logo on your invoice is wrong. */
.brand .brand-logo { width: 116px; }

/* Search sits above the navigation because it is faster than the navigation.
   The shortcut is printed on it so people find it without being told. */
.side-search {
  display: flex; align-items: center; gap: 8px;
  margin: 0 12px 16px; padding: 7px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--ink-3); font-size: 13px;
  transition: border-color 0.14s ease, color 0.14s ease;
}
.side-search:hover { border-color: var(--line-2); color: var(--ink-2); }
.side-search span { flex: 1; text-align: left; }
kbd {
  font-family: var(--font-ui); font-size: 11px;
  border: 1px solid var(--line); border-radius: 4px;
  padding: 1px 4px; color: var(--ink-3); background: var(--surface-2);
}

/* min-height:0 is what actually lets this scroll — without it a flex child
   refuses to shrink below its content and the overflow lands on the rail. */
.nav {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 0 12px 16px; display: flex; flex-direction: column; gap: 1px;
}
.nav-group + .nav-group { margin-top: 18px; }
.nav-label {
  font-family: var(--font-ui);
  font-size: 10px; font-weight: 600; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-3);
  padding: 6px 10px 6px;
}
.nav-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left;
  padding: 7px 10px; border-radius: var(--radius-sm);
  color: var(--ink-2); font-size: 14px;
  /* These became real <a> elements so the browser can open them, and picked up
     the default underline with it. Eight underlined words down the rail read as
     a list of footnotes rather than as navigation. */
  text-decoration: none;
  transition: background 0.12s ease, color 0.12s ease;
}
.nav-item svg { flex: none; width: 16px; height: 16px; opacity: 0.65; }
.nav-item:hover { background: var(--surface-2); color: var(--ink); }
/* The current screen is filled, not tinted. On a monochrome ground a wash of
   pale colour is barely visible; solid ink is unmistakable at a glance. */
.nav-item.on { background: var(--ink); color: var(--ground); font-weight: 500; }
.nav-item.on svg { opacity: 1; }
.nav-item.on:hover { background: var(--ink); color: var(--ground); }

/* A count beside a section only earns its place when it means "somebody has to
   do something". A badge showing how many companies exist is noise; one showing
   two orders waiting for approval is the reason you opened the console. */
.nav-count {
  margin-left: auto;
  font-size: 11.5px; font-weight: 600;
  min-width: 17px; text-align: center;
  padding: 0 5px; border-radius: 99px;
  background: var(--accent); color: var(--on-accent);
}
.nav-item.on .nav-count { background: var(--ground); color: var(--ink); }

.side-foot {
  display: flex; align-items: center; gap: 10px;
  margin: 0 12px 14px; padding: 9px 10px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
.avatar {
  flex: none; width: 27px; height: 27px; border-radius: 50%;
  background: var(--ink); color: var(--ground);
  display: grid; place-items: center;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
}
.side-who { min-width: 0; flex: 1; }
.side-name {
  font-size: 13px; font-weight: 500; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.side-role {
  font-family: var(--font-ui);
  font-size: 10px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-3);
}
.icon-btn {
  flex: none; display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 7px; color: var(--ink-3);
  transition: background 0.12s ease, color 0.12s ease;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }

main { padding: 30px 32px 64px; max-width: 1240px; width: 100%; margin: 0 auto; }
main:focus { outline: none; }

/* ============================================================ view header */

/* ================================================== THE MASTHEAD
 *
 * Every screen used to open with an eyebrow and a heading floating in black —
 * correct, and completely anonymous. This is Bercroft's own register, and it
 * should look like it belongs to somebody.
 *
 * Two devices, both drawn in CSS so no markup changes and nothing new is
 * fetched: a rule down the left in the accent, and the falcon set large and
 * almost invisible on the right, bleeding off the edge the way a watermark on
 * headed paper does. Three per cent opacity — present when you look for it,
 * never competing with a number. */
.vhead {
  position: relative;
  margin-bottom: 26px;
  padding: 15px 0 2px;
  isolation: isolate;
}
/* THE RULE LIES ABOVE THE WORDS, NOT BESIDE THEM.
 *
 * Twice wrong before this. First as 18px of left padding, which pushed the
 * heading in while the band and tiles below started at the content edge — the
 * top tile looked out of line because it was. Then as a vertical rule pulled
 * out into the gutter, which fixed the alignment and created two new faults:
 * measured, it sat 6px from the screen edge and 8px clear of the text, so it
 * crowded the edge and belonged to nothing.
 *
 * Horizontal solves both at once. Everything — rule, eyebrow, heading, band,
 * tiles — starts at the same left edge, and a short bar above a label is a
 * device that reads as deliberate rather than as a stray line. */
.vhead::before {
  content: ''; position: absolute; left: 0; top: 0;
  width: 26px; height: 3px; border-radius: 99px;
  background: linear-gradient(to right, var(--accent), transparent);
}
/* The bird. pointer-events:none and z-index below the text, so it can never
   swallow a click or sit on top of a heading. */
/* right:0, not a negative inset. Bleeding it 14px past the edge looked right
   on a desktop and gave a 390px phone a sideways scroll — measured as
   "vhead 376>362" by the overflow guard, which is exactly the width of the
   bleed. Nothing on this page may push the page sideways. */
.vhead::after {
  content: ''; position: absolute; right: 0; top: 50%;
  transform: translateY(-50%);
  width: 104px; height: 150px;
  background: url('/brand/falcon-light.png') right center / contain no-repeat;
  /* 0.07, up from 0.03. Three per cent of white on black survives a rendered
     screenshot and disappears on an actual phone — reported as simply not
     being there in dark mode, and that is a fair description of 3% on an OLED
     panel at ordinary brightness. Still a watermark; it just exists now. */
  opacity: 0.07; pointer-events: none; z-index: -1;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .vhead::after {
    /* Dark ink on a light ground carries further than light ink on a dark one,
       so this stays lower than the dark theme's 0.07. */
    background-image: url('/brand/falcon-dark.png'); opacity: 0.055;
  }
}
:root[data-theme="light"] .vhead::after {
  background-image: url('/brand/falcon-dark.png'); opacity: 0.055;
}

.eyebrow {
  font-family: var(--font-ui);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 7px;
}
.vhead-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
/* Bigger and tighter than it was (32px). A register's own name should be the
   first thing read from across a desk, not the fourth. */
.vhead h1 {
  margin: 0; font-size: 40px; font-weight: 500; letter-spacing: -0.035em; line-height: 1.05;
}
.vhead-meta { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.vhead-sub { color: var(--ink-3); font-size: 13.5px; margin-top: 7px; max-width: 74ch; line-height: 1.6; }

/* ================================================== THE BAND
 *
 * The overview's headline figure, out of the grid and across the width. The
 * gradient runs on the diagonal so the chart at the bottom sits in the darker
 * half and the figure at the top in the lighter — the number gets the contrast
 * and the bars get the ground, rather than both fighting over one flat fill.
 *
 * clamp() on the figure rather than a media query: it is 3.4rem on a phone and
 * 5.2rem on a desk with everything between, and there is no width at which it
 * suddenly jumps. */
/* The tone tokens live on .k.accent.p-*, so the band has to claim them too or
   it silently falls back to a hard-coded green that drifts from the palette. */
.band.p-teal   { --hero: var(--p-teal);   --hero-deep: var(--p-teal-d); }
.band.p-indigo { --hero: var(--p-indigo); --hero-deep: var(--p-indigo-d); }
.band.p-violet { --hero: var(--p-violet); --hero-deep: var(--p-violet-d); }
.band.p-clay   { --hero: var(--p-clay);   --hero-deep: var(--p-clay-d); }
.band.p-rose   { --hero: var(--p-rose);   --hero-deep: var(--p-rose-d); }
.band.p-slate  { --hero: var(--p-slate);  --hero-deep: var(--p-slate-d); }

.band {
  position: relative; overflow: hidden;
  border-radius: var(--radius);
  padding: 18px 26px 14px;
  margin-bottom: 14px;
  color: var(--on-hero);
  /* No hard-coded fallback. --hero and --hero-deep are declared on :root and
     redefined per theme, so a literal here would be a third green that only
     ever showed up if a token went missing — and would be wrong in one theme
     by construction. The invented #14584a was doing exactly that. */
  background: linear-gradient(152deg, var(--hero) 0%, var(--hero-deep) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
  animation: tile-in 460ms cubic-bezier(0.2, 0.75, 0.3, 1) backwards;
}
@media (prefers-reduced-motion: reduce) { .band { animation: none; } }

.band-top { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
.band-eyebrow {
  font-family: var(--font-ui);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--on-hero); opacity: 0.72;
}
.band-fig {
  /* 2.2rem at the bottom, not 2.9. At 2.9 the figure ran nearly the full width
     of a 390px screen and stopped reading as a number on a card and started
     reading as a banner. */
  font-size: clamp(2.2rem, 5.4vw, 4.4rem);
  font-weight: 300; letter-spacing: -0.045em; line-height: 1;
  font-variant-numeric: tabular-nums;
  margin: 2px 0 0;
}
.band-sub { font-size: 13px; opacity: 0.74; margin-top: 8px; }
/* Edge to edge: the negative inline margins pull the chart out to the band's
   own edges, past the padding that keeps the text comfortable. overflow:hidden
   above is what stops that becoming a scrollbar. */
/* 64px, down from 132. Two reasons, and the second is the better one.
   At 132 the overview stood 2,090px against a 900px viewport and tripped the
   "not several screens tall" guard — a headline that pushes everything a
   person came for below the fold is a splash screen, not a headline. And a
   chart that tall makes the CHART the hero when the number is: at 64 it reads
   as twelve months supporting a figure, which is the right way round. */
.band-chart { margin: 12px -26px 0; padding: 0 26px; }
.band-chart .chart { height: 64px; }
/* EVERYTHING ON THE BAND NEEDS THE BAND'S INK.
 *
 * These were missing and the contrast guard caught it in both themes: the
 * month labels came out at 1.15:1 and the change chip at 1.53:1, because both
 * were still painted in colours chosen for a neutral card and were sitting on
 * a saturated gradient. .k.accent has carried the same overrides all along;
 * the band is a new surface and had to claim them for itself. */
.band .chart-axis { color: var(--on-hero); opacity: 0.66; }
/* A DARK chip, not a lighter one. White-on-white-at-16% measured 3.56:1 in the
   light theme, where the gradient itself is lighter — under the 4.5:1 that
   11px text needs. Darkening the chip instead of lightening the text works on
   both grounds, because the chip supplies its own contrast rather than
   borrowing whatever the band happens to be behind it. */
.band .delta {
  background: rgba(0, 0, 0, 0.38);
  color: #ffffff;
  border-color: transparent;
}
.band .chart .b { fill: var(--on-hero); opacity: 0.26; }
.band .chart .b.now { fill: var(--mark); opacity: 1; }

/* ================================================== ARRIVAL
 *
 * The tiles rise into place instead of appearing all at once. It is the one
 * piece of motion in the product and it earns its place twice: a screen that
 * assembles reads as a thing being built for you rather than a page that was
 * already there, and the stagger walks the eye left-to-right along the top row,
 * which is the order the numbers should be read in.
 *
 * `backwards` on the fill matters — without it every tile is briefly visible at
 * full opacity before its delay elapses, which is a flash of the finished
 * screen followed by it rebuilding itself.
 *
 * Six steps and then nothing: past the sixth the delay would be longer than
 * anybody will wait to see a number they came here for. */
@keyframes tile-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.mosaic > * { animation: tile-in 400ms cubic-bezier(0.2, 0.75, 0.3, 1) backwards; }
.mosaic > :nth-child(2) { animation-delay: 45ms; }
.mosaic > :nth-child(3) { animation-delay: 90ms; }
.mosaic > :nth-child(4) { animation-delay: 135ms; }
.mosaic > :nth-child(5) { animation-delay: 180ms; }
.mosaic > :nth-child(n+6) { animation-delay: 215ms; }
/* Not negotiable: somebody who has asked their machine for less movement has
   asked this too. */
@media (prefers-reduced-motion: reduce) {
  .mosaic > * { animation: none; }
}

/* Tabular figures everywhere, so a column of numbers lines up on the decimal
   and a changing value does not jiggle its neighbours.

   The lead figure was pushed to 52px here and put back: .k-n.big is already
   46px and the tile is full at that size — the layout guard caught it spilling
   its row on four screens. The heading and the arrival carry the drama
   instead, which they can do without moving anything. */
.k-n { font-variant-numeric: tabular-nums; }

/* ================================================================== cards */

.card, .panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* The lit top edge. A single hairline of brighter white along the top is what
   makes a translucent panel read as sitting ON something rather than being a
   hole cut in it. One inset shadow, no blur radius, no cost. */
.card, .panel, .k { box-shadow: inset 0 1px 0 rgba(255,255,255,0.55); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card,
  :root:not([data-theme="light"]) .panel,
  :root:not([data-theme="light"]) .k { box-shadow: inset 0 1px 0 rgba(255,255,255,0.06); }
}
  :root[data-theme="dark"] .card,
  :root[data-theme="dark"] .panel,
  :root[data-theme="dark"] .k { box-shadow: inset 0 1px 0 rgba(255,255,255,0.06); }

.panel { padding: 20px 22px; }
.card { overflow: hidden; }

.panel h2, .panel h3 {
  margin: 0 0 14px;
  font-family: var(--font-ui);
  font-size: 11px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-3);
}
/* A heading with an action beside it. */
.panel h2.with-act { display: flex; align-items: center; gap: 10px; }
.panel h2 .right { margin-left: auto; }
/* A heading is set in the small uppercase label face; a button sitting inside
   one must not inherit that, or "Full log" arrives as "FULL LOG" letter-spaced
   and reads as another heading rather than as something to press. */
.panel h2 .btn {
  font-family: var(--font-ui);
  text-transform: none; letter-spacing: 0; font-size: 12.5px; font-weight: 500;
  color: var(--accent-text);
}

.split { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--gap); align-items: start; }
@media (max-width: 1000px) { .split { grid-template-columns: minmax(0, 1fr); } }
/* Spacing between stacked panels comes from the container's gap, never from a
   margin on the panel.
 *
 * This was `.panel + .panel { margin-top: 16px }`, which is right for panels
 * stacked in a column and wrong everywhere else: inside a grid it applies to
 * every sibling after the first, so the second card in each row of the
 * dashboard sat exactly 16px lower than the card beside it. Two cards in one
 * row with their headings at different heights is precisely the "bad
 * alignment" this rebuild was asked to fix, and it was invisible in a
 * screenshot until the tops were measured. */
.split > div { display: grid; gap: var(--gap); align-content: start; }

/* STRETCH, NOT START.
 *
 * These panels size to their content, and align-items:start let one end short
 * while the one beside it ran on — a ragged bottom edge with a pool of nothing
 * under the shorter card. It is the same "uneven spacing" complaint as the
 * mosaic had, in the one container the mosaic's fixed rows did not cover.
 * Stretching makes both panels in a row the same height, which is what the
 * references do and what the eye expects of a row. */
.cols2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(440px, 1fr)); gap: var(--gap); align-items: stretch; }
.cols2 > .panel { display: flex; flex-direction: column; }
@media (max-width: 1000px) { .cols2 { grid-template-columns: minmax(0, 1fr); } }

/* ================================================== the card mosaic */

/* A GRID OF SMALL CARDS, NOT A STACK OF WIDE PANELS.
 *
 * This is the shape all five references share and it is the change that makes
 * the difference: label top-left, a quiet affordance top-right, a light-weight
 * number, then whatever explains it. Cards of two or three widths tiled
 * together, so the eye gets a rhythm instead of a column of slabs.
 *
 * PAINTED ONCE AND THEN LEFT ALONE. This has to be quick on a cheap Windows
 * laptop and kind to a battery, so: no backdrop-filter anywhere (a blur is
 * recomposited on every scroll and is the single most expensive thing a page
 * can ask for), no animation that runs without being asked, no shadow on
 * anything that repeats. Colour is a flat fill or a gradient the compositor
 * rasterises once. content-visibility lets the browser skip cards below the
 * fold entirely until they are needed.
 */
.mosaic {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: var(--gap);
  margin-bottom: var(--gap);
  grid-auto-rows: var(--row);
}
/* A REGISTER OF ONE MUST NOT LOOK LIKE A BROKEN ROW.
   With auto-fit and 1fr, a single card stretches the full width of the page —
   a 1,580px letterbox holding one product's name. Capping the track keeps a
   card a card whether there is one of them or nine. */
.mosaic.cap { grid-template-columns: repeat(auto-fit, minmax(232px, 340px)); }

.mosaic .wide { grid-column: span 2; }
.mosaic .tall { grid-row: span 2; }
/* A card that reads as a band under the others rather than one that happens to
   be wide. "Licences by state" spanning two of four columns left a hole beside
   it that looked like a card had failed to load. */
.mosaic .full { grid-column: 1 / -1; }

@media (max-width: 720px) { .mosaic .wide { grid-column: span 1; } }

.k {
  display: flex; flex-direction: column;
  min-height: 0; min-width: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 15px 17px 16px;
  min-width: 0;
  /* Cards below the fold cost nothing until they are scrolled to. */
  content-visibility: auto;
  contain-intrinsic-size: auto 150px;
}

.k-top { display: flex; align-items: center; gap: 8px; margin-bottom: 11px; min-height: 16px; }
/* A product's mark, sized to be READ rather than merely present. At 13px it
   was a dark smudge next to the label — technically the icon, legible as
   nothing. `contain` rather than a fixed size, because the next product's mark
   will not share Elevation's proportions. */
.k-icon {
  flex: none; width: 26px; height: 26px; border-radius: 7px;
  background: var(--surface-3) center no-repeat;
  background-size: 17px 17px;
  border: 1px solid var(--line);
}
.vhead-icon {
  display: block; width: 40px; height: 40px; border-radius: 10px;
  background: var(--surface-3) center / 24px no-repeat;
  border: 1px solid var(--line);
}
.k-label {
  font-family: var(--font-ui);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.11em; text-transform: uppercase;
  color: var(--ink-3);
  /* Wraps rather than truncates. It used to be nowrap + ellipsis, which in a
     two-column phone tile turned "WITH NOTHING YET" into "WITH NOTHING Y…".
     A label on two lines still reads; a label missing its last word does not. */
  overflow-wrap: anywhere;
}
/* The affordance every reference puts in the corner. Quiet until wanted. */
.k-go {
  margin-left: auto; flex: none;
  width: 20px; height: 20px; border-radius: 5px;
  display: grid; place-items: center;
  color: var(--ink-3); opacity: 0.6;
  transition: opacity 0.12s ease, background 0.12s ease;
}
.k-go svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.k:hover .k-go { opacity: 1; background: var(--surface-3); }

.k-n {
  display: flex; align-items: baseline; gap: 5px;
  font-size: 32px; font-weight: 300; letter-spacing: -0.035em; line-height: 1.05;
  color: var(--ink);
}
.k-n.big { font-size: 46px; letter-spacing: -0.04em; }
.k-u { font-size: 13.5px; font-weight: 400; color: var(--ink-3); letter-spacing: 0; }
.k-sub { font-size: 12.5px; color: var(--ink-3); margin-top: 7px; line-height: 1.45; }
/* Whatever the card carries under its number gets the remaining height, so a
   chart sits on the floor of the card rather than floating in the middle. */
.k > .chart, .k > .chart-axis, .k > .legend, .k > .scale, .k > .ring { margin-top: auto; }
/* A ring is a big round thing and it was sitting straight under its own
   caption — "live subscriptions" with the dial almost touching it. Everything
   else in a tile is a flat bar; this one needs air above it. */
.k > .ring { padding-top: 10px; }
.k > .chart { padding-top: 14px; }

/* THE ONE COLOURED CARD.
 *
 * Every reference has exactly one — an orange panel, a blue panel, a black
 * panel — and it is what stops a grid of white cards reading as a spreadsheet.
 * A two-stop gradient, which the compositor rasterises once and then never
 * thinks about again. */
/* Each register names its own panel; the overview keeps the house green. */
.k.accent.p-teal   { --hero: var(--p-teal);   --hero-deep: var(--p-teal-d); }
.k.accent.p-indigo { --hero: var(--p-indigo); --hero-deep: var(--p-indigo-d); }
.k.accent.p-violet { --hero: var(--p-violet); --hero-deep: var(--p-violet-d); }
.k.accent.p-clay   { --hero: var(--p-clay);   --hero-deep: var(--p-clay-d); }
.k.accent.p-rose   { --hero: var(--p-rose);   --hero-deep: var(--p-rose-d); }
.k.accent.p-slate  { --hero: var(--p-slate);  --hero-deep: var(--p-slate-d); }

.k.accent {
  background: linear-gradient(155deg, var(--hero) 0%, var(--hero-deep) 100%);
  border-color: transparent;
  color: var(--on-hero);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.14);
}
.k.accent /* A product's mark, sized to be READ rather than merely present. At 13px it
   was a dark smudge next to the label — technically the icon, legible as
   nothing. `contain` rather than a fixed size, because the next product's mark
   will not share Elevation's proportions. */
.k-icon {
  flex: none; width: 26px; height: 26px; border-radius: 7px;
  background: var(--surface-3) center no-repeat;
  background-size: 17px 17px;
  border: 1px solid var(--line);
}
.vhead-icon {
  display: block; width: 40px; height: 40px; border-radius: 10px;
  background: var(--surface-3) center / 24px no-repeat;
  border: 1px solid var(--line);
}
.k-label { color: var(--on-hero); opacity: 0.72; }
.k.accent .k-n { color: var(--on-hero); }
.k.accent .k-u { color: var(--on-hero); opacity: 0.7; }
.k.accent .k-sub { color: var(--on-hero); opacity: 0.78; }
.k.accent .chart-axis { color: var(--on-hero); opacity: 0.6; }
.k.accent .ring-track { stroke: var(--on-hero); opacity: 0.25; }
.k.accent .ring-fill { stroke: var(--on-hero); }
.k.accent .ring-n, .k.accent .legend-val { color: var(--on-hero); }
.k.accent .legend-label { color: var(--on-hero); opacity: 0.78; }
.k.accent .legend-row { border-color: rgba(255,255,255,0.16); }
.k.hero { grid-column: span 2; grid-row: span 2; justify-content: flex-start; }
/* A register's lead: the same panel, two columns wide and one row tall, so it
   sits in the mosaic beside its tiles rather than dominating the screen the way
   the overview's does. */
.k.lead { grid-column: span 2; }
@media (max-width: 720px) { .k.lead { grid-column: span 1; } }
.k.hero .k-n { margin-top: 4px; }
/* The hero is twice the height of its neighbours, so its chart takes the room
   rather than leaving a pane of empty colour above a thin strip of bars. */
.k.hero .k-sub { max-width: 62ch; }
@media (max-width: 720px) { .k.hero { grid-column: span 1; grid-row: span 1; } }

/* A CARD'S HUE, CARRIED BY EVERYTHING INSIDE IT.
 *
 * Set once on the card and inherited by its chart, its ring, its scale and its
 * dots — so a colour cannot drift out of step with the card it belongs to, and
 * so adding a hue is one line here rather than five. */
.k.tone-green  { --tone: var(--c-green);  --tone-soft: var(--c-green-soft); }
.k.tone-indigo { --tone: var(--c-indigo); --tone-soft: var(--c-indigo-soft); }
.k.tone-amber  { --tone: var(--c-amber);  --tone-soft: var(--c-amber-soft); }
.k.tone-teal   { --tone: var(--c-teal);   --tone-soft: var(--c-teal-soft); }
.k.tone-violet { --tone: var(--c-violet); --tone-soft: var(--c-violet-soft); }
.k.tone-rose   { --tone: var(--c-rose);   --tone-soft: var(--c-rose-soft); }
.k.tone-lime   { --tone: var(--c-lime);   --tone-soft: var(--c-lime-soft); }

/* The whole series takes the card's hue at low opacity, with the current month
   at full strength — the treatment every reference uses. Colouring only the
   current bar left eleven grey ones and a card that looked uncoloured whenever
   this month happened to be empty. */
.k[class*="tone-"] .spark-col:hover .b { opacity: 0.6; }
.k[class*="tone-"] .ring-fill { stroke: var(--tone); }
.k[class*="tone-"] .scale-fill { background: var(--tone); }
.k[class*="tone-"] .dot.accent { background: var(--tone); }
.k[class*="tone-"] .ring-track { stroke: var(--tone-soft); }
.k[class*="tone-"] .scale-track { background: var(--tone-soft); }
/* The label takes the hue rather than the number, so a wall of cards reads as
   one family of figures with differently coloured headings — not as seven
   different-coloured numbers competing. */
.k[class*="tone-"] /* A product's mark, sized to be READ rather than merely present. At 13px it
   was a dark smudge next to the label — technically the icon, legible as
   nothing. `contain` rather than a fixed size, because the next product's mark
   will not share Elevation's proportions. */
.k-icon {
  flex: none; width: 26px; height: 26px; border-radius: 7px;
  background: var(--surface-3) center no-repeat;
  background-size: 17px 17px;
  border: 1px solid var(--line);
}
.vhead-icon {
  display: block; width: 40px; height: 40px; border-radius: 10px;
  background: var(--surface-3) center / 24px no-repeat;
  border: 1px solid var(--line);
}
.k-label { color: var(--tone); }
a.k[class*="tone-"]:hover { border-color: var(--tone); }
a.k[class*="tone-"]:hover .k-n { color: var(--tone); }

/* A card that is a link. No lift, no shadow — the border darkens and the
   figure takes the accent, which is enough and costs one repaint. */
a.k { text-decoration: none; color: inherit; transition: border-color 0.12s ease; }
a.k:hover { border-color: var(--ink-3); }
a.k:hover .k-n { color: var(--accent-text); }
a.k.accent:hover { border-color: transparent; }
a.k.accent:hover .k-n { color: var(--on-hero); }

/* The change on last month, beside the label so the figure stays the biggest
   thing in the card. Rendered only where there is a real month to compare
   with — see api/_series.js. */
.delta {
  margin-left: auto; flex: none;
  font-size: 11px; font-weight: 600;
  padding: 2px 7px; border-radius: 99px;
  background: var(--surface-3); color: var(--ink-2);
  white-space: nowrap;
}
/* Quiet. The reference's are plain grey pills and it is right: a wall of red
   and green arrows makes every card shout, and the figure is the thing worth
   reading. The direction still shows, in the sign and in a tint you notice
   only when you look for it. */
.delta.up { background: var(--ok-soft); color: var(--ink-2); }
.delta.down { background: var(--bad-soft); color: var(--ink-2); }
/* Flat is neither good nor bad and must not be painted as either. */
.delta.flat { background: var(--surface-3); color: var(--ink-3); }
.k-top .delta + .k-go { margin-left: 0; }
.k.accent .delta { background: rgba(255,255,255,0.18); color: var(--on-hero); }

/* ============================================================== products */

/* One product today, and the console must not look like it assumes that. A row
   per product with its figures on the right, so the list reads the same with
   one in it as with six. */
.prods { display: grid; gap: 2px; }
.prod {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 10px; border-radius: var(--radius-sm);
  text-decoration: none; color: inherit;
  transition: background 0.12s ease;
}
.prod:hover { background: var(--surface-2); }
.prod-icon {
  flex: none; width: 34px; height: 34px; border-radius: 8px;
  background: var(--surface-3) center / 20px no-repeat;
  border: 1px solid var(--line);
}
.prod-txt { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.prod-name { font-size: 14px; font-weight: 500; }
.prod-tag { font-size: 12.5px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prod-n { flex: none; display: flex; flex-direction: column; align-items: flex-end; min-width: 74px; }
.prod-n b { font-size: 16px; font-weight: 500; }
.prod-n span {
  font-family: var(--font-ui);
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
}
@media (max-width: 700px) { .prod-n:not(:last-child) { display: none; } }

/* ================================================================ charts */

/* One <svg> per chart rather than one div per bar. A mosaic carries a chart in
   most of its cards; a div per bar is hundreds of layout boxes to style and
   paint on every resize, and on the machines this will actually run on that is
   the difference between a screen that appears and one that arrives. */
.chart { display: block; width: 100%; height: 56px; }

/* Pale bars with exactly one marked, which is how every reference draws a
   series: the shape is context and the highlighted column is the answer.
   currentColor, so the same rule works on a white card and on the coloured
   panel without a second copy of it. */
/* Faded out at the base, the way every reference draws them. The opacity that
   used to carry the whole bar now only sets its overall weight; the gradient
   does the falling away. */
.chart .b { fill: url(#barFade); opacity: 0.30; }
.chart .b.now { fill: url(#markFade); opacity: 1; }
/* An empty month is drawn as a visible baseline tick, not a hairline. At 1px
   it vanished, and a chart with four bars and eight invisible gaps reads as a
   chart that failed to load rather than as a quiet year. */
.chart .b.zero { fill: currentColor; opacity: 0.10; }

/* THE CALLOUT ABOVE THE MARKED COLUMN.
 *
 * The reference puts the value in a lime chip with a hairline dropping to the
 * bar, and it is the one detail that turns a sparkline into something a person
 * reads a number off rather than a texture. Positioned by percentage so it
 * tracks the bar at any card width without measuring anything. */
.chart-wrap { position: relative; padding-top: 24px; }
.chart-call {
  /* Anchored from the BOTTOM, at the height of the bar it belongs to, so the
     chip rides on top of its column instead of floating at a fixed altitude
     with a leader line reaching for nothing. */
  position: absolute; transform: translateX(-50%);
  background: var(--mark); color: var(--on-mark);
  font-size: 11.5px; font-weight: 600; line-height: 1.5;
  padding: 1px 7px; border-radius: 5px;
  white-space: nowrap; pointer-events: none;
}
.chart-call::after {
  content: ''; position: absolute; left: 50%; top: 100%;
  width: 1px; height: 7px; background: var(--mark);
}
.chart-axis {
  display: flex; justify-content: space-between;
  margin-top: 7px; font-size: 10.5px; color: var(--ink-3);
}

.ring { position: relative; width: 100%; max-width: 88px; margin: 4px auto 0; }
.ring svg { display: block; width: 100%; height: auto; }
.ring circle { fill: none; stroke-width: 8; stroke-linecap: round; }
.ring-track { stroke: var(--surface-3); }
.ring-fill { stroke: var(--accent); }
.ring-mid {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.ring-n { font-size: 19px; font-weight: 300; letter-spacing: -0.03em; line-height: 1; }
.ring-sub { font-size: 11px; color: var(--ink-3); margin-top: 4px; }

.scale { margin-top: 12px; }
.scale-track { height: 4px; border-radius: 99px; background: var(--surface-3); overflow: hidden; }
.scale-fill { display: block; height: 100%; border-radius: 99px; background: var(--accent); }
.scale-ends { display: flex; justify-content: space-between; margin-top: 5px; font-size: 10.5px; color: var(--ink-3); }

.legend { margin-top: 12px; }
.legend-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 0; border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.legend-row:last-child { border-bottom: none; }
.dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); }
.dot.ok { background: var(--ok); }
.dot.warn { background: var(--warn); }
.dot.bad { background: var(--bad); }
.dot.accent { background: var(--accent); }
.legend-label { color: var(--ink-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.legend-val { margin-left: auto; font-weight: 500; }

/* The old tile grid still used by the detail pages, kept in step with the
   mosaic so there is one card language and not two. */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: var(--gap); margin-bottom: var(--gap); grid-auto-rows: var(--row); }

/* ================================================================= tables */

.tbl-wrap { overflow-x: auto; }
.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.tbl th {
  font-family: var(--font-ui);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-3); text-align: left;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);
  white-space: nowrap;
}
.tbl td { border-bottom: 1px solid var(--line); padding: 12px 14px; white-space: nowrap; }
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl tbody tr { transition: background 0.1s ease; }
.tbl tbody tr:hover td { background: var(--surface-2); }
.tbl td.main { font-weight: 500; }
.tbl td.num, .tbl th.num { text-align: right; }
.tbl td.dim { color: var(--ink-3); }
.tbl td.mono { font-family: var(--font-mono); font-size: 12.5px; }
.cell-why { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; white-space: normal; }

/* Whole-row navigation. The name inside is a real <a> so the keyboard and the
   middle mouse button both work and the browser shows the target in the status
   bar; the row listens for clicks so the other seven columns are not dead
   space. */
.tbl tr.go { cursor: pointer; }
.tbl tr.go:hover td { background: var(--accent-soft); }
.tbl td a { color: inherit; text-decoration: none; }
.tbl tr.go:hover td.main { color: var(--accent-text); }
.tbl td a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ================================================================== chips */

.chip {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.07em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 99px;
  background: var(--surface-3); color: var(--ink-2);
  white-space: nowrap;
}
.chip.ok { background: var(--ok-soft); color: var(--ok); }
.chip.warn { background: var(--warn-soft); color: var(--warn); }
.chip.bad { background: var(--bad-soft); color: var(--bad); }
.chip.mute { background: var(--surface-3); color: var(--ink-3); }

.pill-row { display: flex; flex-wrap: wrap; gap: 6px; }
.pill {
  font-size: 12.5px; padding: 3px 10px; border-radius: 99px;
  border: 1px solid var(--line-2); color: var(--ink-2);
}

/* ================================================================ buttons */

.btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 15px;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--surface-solid); color: var(--ink);
  font-size: 13.5px; font-weight: 500;
  white-space: nowrap;
  transition: border-color 0.12s ease, background 0.12s ease;
  text-decoration: none;
}
.btn:hover { border-color: var(--ink-3); background: var(--surface-2); }
/* Solid ink, not the accent. The accent marks state; the ink marks the action,
   and on a monochrome screen a black button is unmissable without shouting. */
.btn.primary { background: var(--ink); border-color: var(--ink); color: var(--ground); }
.btn.primary:hover { background: var(--ink-2); border-color: var(--ink-2); }
.btn.ghost { background: none; border-color: transparent; color: var(--ink-2); }
.btn.ghost:hover { background: var(--surface-2); color: var(--ink); border-color: transparent; }
.btn.sm { padding: 5px 11px; font-size: 13px; }
.btn:disabled { opacity: 0.45; cursor: default; }

/* A register narrowed by a tile says so, and offers one way back. A filtered
   table that looks identical to an unfiltered one is how somebody concludes
   half their customers have vanished. */
/* Clear of the phone's sticky top bar when scrolled to, or the banner
   explaining the filter lands underneath it. */
.filtered { scroll-margin-top: 74px; }
.filtered {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px; margin-bottom: var(--gap);
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--surface); font-size: 13.5px;
}
.filtered-what { font-weight: 500; }
.filtered-n {
  font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
}
.filtered-clear { margin-left: auto; color: var(--accent-text); text-decoration: none; font-size: 13px; }
.filtered-clear:hover { text-decoration: underline; }

/* ================================================================ filters */

.filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.search-box {
  flex: 1; min-width: 210px; max-width: 340px;
  display: flex; align-items: center; gap: 9px;
  padding: 8px 13px;
  border: 1px solid var(--line-2); border-radius: var(--radius-sm);
  background: var(--surface-solid);
}
.search-box svg { flex: none; color: var(--ink-3); }
.search-box input {
  flex: 1; border: none; background: none; font: inherit; font-size: 13.5px; color: var(--ink);
  min-width: 0;
}
.search-box input:focus { outline: none; }
.search-box:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* A segmented filter rather than a dropdown. Every option is visible, each one
   carries its count, and choosing one is a single click — a register whose
   states are hidden behind a closed <select> makes people forget the states
   exist. */
.seg { display: flex; gap: 2px; padding: 2px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.seg button {
  padding: 5px 11px; border-radius: 7px;
  font-size: 13px; color: var(--ink-2);
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}
.seg button:hover { background: var(--surface-2); color: var(--ink); }
.seg button.on { background: var(--ink); color: var(--ground); font-weight: 500; }
.seg .n { color: var(--ink-3); margin-left: 5px; }
.seg button.on .n { color: var(--ground); opacity: 0.6; }

.count-line { font-size: 12.5px; color: var(--ink-3); margin: 0 0 12px; }

/* ============================================================= detail page */

.crumb { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; font-size: 13px; }
/* Shaped like a control, because it is one. As a bare grey word it read as a
   label and people concluded there was no way back off a sub-page. */
.crumb-back {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 11px 5px 8px;
  border: 1px solid var(--line-2); border-radius: 99px;
  color: var(--ink-2); text-decoration: none; font-weight: 500;
  transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}
.crumb-back svg {
  width: 13px; height: 13px; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.crumb-back:hover { border-color: var(--ink-3); color: var(--ink); background: var(--surface-2); }
.crumb-here { color: var(--ink-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Label above value, not beside it.
 *
 * Side by side, the longest label sets the column: "ON THE BOOKS SINCE" pushed
 * every value 150px right inside a 320px panel, leaving an email to break in
 * the middle of a word. Stacking gives the value the full width and reads down
 * the panel in one column instead of zig-zagging. */
.facts { display: grid; gap: 13px; margin: 0; font-size: 14px; }
.fact { min-width: 0; }
.facts dt {
  font-family: var(--font-ui);
  font-size: 10px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 2px;
}
.facts dd { margin: 0; color: var(--ink); min-width: 0; overflow-wrap: break-word; line-height: 1.45; }
.facts dd.dim { color: var(--ink-3); }
.facts dd.mono { font-family: var(--font-mono); font-size: 12.5px; }
.facts dd a { color: var(--accent-text); text-decoration: none; }
.facts dd a:hover { text-decoration: underline; }
.facts dd i { font-style: normal; color: var(--ink-3); }

/* Wide panels can afford several columns, which turns a 900px stack of twelve
   short facts into three lines. */
.panel-wide .facts { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 17px 26px; }

/* Three figures that are read together belong on one line. Stacked, "booked /
   paid / outstanding" ran half the height of the panel and the eye had to
   travel to compare the three numbers it is there to compare. */
.facts.row { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 16px 24px; }
.facts.row dd { font-size: 21px; font-weight: 300; letter-spacing: -0.02em; }
.facts.row dd b { font-weight: 400; }

/* =============================================================== worklist */

/* The dashboard's action blocks. A row here is always something with a person
   waiting at the other end of it, so each one carries its own button. */
.work { display: grid; }
.work-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
}
.work-row:last-child { border-bottom: none; }
.work-row:first-child { padding-top: 2px; }
.work-main { min-width: 0; flex: 1; }
.work-title { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.work-title a { color: inherit; text-decoration: none; }
.work-title a:hover { color: var(--accent-text); }
/* One line, clipped, rather than four lines of wrapped detail. The detail is
   supporting text; the name and the action are what the row is for. */
.work-sub {
  font-size: 12.5px; color: var(--ink-3); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.work-when { flex: none; font-size: 12.5px; color: var(--ink-3); }
.work-row .chip, .work-row .btn { flex: none; }

/* =================================================================== bars */

/* A count per state with its share, as rows. Eight numbers do not need a
   drawing, but the eye cannot compare eight numbers without one, so the bar
   carries the comparison and the chip carries the meaning. Each row links into
   the register: "four expiring" is a question, not an answer. */
.bars { display: grid; }
.bar-row {
  display: grid; grid-template-columns: 152px minmax(0, 1fr) 34px 40px;
  align-items: center; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
  text-decoration: none; color: inherit;
}
.bar-row:last-child { border-bottom: none; }
.bar-row:hover .bar-fill { opacity: 0.75; }
.bar-label { min-width: 0; }
.bar-track { height: 6px; border-radius: 99px; background: var(--surface-3); overflow: hidden; }
.bar-fill { display: block; height: 100%; border-radius: 99px; background: var(--ink-3); transition: opacity 0.14s ease; }
.bar-fill.ok { background: var(--ok); }
.bar-fill.warn { background: var(--warn); }
.bar-fill.bad { background: var(--bad); }
.bar-fill.mute { background: var(--line-2); }
.bar-n { text-align: right; font-size: 14px; font-weight: 500; }
.bar-pct { text-align: right; font-size: 12.5px; color: var(--ink-3); }
@media (max-width: 560px) { .bar-row { grid-template-columns: 1fr auto auto; } .bar-track { display: none; } }

/* =================================================================== feed */

.feed { display: grid; font-size: 13px; }
.feed-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 12px; align-items: baseline;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.feed-row:last-child { border-bottom: none; }
.feed-act { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; letter-spacing: 0.01em; }
.feed-txt { color: var(--ink-2); min-width: 0; overflow-wrap: anywhere; }
.feed-txt b { font-weight: 500; color: var(--ink); }
.feed-when { color: var(--ink-3); font-size: 12px; white-space: nowrap; }

/* ================================================================== misc */

.mono { font-family: var(--font-mono); }
.dim { color: var(--ink-3); }
.right { margin-left: auto; }
.stack-sub { font-size: 12.5px; color: var(--ink-3); line-height: 1.55; }
.err { color: var(--bad); font-size: 13px; margin: 12px 0 0; line-height: 1.6; }
.loading { color: var(--ink-3); padding: 22px 2px; }

.note {
  font-size: 12.5px; color: var(--ink-3); line-height: 1.6;
  margin: 0 0 var(--gap); max-width: 88ch;
}
.note b { color: var(--ink-2); font-weight: 600; }

/* A caution block, for the two places where what the software does is narrower
   than what the word on the button suggests. */
.caution {
  border-left: 2px solid var(--warn);
  background: var(--warn-soft);
  padding: 12px 15px; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 13px; color: var(--ink-2); line-height: 1.6;
  margin: 0 0 16px;
}
.caution b { color: var(--ink); font-weight: 600; }

.empty { padding: 46px 24px; text-align: center; }
.empty p { margin: 0 0 6px; font-size: 15px; font-weight: 500; }
.empty span { display: block; font-size: 13px; color: var(--ink-3); max-width: 52ch; margin: 0 auto 18px; line-height: 1.65; }

/* ================================================================= sheets */

.sheet {
  position: fixed; inset: 0; z-index: 75;
  background: rgba(14, 15, 17, 0.48);
  display: flex; justify-content: center; align-items: flex-start;
  padding: 7vh 20px 20px;
  overflow-y: auto;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sheet { background: rgba(0, 0, 0, 0.62); }
}
  :root[data-theme="dark"] .sheet { background: rgba(0, 0, 0, 0.62); }

.form-card {
  width: 100%; max-width: 480px;
  background: var(--surface-solid);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-veil);
  padding: 26px 28px 24px;
}
.form-card h2 { margin: 0 0 5px; font-size: 19px; font-weight: 500; letter-spacing: -0.015em; }
.form-card .sub { font-size: 13px; color: var(--ink-3); margin: 0 0 20px; line-height: 1.6; }
.form-actions { display: flex; gap: 9px; margin-top: 20px; }

.key-card { max-width: 560px; }

/* THE PASSPHRASE, WHICH IS THE POINT OF THE SCREEN IT IS ON.
 *
 * Eight words that somebody has to read down a telephone, and the only thing
 * that opens the file just emailed. Set large, spaced, and in the reading face
 * rather than mono — this is spoken, not typed, and mono makes people spell it
 * out letter by letter. The accent border is there so the eye lands here first
 * and not on the raw key folded away below. */
.pass-block {
  margin-top: 20px; padding: 18px 20px;
  border: 1px solid var(--accent); border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
}
.pass-label {
  font-family: var(--font-ui);
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--accent-text); margin-bottom: 10px;
}
.pass {
  font-size: 17px; line-height: 1.7; font-weight: 500;
  letter-spacing: 0.005em; color: var(--ink);
  margin-bottom: 14px; user-select: all;
}

/* The raw key, folded away. The customer should never need it; the person on
   the telephone might, if the file will not reach them at all. */
.key-fold { margin-top: 20px; border-top: 1px solid var(--line); padding-top: 14px; }
.key-fold summary {
  cursor: pointer; font-size: 13px; color: var(--ink-3);
  list-style: none;
}
.key-fold summary::-webkit-details-marker { display: none; }
.key-fold summary::before { content: '▸ '; }
.key-fold[open] summary::before { content: '▾ '; }
.key-fold summary:hover { color: var(--ink); }
.key-fold .stack-sub { margin: 10px 0 10px; }
.key {
  font-family: var(--font-mono); font-size: 13px; line-height: 1.7;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 14px 16px; margin: 0 0 12px;
  word-break: break-all; white-space: pre-wrap;
  user-select: all;
}

/* ================================================================= toasts */

/* alert() blocks the whole browser, cannot be styled, cannot be dismissed by
   anything but a click, and says "localhost:4300 says" above the message. It
   was how this console reported that a licence had been blocked. */
.toasts {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 10px;
  /* width:max-content so the box grows to its message before the message is
     allowed to wrap. Without it the text was a shrinkable flex item next to a
     button that is not, so on a phone "Use Face ID next time?" folded onto
     three lines inside a box less than half the width it was permitted —
     195px used of an available 358. */
  width: max-content;
  max-width: min(560px, calc(100vw - 28px));
  background: var(--ink); color: var(--ground);
  border-radius: var(--radius-sm); padding: 11px 16px;
  font-size: 13px; line-height: 1.5;
  box-shadow: var(--shadow-veil);
  pointer-events: auto;
  animation: toast-in 0.2s cubic-bezier(0.3, 0.9, 0.3, 1);
}
.toast .tick { color: var(--accent); font-weight: 700; flex: none; }
.toast .btn { flex: none; }
.toast.bad .tick { color: #ff8b82; }
.toast-btn {
  flex: none; margin-left: 4px; padding: 3px 10px;
  border: 1px solid currentColor; border-radius: 6px;
  color: inherit; font-size: 12.5px; font-weight: 600; opacity: 0.75;
}
.toast-btn:hover { opacity: 1; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }

/* ======================================================= command palette */

.cmdk-veil {
  position: fixed; inset: 0; z-index: 85;
  background: rgba(14, 15, 17, 0.48);
  display: flex; justify-content: center; align-items: flex-start;
  padding: 12vh 20px 20px;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .cmdk-veil { background: rgba(0, 0, 0, 0.62); }
}
  :root[data-theme="dark"] .cmdk-veil { background: rgba(0, 0, 0, 0.62); }

.cmdk {
  width: 100%; max-width: 560px;
  background: var(--surface-solid);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-veil);
  overflow: hidden;
}
#cmdkInput {
  width: 100%; padding: 16px 18px;
  border: none; border-bottom: 1px solid var(--line);
  background: none; color: var(--ink);
  font: inherit; font-size: 15px;
}
#cmdkInput:focus { outline: none; }
#cmdkInput::placeholder { color: var(--ink-3); }
.cmdk-res { max-height: 48vh; overflow-y: auto; padding: 6px; }
.cmdk-group {
  font-family: var(--font-ui);
  font-size: 10px; font-weight: 600; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-3); padding: 10px 12px 5px;
}
.cmdk-row {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  padding: 8px 12px; border-radius: var(--radius-sm);
  font-size: 14px; color: var(--ink-2);
}
/* Selection is a class, not :hover. The keyboard drives this, and a row the
   mouse happens to be resting over must not look like the row Enter will
   open. */
.cmdk-row.sel { background: var(--ink); color: var(--ground); }
.cmdk-row .r-sub { margin-left: auto; font-size: 12.5px; color: var(--ink-3); }
.cmdk-row.sel .r-sub { color: var(--ground); opacity: 0.65; }
.cmdk-empty { padding: 24px 14px; text-align: center; color: var(--ink-3); font-size: 13.5px; }
.cmdk-foot {
  display: flex; gap: 16px; padding: 9px 14px;
  border-top: 1px solid var(--line); background: var(--surface-2);
  font-size: 12px; color: var(--ink-3);
}
.cmdk-foot span { display: flex; align-items: center; gap: 5px; }

@media (max-width: 700px) {
  main { padding: 20px 14px 44px; }
  .vhead h1 { font-size: 26px; }
  .hero-n { font-size: 42px; }
}

/* ===========================================================================
 * SMALL SCREENS
 *
 * AT THE END OF THE FILE, AND THAT IS LOAD-BEARING.
 *
 * This block lived up in the chrome section, before .mosaic and .k were even
 * defined. A media query carries no extra specificity — it is ordinary source
 * order — so every phone rule that named a selector defined later in the file
 * was silently overridden by the desktop one. Cards measured exactly 200px on a
 * phone because grid-auto-rows: var(--row) won, and the "grid-auto-rows: auto"
 * written for phones did nothing at all.
 *
 * Nothing looked broken. The page simply stayed a third longer than it needed
 * to be, which is the kind of defect that survives every review.
 * ======================================================================== */

/* The top bar and the veil exist only here. */
.topbar, .side-veil { display: none; }

/* A RAIL IS NAVIGATION ON A DESKTOP AND AN OBSTRUCTION ON A PHONE.
 *
 * The first version folded it to a 60px strip of icons: a sixth of a 390px
 * screen spent on seven anonymous glyphs, permanently. A drawer costs nothing
 * until it is opened, and the icons get their words back. */
@media (max-width: 860px) {
  /* ROWS, NAMED. Without these the single-column grid has only implicit rows,
     and #app carries min-height:100vh — so on a SHORT page the leftover height
     was shared out between the rows and the topbar grew to absorb its portion.
     Products has one tile, so its bar stood 239px tall against 59px everywhere
     else, and the whole page appeared to shrink under a header that had slid
     down the screen. The bar takes what it needs; the content takes the rest. */
  #app { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }

  .topbar {
    display: flex; align-items: center; gap: 12px;
    position: sticky; top: 0; z-index: 40;
    padding: 10px 14px;
    background: var(--ground);
    border-bottom: 1px solid var(--line);
  }
  /* ── THE MARK, CENTRED, AND THE SHORT ONE ──────────────────────────────
   *
   * Centred by position rather than by flex order: the menu and the search
   * buttons are not the same width, so a logo sitting between them in the
   * flow lands off-centre by the difference. Taking it out of the flow means
   * it is centred on the SCREEN, which is the thing the eye measures against.
   *
   * pointer-events: none so the centred element cannot swallow a tap meant
   * for either button underneath it. */
  .topbar { position: sticky; }
  .topbar-brand {
    position: absolute; left: 50%; transform: translateX(-50%);
    /* Its own ratio, not the lockup's. .brand-logo is 471/168 because that is
       the shape of "BERCROFT SYSTEMS"; the short mark is 258/96, and left on
       the wide ratio the box is taller than the art and the centring is off
       by the slack. */
    width: 96px; aspect-ratio: 258 / 96; pointer-events: none;
    background-image: url('/brand/bercroft-light.png');
  }
  .topbar-search { margin-left: auto; }
  .topbar-menu, .topbar-search {
    /* 44, not 38. These are the only two controls on a phone's top bar and
       both were six pixels under what a thumb reliably hits. */
    flex: none; display: grid; place-items: center;
    width: 44px; height: 44px; border-radius: 12px;
    color: var(--ink-2);
  }
  .topbar-search { margin-left: auto; }
  .topbar-menu svg, .topbar-search svg { width: 20px; height: 20px; }
  .topbar-menu:active, .topbar-search:active { background: var(--surface-3); }

  .side {
    position: fixed; inset: 0 auto 0 0; z-index: 60;
    width: min(300px, 84vw);
    transform: translateX(-100%);
    /* The one animated thing in the product, because a drawer that appears
       without moving reads as the page having been replaced. 180ms, and it
       respects a reduced-motion preference below. */
    transition: transform 0.18s cubic-bezier(0.3, 0.9, 0.3, 1);
    background: var(--surface-solid);
    border-right: 1px solid var(--line);
    height: 100dvh;
  }
  body.drawer-open .side { transform: translateX(0); }
  .side-veil {
    display: block; position: fixed; inset: 0; z-index: 50;
    background: rgba(14, 15, 17, 0.48);
  }

  main { padding: 18px 14px 56px; }
  .vhead { margin-bottom: 18px; }
  .vhead h1 { font-size: 27px; }
  /* Was width:100% with flex:1 on the button, which made every screen open
     with a full-width white slab louder than its own heading. Sized to its
     text, like the desktop one.
     
     And on its own line. Beside the title it squeezed "Companies" against a
     white slab with the watermark immediately behind both — three things
     competing for one row on a 390px screen, which is what read as messy.
     Stacked, the heading gets the width it deserves and the button sits under
     it where a thumb expects an action anyway. */
  .vhead-row { flex-direction: column; align-items: flex-start; gap: 12px; }
  .vhead-meta { margin-left: 0; width: auto; }
  .vhead-meta .btn { flex: 0 0 auto; }

  /* THUMBS, NOT CURSORS. Anything you press gets 44px, which is the smallest
     target a finger hits reliably. */
  .btn { padding: 11px 16px; }
  .btn.sm { padding: 9px 14px; }
  .nav-item { padding: 11px 12px; }
  .icon-btn { width: 38px; height: 38px; }
  /* Height, not padding. Padding plus a 13px line box came to 30px, and a
     30px target is one a thumb misses. */
  .seg button { padding: 9px 14px; min-height: 38px; }
  .seg { align-items: stretch; }
  .crumb-back { padding: 8px 14px 8px 11px; }

  /* One column, and the hero stops being twice as tall as everything else. */
  /* One column, and every card sizes to what is in it.
   *
   * A fixed row height is what makes four cards in a row line up; in a single
   * column it lines up with nothing and only pads short cards out. "Companies
   * 6" was occupying 150px to say one number, and eight of those is a screen
   * and a half of nothing. Scrolling is the cost on a phone, so the fixed row
   * goes and the page loses about a third of its height. */
  .mosaic { grid-template-columns: minmax(0, 1fr); grid-auto-rows: auto; }
  .k.hero, .k.lead, .mosaic .wide, .mosaic .full { grid-column: auto; grid-row: auto; }
  .k { min-height: 0; padding: 14px 16px; }
  .k-n { font-size: 30px; }
  .k-n.big { font-size: 38px; }
  .k.hero .chart { height: 86px; }
  .k > .chart { padding-top: 12px; }
  .split, .cols2 { grid-template-columns: minmax(0, 1fr); }

  /* Filters stack rather than overflowing, and the state chips scroll sideways
     on purpose — hiding them would hide the counts. */
  .filters { gap: 8px; }
  .search-box { max-width: none; min-width: 0; width: 100%; }
  .seg {
    width: 100%; overflow-x: auto; flex-wrap: nowrap;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .seg::-webkit-scrollbar { display: none; }

  /* ── A ROW BECOMES A CARD ──────────────────────────────────────────────
   *
   * This used to say "a table stays a table, and keeps its first column" —
   * it scrolled sideways with the name pinned. That was the wrong call, and
   * measuring it says why: the companies table is 933px wide in a 360px
   * window, licences 989px, activity 969px. Two thirds of every register was
   * off-screen. You could read a company's name and had to drag to learn one
   * single fact about it, then drag back.
   *
   * Nothing is hidden — that would lose data, which was the right instinct in
   * the old note. Instead each row stands up into a card and each cell brings
   * its own column name with it, stamped onto the cell by labelTableCells().
   * Everything that was in the table is still on the screen; it reads down
   * instead of across, which is the direction a phone actually has.
   *
   * A register of fourteen companies becomes fourteen cards you can thumb
   * through, which is the same gesture as every other list on a phone. */
  .tbl-wrap { overflow: visible; }
  .tbl, .tbl tbody, .tbl tr, .tbl td { display: block; width: 100%; }
  .tbl thead { display: none; }
  .tbl { border-collapse: separate; }

  .tbl tbody tr {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    padding: 12px 14px;
    margin-bottom: 10px;
  }
  .tbl tbody tr:last-child { margin-bottom: 0; }

  .tbl td {
    border: 0;
    padding: 4px 0;
    display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
    font-size: 13.5px;
    text-align: left;
    /* The desktop table sets nowrap so columns keep their shape. In a card
       there are no columns to keep, and nowrap is what was still pushing the
       activity log 124px off the side — its Detail cells run to 380px. They
       wrap now, dropping under their own label when they are long. */
    white-space: normal;
    flex-wrap: wrap;
    overflow-wrap: anywhere;
  }
  /* A value long enough to wrap takes the full width beneath its label rather
     than a ragged 40px column beside it. */
  .tbl td > * { min-width: 0; }
  /* The column name, on the cell that carries it. Cells with nothing worth
     showing are dropped entirely rather than printing "GSTIN —" fourteen
     times down a page. */
  .tbl td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-family: var(--font-ui);
    font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3);
  }
  .tbl td[data-empty] { display: none; }

  /* The first cell is the card's title, so it gets the whole width, a size
     that reads as a heading, and no label — you do not need to be told that
     the name of a company is its name. */
  .tbl td.main {
    display: block;
    font-size: 16px; font-weight: 500;
    color: var(--ink);
    padding-bottom: 8px; margin-bottom: 6px;
    border-bottom: 1px solid var(--line);
  }
  .tbl td.main::before { content: none; }
  .tbl td.num { justify-content: space-between; }
  .tbl tbody tr:hover td.main { background: transparent; }

  .prod { flex-wrap: wrap; }
  .prod-n { min-width: 62px; }
  .facts.row { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }
  .form-card, .cmdk { max-width: none; }
  .sheet { padding: 4vh 12px 12px; }
  .cmdk-veil { padding: 6vh 12px 12px; }
  /* The track goes and the chip takes the room. A state like "awaiting
     activation" needs 151px to say its own name, and squeezing it into a
     118px column to keep a decorative bar is the wrong trade — the count and
     the share are the information, and they stay. */
  .bar-row { grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; }
  .bar-track { display: none; }
  .bar-n { min-width: 26px; }
  .bar-pct { min-width: 40px; }

  /* ── NOTHING MAY ZOOM THE PAGE WHEN YOU TAP IT ─────────────────────────
   *
   * Safari on iOS zooms the whole page whenever a focused control's text is
   * under 16px, and it does not zoom back out afterwards. The search box was
   * 13.5px, the sidebar search 13px, the command palette 15px — so tapping
   * search threw the layout sideways and left it there.
   *
   * The fix is the font size, not `maximum-scale=1` on the viewport: that
   * stops the zoom by forbidding ALL zooming, including the pinch somebody
   * needs to read a figure. Never take that away to fix a layout bug.
   *
   * 16px only while the control can be tapped — the desktop sizes are
   * deliberate and stay. */
  input, select, textarea,
  .search-box input, .side-search, #cmdkInput,
  .field input[type=number], .field input[type=text], .field input[type=email],
  .field input[type=password], .field input[type=date] {
    font-size: 16px;
  }

  /* ── TWO ACROSS, SO A SCREEN HOLDS A SCREENFUL ─────────────────────────
   *
   * One column meant a phone showed three tiles and the rest was scrolling.
   * A tile is a label and a number; at 390px two of them still leave 168px
   * each, which fits "12 running" and its caption without wrapping.
   *
   * The hero keeps the full width, because it carries a chart and a chart
   * squeezed to 168px is a smudge. Anything explicitly wide stays wide. */
  /* .tiles gets the same treatment as .mosaic, which it never had. It is
     minmax(210px, 1fr), and 362px of usable width fits exactly one of those —
     so every detail page stacked its figures one per screen-width, each in a
     fixed-height row with a number floating at the top of it. That is what
     made a customer page look shabby on a phone while the list pages looked
     fine. */
  .mosaic, .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .tiles { grid-auto-rows: minmax(120px, auto); }
  .k.hero, .mosaic .full, .k.lead { grid-column: 1 / -1; }
  .mosaic .wide { grid-column: auto; }
  .k { padding: 13px 14px; }
  .k-n { font-size: 25px; }
  .k-n.big { font-size: 30px; }
  .k-label { font-size: 10.5px; }
  .k-sub { font-size: 11.5px; }
  /* The hero is the exception that keeps its old size, since it has the room. */
  .k.hero .k-n, .k.hero .k-n.big { font-size: 34px; }

  /* ── THE DRAWER TAKES THE SCREEN ───────────────────────────────────────
   *
   * It was min(300px, 84vw), which on a 390px phone left a 62px strip of
   * dimmed page down the side doing nothing but making the drawer feel like
   * a panel that had not finished opening. Navigation is the whole task at
   * that moment; it gets the whole screen. */
  /* The sign-in card is nearly the whole screen on a phone, so the mark can
     take its share of it. 200px of a ~342px card is a little over half. */
  .auth-brand .brand-logo { width: 200px; }
  .auth-brand { margin-bottom: 26px; }

  .side { width: 100vw; max-width: 100vw; border-right: 0; }
  .side-close { display: flex; }
  /* Tighter than it was: at 14px of padding and 18px between groups the nav
     ran past the fold on a 390px screen, so the footer needed a scroll to
     reach even when there was room for it. Still above the 44px Apple asks
     for a touch target. */
  /* Measured, not guessed: at 11px of padding the nav overflowed a 390x844
     screen by 46px, which put half of "Activity" behind the footer. At 9px
     the whole thing fits with room to spare. Still a 42px row. */
  /* 10px of padding puts the row at 44 rather than 42. Measured after the
     change: the whole nav still fits a 390x844 screen without scrolling. */
  .side .nav-item { padding: 10px 14px; font-size: 15.5px; }
  .side .nav-group + .nav-group { margin-top: 12px; }
  /* Aligned with the icon column. The phone override tightened items to 14px
     of side padding but left the labels at 10px, so REGISTER / SALES / ADMIN
     hung four pixels left of everything under them — small, and exactly the
     kind of thing that reads as sloppy without being nameable. */
  .side .nav-label { padding: 3px 14px; }
  /* 44px is a TOUCH requirement, so it is enforced here rather than globally —
     a 44px icon button under a mouse is clumsy, and the desktop sizes are
     deliberate. Sign-out and the segmented filters were both 38. */
  .icon-btn { width: 44px; height: 44px; border-radius: 12px; }
  .seg button { padding: 12px 14px; }
  /* THE BIRD DECORATES A CORNER, IT DOES NOT SIT UNDER A PARAGRAPH.
     Measured on a customer page: the mark is 104px wide starting at x=272,
     and both the eyebrow and the contact line reach 376 — so a hundred pixels
     of text lay across it. Anchored to the top and smaller, it keeps the
     corner it was meant to occupy and the lines beneath it run clear. */
  .vhead::after {
    width: 72px; height: 104px;
    top: 0; transform: none;
  }
  /* No Command key on a phone. The badge is a desktop shortcut hint and on a
     handset it is a small grey box promising something that cannot be done. */
  .side-search kbd { display: none; }
  /* A smaller phone still cannot fit it — an SE is 223px short, and no
     amount of tightening buys that back. So the bottom edge fades, and a
     clipped row reads as "more below" rather than as a rendering fault.
     The fade is the depth of the padding, so nothing is dimmed once you
     have scrolled to the end. */
  .side .nav {
    /* Room above the first item for its focus ring. .nav scrolls, which makes
       it a clipping box, and the first item sat flush against the top edge —
       so the ring drawn around "Overview" had its top half sliced off. */
    padding-top: 4px;
    padding-bottom: 22px;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
  }
  .brand { padding: 14px 14px 12px; }
  .side-search { font-size: 16px; padding: 12px 14px; }
  /* With no page showing behind it, the veil has nothing to dim — but it is
     still what catches a tap outside the nav, so it stays and goes clear. */
  body.drawer-open .side-veil { background: transparent; }
}

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

/* Nothing may push the page sideways, whatever the content. Every one of these
   was measured overflowing at 390px: a work row with a long customer name, a
   product row with three figures, the filter bar. */
.work, .bars, .feed, .thread { grid-template-columns: minmax(0, 1fr); }
.work-row, .bar-row, .feed-row, .prod { min-width: 0; }
.prod-name, .prod-tag { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.legend-row, .fact, .k, .panel, .card { min-width: 0; }


