/* Pisces Central — one place the company opens its apps from.
 *
 * THREE RULES DRIVE EVERYTHING HERE. They come from the capture form this hub grew out of,
 * and they still hold: the form charges cards, and the surface it lives on must not make
 * that ambiguous.
 *
 * 1. COLOUR IS RATIONED, AND IT REPORTS OUTCOMES. Red and green are never decoration
 *    and never chrome. Green appears only on a result that came back confirmed; red
 *    only on one that failed — a declined order, an error notice, a sign-in that broke.
 *    If you can see red inside a panel, something did not work.
 *
 *    THE PHOTOGRAPH IS THE ONE EXCEPTION, and it is exempt for a reason that has to keep
 *    being true: it is behind every reporting surface, it never changes with state, and it
 *    carries no green a person could read as a verdict. A Hollywood-style PISCES sign that
 *    lit green letter by letter stood on the mountain for an afternoon; it came out because
 *    the panels cover the mountain, and the only moving green on the page went with it.
 *
 *    Identity and structure are carried by Pisces navy #003680, which the light glass can
 *    finally hold at full strength. Navy never reports an outcome, so it cannot be
 *    confused with one.
 *
 * 2. DATA IS MONOSPACE. Every identifier a person could mistype — order numbers, POs,
 *    card digits, transaction ids — is set in a monospaced face with tabular figures.
 *    A transposed digit charges the wrong customer, and proportional type hides
 *    exactly that. This is a safety property that happens to be the type direction.
 *
 * 3. THE IDENTIFIER IS THE LARGEST THING IN THE PANEL. Rule 2 taken to its conclusion.
 *    The ledger sets each recognised order bigger than the heading above it, because
 *    the number is the one thing a person must actually read before committing.
 *
 * THE MATERIALS. Two floating panels on a photograph: the rail and the screen. Both are
 * the same glass — a translucent fill, a blur behind it, one hairline of light on the edge,
 * a large radius and a long soft shadow. Nothing else in the layout may introduce a third
 * material, because the depth only reads if there are exactly two planes: the photograph,
 * and the glass.
 *
 * LIGHT GLASS, AND WHY. Dark glass over a photograph fights it: the picture goes grey and
 * white type lands around 3.5:1, which is not enough for an 11.5px label on a form that
 * charges cards. Light glass with dark ink goes the other way — the ink is the darkest thing
 * in the composition rather than the ground, so contrast stays above 10:1 whatever is
 * behind it, and the panels can still be see-through. Measured, not assumed.
 *
 * THE PALETTE MATCHES THE PICTURE AND THE BRAND, which here are the same two colours. The
 * backdrop is a deep blue polar night with a green aurora; Pisces is navy #003680 and green
 * #00DE73. So the glass is tinted a cool ice-white rather than neutral, the ink is a very
 * dark navy rather than black, and navy carries every structural mark — the selected app,
 * the ledger spine, focus, the commit button. Green stays where rule 1 put it: on a result
 * that came back confirmed, and nowhere else.
 *
 * Both palettes are here. Light is the default; add data-theme="dark" to <html> to get the
 * night version back, and every component follows — they only ever read tokens.
 *
 * Radii are generous and consistent (panels 30px, cards 18px, controls 12px, pills full).
 *
 * A REQUIREMENT KEPT FROM THE FORM: the commit button is always in view. It used to be
 * "the whole form fits on one screen"; inside a panel that stopped holding, so the action
 * bar is pinned to the bottom of the screen panel instead. See .commit.
 *
 * DELIBERATELY GONE: the animated green-to-navy sweep along the top edge of the document.
 * It was safe on white paper, but a moving green bar in front of a photograph reads as a
 * progress bar, which is the one thing rule 1 forbids green from doing. The mark itself sits
 * at the top of the rail, permanently, which is the job that edge was doing.
 *
 * No webfonts, by constraint and by preference: the CSP is font-src 'self', so the
 * personality here comes from treatment — tracking, scale, tabular figures, glass —
 * rather than from a downloaded face.
 */

/* ══════════════════════════════════════════════════════════════════════════
   LIGHT — the default, drawn for the Mount Hood backdrop
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  /* ── the glass ──────────────────────────────────────────────────────────
     .52 is a deliberate number: the mountain is plainly visible through the panels, and
     dark ink on it still measures better than 10:1 over both the bright sky and the dark
     forest. The blur is what makes those two facts compatible. */
  /* Ice-white, not neutral white: a few points of blue is what makes the panels belong to
     the polar night behind them instead of sitting on top of it.

     .62 IS A TRADE, NOT A SETTING. Thin glass only works if the things on it carry their own
     contrast, so this number moved down and everything below moved up: ink is nearly black,
     rules are firmer, tints are twice as strong, and every button is a filled chip rather
     than an outline. Undo one half of that and the other half stops making sense.

     What keeps it neutral at this thinness is the desaturating blur, not the fill — see
     --blur. Without it the aurora comes straight through and body text sits on a field that
     changes hue across its own line.

     The tint follows the picture: ice-blue under a blue aurora, ice-violet under a violet
     one, and a warm neutral under this sunrise. Two or three points of hue, no more — enough
     to belong, not enough to read as a colour. Cool glass on a warm photograph looks like a
     window someone left open from another season. */
  --glass:      rgba(250, 252, 255, .28);
  /* THE NAVIGATION SURFACES stay a step more solid than the working surface: the rail and
     the screen's top bar. They hold small type — 13px app names, a breadcrumb, a greeting —
     so they remain the steadiest of the three surfaces even as all of them thin. The GAP
     between these numbers is what matters, not any one alone: chrome above panel, panel
     above nothing, and the boxes on top of everything with a near-solid ground of their
     own. */
  --glass-chrome: rgba(251, 253, 255, .44);
  --glass-soft: rgba(255, 255, 255, .66);   /* a raised box INSIDE a panel */
  /* THE CARDS: the orders box, the locked ledger, the steps box, every input. They began as
     a 9% dark wash, which meant their lightness was really the panel's lightness. They are
     now nearly opaque, and that is a legibility decision AND a styling one: at 72% a bright
     cloud behind the panel showed through the steps box as a diagonal streak, which reads as
     dirt rather than as glass. These hold the order numbers and card digits somebody has to
     read character by character, so they get the one surface on the page with no weather in
     it. Everything behind them stays as thin as it likes. */
  --glass-sunk: rgba(255, 255, 255, .94);   /* a card inside a panel */
  /* Cards get an edge and a lift of their own, which is what makes them read as objects on
     the glass rather than as pale patches of it. */
  --card-edge:  rgba(14, 22, 40, .10);
  --card-lift:  0 1px 2px rgba(14, 22, 40, .05), 0 10px 24px -14px rgba(14, 22, 40, .22);
  --edge:       rgba(255, 255, 255, .95);   /* the hairline of light on a panel edge */
  --spec:       rgba(255, 255, 255, .98);   /* the specular sheen on the top edge */
  /* SATURATE BELOW 100% ON PURPOSE. This filter applies to what shows THROUGH the panel, so
     dropping the saturation there keeps the glass neutral while the picture outside it stays
     fully saturated — the panel reads as frosted, not tinted. */
  --blur:       saturate(55%) brightness(106%) blur(30px);
  /* Barely there. The picture is the reason the hub does not feel like a form, and the
     panels carry their own contrast now, so the veil only takes the edge off the aurora. */
  /* Warm and faint. On a bright photograph the veil is not helping contrast — the panels
     do that themselves — so it only takes the hardest edge off the clouds. */
  --veil:       rgba(255, 250, 242, .04);
  --void:       #6E7C93;                    /* ground with no photograph at all */
  --hover:      rgba(32, 26, 22, .06);
  --field:      rgba(255, 254, 252, .70);   /* the search pill */
  --field-lift: rgba(255, 255, 254, .88);
  --disabled:   rgba(32, 26, 22, .05);
  --menu-bg:    #FFFFFF;                    /* a native <select> popup */
  --scrim:      rgba(28, 22, 18, .45);      /* behind the rail when it is a layer */

  /* Aliases the components inherited from the paper version. Keeping the names means the
     component rules did not have to be rewritten to be re-skinned. */
  --paper:      var(--glass-sunk);
  --surface:    var(--glass-soft);

  /* identity — structure only, never a state */
  --navy:       #003680;
  --navy-deep:  #002356;
  /* The card panel's ground, and the same story as --glass-sunk: a wash this faint reads as
     nothing on a 40% panel, so it carries more of its own weight now. */
  --navy-tint:  rgba(0, 54, 128, .20);
  --navy-ink:   #FFFFFF;                    /* type ON a navy fill */
  --state-ink:  #FFFFFF;                    /* type ON an outcome fill */
  --brand-green:#00DE73;

  /* Cool near-blacks, all of them a step towards navy. That is what puts the type in the
     same family as the brand rather than leaving it a neutral grey that belongs to nothing —
     and it is what stopped the whole panel reading as one band of beige. */
  --ink:        #0A1120;
  --ink-soft:   #2B3A52;
  --ink-faint:  #58657A;
  --rule:       rgba(14, 22, 40, .16);
  --rule-firm:  rgba(14, 22, 40, .30);
  /* Field labels and section headings: navy, not grey. The small caps above every box are
     the page's structure, and structure is navy's job here. */
  --label:      #2C4C7E;

  /* state — outcomes only */
  --charge:     #9B1410;   /* a result that failed */
  --charge-dim: rgba(155, 20, 16, .16);
  /* A deep version of the brand green, which is also the aurora's green — dark enough to
     read as type on the glass, and unmistakably the same colour as the mark. */
  --ok:         #04593F;   /* confirmed, past tense */
  --ok-dim:     rgba(4, 89, 63, .17);
  /* IT → System Status: the five top-row headings */
  --st-va: #6F5BD9; --st-innovations: #C0578F; --st-central: #2F6FD6; --st-shiphawk: #129C8C; --st-netsuite: #B7791F;

  --mono: ui-monospace, "Cascadia Mono", "Cascadia Code", Consolas,
          "SF Mono", "Roboto Mono", monospace;
  --sans: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;

  --step: 4px;
  /* The screen panel's inner padding, as a token because the pinned action bar has to
     cancel it exactly — see .commit. Two literals drifted apart the moment the short-window
     rules changed one of them, and the bar hung 4px past the panel's edge. */
  --pad-y: 20px;
  --pad-x: 24px;
  --r-panel: 30px;
  --r-card: 18px;
  --r-ctl: 12px;
  /* A hairline ring, a close shadow, and a long soft one. The ring is what gives the panel
     an edge against a bright aurora; without it a pale panel on a pale sky has no boundary. */
  --lift: 0 0 0 1px rgba(38, 26, 16, .08),
          0 1px 3px rgba(38, 26, 16, .13),
          0 34px 80px -30px rgba(30, 20, 12, .48);

  /* The photograph, untouched apart from the veil: it is the reason the hub feels light. */
  /* A touch DOWN, not up: this one is a sunrise, and at full brightness the sky beside the
     panels glared next to type set on 62% glass. */
  --photo-filter: saturate(103%) brightness(95%) blur(0px);
  /* The sky over it — see .sky. How strong the drifting cloud is, and the photograph's own
     tone, so the cloud is lit like the scene it is drifting across. */
  --sky-filter: saturate(103%) brightness(95%);
  --sky-wisps: .5;
  --sky-mist: .34;
}

/* ══════════════════════════════════════════════════════════════════════════
   DARK — kept whole. `<html data-theme="dark">` and every component follows.
   ══════════════════════════════════════════════════════════════════════════ */
:root[data-theme="dark"] {
  --glass:      rgba(9, 19, 22, .22);
  --glass-chrome: rgba(9, 19, 22, .38);
  --glass-soft: rgba(255, 255, 255, .055);
  /* Darker than the panel here, for the same reason it is lighter in the light theme: a box
     should read as a different surface, and on a dark panel that means deeper. Deep enough now
     to be a ground in its own right rather than a wash over the panel. */
  --glass-sunk: rgba(0, 0, 0, .55);
  --card-edge:  rgba(255, 255, 255, .10);
  --card-lift:  0 1px 2px rgba(0, 0, 0, .30);
  --label:      #A6C6FF;
  --edge:       rgba(255, 255, 255, .14);
  --spec:       rgba(255, 255, 255, .30);
  --blur:       saturate(150%) blur(26px);
  --veil:       rgba(3, 9, 11, .38);
  --void:       #060C0F;
  --hover:      rgba(255, 255, 255, .06);
  --field:      rgba(255, 255, 255, .07);
  --field-lift: rgba(255, 255, 255, .10);
  --disabled:   rgba(255, 255, 255, .03);
  --menu-bg:    #0C1519;
  --scrim:      rgba(3, 8, 10, .55);

  --navy:       #A6C6FF;
  --navy-deep:  #CBDDFF;
  --navy-tint:  rgba(120, 162, 255, .16);
  --navy-ink:   #061428;
  --state-ink:  #04231A;

  --ink:        #EEF2F6;
  --ink-soft:   #B4BFCB;
  --ink-faint:  #8593A2;
  --rule:       rgba(255, 255, 255, .10);
  --rule-firm:  rgba(255, 255, 255, .22);

  --charge:     #FF7A70;
  --charge-dim: rgba(255, 122, 112, .14);
  --ok:         #3ADE93;
  --ok-dim:     rgba(58, 222, 147, .14);
  /* IT → System Status: the five top-row headings */
  --st-va: #9B8CF0; --st-innovations: #E08CC0; --st-central: #6FA2F2; --st-shiphawk: #2BC4B0; --st-netsuite: #E6B45A;

  --lift: 0 2px 6px rgba(0, 0, 0, .28), 0 36px 90px -34px rgba(0, 0, 0, .85);
  --photo-filter: saturate(88%) brightness(62%) blur(1px);
  --sky-filter: saturate(88%) brightness(62%);
  --sky-wisps: .36;
  --sky-mist: .24;
}

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

/* The `hidden` attribute must win over every layout rule below.
   The UA stylesheet's [hidden] { display: none } has the lowest possible priority, so
   ANY author `display` rule silently beats it — `.gate { display: grid }` kept the
   sign-in panel painted over the whole app after a successful sign-in, which read as
   "sign-in did nothing". Anything toggled with .hidden in JS depends on this line. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; height: 100%; }

body {
  margin: 0;
  min-height: 100%;
  /* What shows if backdrop.jpg is not there: a plain wash in the photograph's own key, so
     a missing file looks deliberate rather than broken. */
  background: var(--void);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
}

.mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "zero" 1;
  letter-spacing: -.01em;
}

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

:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

/* ── the backdrop ─────────────────────────────────────────────────────────
   Fixed behind everything, and never scrolls: the panels move over the photograph, the
   photograph does not move with them.

   Plain `object-fit: cover`. It briefly used a hand-built plate that reproduced cover with
   a real box, so a sign could be pinned to a fixed point ON the photograph; the sign is gone
   — the panels cover the mountain, so it was only ever visible on the sign-in screen — and
   the plate went with it. */
.backdrop { position: fixed; inset: 0; z-index: -1; overflow: hidden; }
.backdrop img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* A little above centre, so the mountain and the sky get the room rather than the
     foreground rocks. */
  object-position: center 48%;
  filter: var(--photo-filter);
}
/* Light: a white veil, which lifts the dark forest enough that ink on 52% glass over it
   still measures over 10:1. Dark: the same element, doing the opposite job. Either way it
   is what makes ANY supplied photograph safe to put a form on. */
.backdrop-scrim {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

/* ── the sky ──────────────────────────────────────────────────────────────
   Cloud drifting over the photograph, and the lane the banner planes fly in — web/sky.js
   paints both and says why it is built this way. Here: WHERE each layer lies over the
   photograph, and how it moves.

   TWO BANDS, masked rather than cut: the high wisps over the sky, fading out before the
   horizon; the mist over the sea of cloud, fading in below it. The photograph's horizon sits
   at a little over half height at every ordinary window shape (object-position 48%), and a
   soft mask forgives the difference between a tall window and a wide one.

   STEPPED, NOT SMOOTH. steps() holds each position for a twelfth of a second. A drift this
   slow looks the same either way — a fraction of a pixel per step — but everything behind
   the glass panels is re-blurred each time it moves, and twelve re-blurs a second is a
   fifth of sixty. The step counts are 12 × the duration in seconds. */
.sky { position: absolute; inset: 0; overflow: hidden; pointer-events: none;
       filter: var(--sky-filter); }
.sky-band { position: absolute; left: 0; right: 0; overflow: hidden; }
.sky-band--high {
  top: 0;
  height: 60%;
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .6), #000 20%, #000 52%,
                                      transparent 96%);
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .6), #000 20%, #000 52%,
                              transparent 96%);
  opacity: var(--sky-wisps);
}
.sky-band--low {
  top: 55%;
  bottom: 0;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 28%, #000 72%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 28%, #000 72%, transparent);
  opacity: var(--sky-mist);
}
/* Two copies of one canvas side by side; sliding the strip by half its width lands the second
   copy exactly where the first began, so the loop has no seam. Invisible until sky.js has
   painted it, then faded in rather than switched on. Left to right: the plane flies the other
   way, and two motions in one direction read as one. */
.sky-drift {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  width: 200%;
  height: 100%;
  opacity: 0;
  transition: opacity 6s ease;
  will-change: transform;
  animation: sky-drift 420s steps(5040) infinite;
}
.sky-drift.is-ready { opacity: 1; }
.sky-drift canvas { display: block; flex: 0 0 50%; width: 50%; height: 100%; }
.sky-drift--b { top: 9%; height: 82%; animation-duration: 660s;
                animation-timing-function: steps(7920); }
.sky-drift--b.is-ready { opacity: .75; }
.sky-drift--mist { animation-duration: 560s; animation-timing-function: steps(6720); }
@keyframes sky-drift {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* THE PLANE, far off. Everything is sized in em off one font size that follows the window,
   so a wider screen gets a proportionally bigger — never a closer — plane. Its haze is the
   filter: a little less saturated and a little less contrasty than something near. */
.sky-lane { position: absolute; inset: 0; overflow: hidden; }
.sky-flight {
  position: absolute;
  top: var(--sky-top, 5%);
  left: 0;
  transform: translate3d(110vw, 0, 0);
  font-size: clamp(10px, calc(7px + .34vw), 15px);
  opacity: .93;
  filter: saturate(.8) contrast(.88);
  will-change: transform;
}
.sky-bob { display: flex; align-items: center; animation: sky-bob 6.5s ease-in-out infinite alternate; }
.sky-plane { flex: none; width: 3.2em; height: auto; overflow: visible; }
.sky-body, .sky-tailplane { fill: #2d2f33; }
.sky-window { fill: #a9b4bd; }
.sky-wing { fill: #efe3be; }
.sky-strut, .sky-gear { fill: none; stroke: #36383c; stroke-width: .9; stroke-linecap: round; }
.sky-wheel { fill: #1c1d20; }
.sky-prop { fill: rgba(70, 70, 70, .32); transform-box: fill-box; transform-origin: center;
            animation: sky-prop .14s linear infinite; }
.sky-tow { flex: none; width: 4.4em; height: 1.6em; margin-left: -.45em; overflow: visible; }
.sky-tow path { fill: none; stroke: rgba(38, 38, 38, .5); stroke-width: .8;
                vector-effect: non-scaling-stroke; }
/* White cloth, red capitals, a dark lead pole — the look of the real thing. The faint
   vertical seams are the panels a towed banner is sewn from. */
.sky-banner {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  min-height: 1.62em;
  padding: .06em .8em;
  border-left: 2px solid #3a3a3a;
  background:
    repeating-linear-gradient(90deg, transparent 0 1.1em, rgba(0, 0, 0, .05) 1.1em 1.16em),
    var(--cloth, #f4f1eb);
  color: var(--cloth-ink, #a3232b);
  font-family: "Bahnschrift SemiCondensed", "Bahnschrift", "Arial Narrow",
               "Roboto Condensed", var(--sans);
  font-stretch: semi-condensed;
  font-size: 1em;
  font-weight: 700;
  letter-spacing: .07em;
  line-height: 1.2;
  white-space: pre;
  transform-origin: left center;
  animation: sky-flutter 1.9s ease-in-out infinite alternate;
}
.sky-banner.is-caps { text-transform: uppercase; }

/* THE CLOTH — the whole banner's colour, for colour-coding one kind of notice from another —
   and the ink that reads best on it when a run names no colour of its own. The ids are the
   closed list in server/announcement-routes.js and web/sky.js. */
.sky-cloth-white  { --cloth: #f4f1eb; --cloth-ink: #a3232b; }
.sky-cloth-yellow { --cloth: #f6dc5f; --cloth-ink: #1b1c1f; }
.sky-cloth-sky    { --cloth: #cfe4f5; --cloth-ink: #0b3a82; }
.sky-cloth-pink   { --cloth: #f6d0da; --cloth-ink: #8a1538; }
.sky-cloth-mint   { --cloth: #cdeed8; --cloth-ink: #15592f; }
.sky-cloth-navy   { --cloth: #14305e; --cloth-ink: #ffffff; }

/* THE FORMATS a run can carry. Classes, never inline styles: the CSP forbids those, and a
   closed list is what the server stores anyway. Underline and strike share one property, so
   the pair has a rule of its own. */
.fx-b { font-weight: 800; }
.sky-banner .fx-b { font-weight: 900; }
.fx-i { font-style: italic; }
.fx-u { text-decoration-line: underline; }
.fx-x { text-decoration-line: line-through; }
.fx-u.fx-x { text-decoration-line: underline line-through; }
.fx-c-red    { color: #b0232c; }
.fx-c-navy   { color: #0b3a82; }
.fx-c-black  { color: #1b1c1f; }
.fx-c-green  { color: #16753a; }
.fx-c-orange { color: #cc5a07; }
.fx-c-purple { color: #6a2c9e; }
.fx-c-blue   { color: #1766c0; }
.fx-c-white  { color: #ffffff; }
.fx-z-sm { font-size: .78em; }
.fx-z-lg { font-size: 1.3em; }
.fx-z-xl { font-size: 1.65em; }
.fx-f-classic { font-family: Georgia, "Times New Roman", serif; font-stretch: normal;
                letter-spacing: .03em; }
.fx-f-script { font-family: "Segoe Script", "Brush Script MT", "Lucida Handwriting", cursive;
               font-stretch: normal; letter-spacing: 0; }
@keyframes sky-bob { from { transform: translateY(-1.5px); } to { transform: translateY(1.5px); } }
@keyframes sky-prop { 50% { transform: scaleY(.5); } }
@keyframes sky-flutter {
  0%   { transform: skewY(-.8deg) scaleY(.97); }
  50%  { transform: skewY(.3deg) scaleY(1.01); }
  100% { transform: skewY(.9deg) scaleY(.98); }
}
@media (prefers-reduced-motion: reduce) {
  .sky-drift, .sky-bob, .sky-prop, .sky-banner { animation: none; }
}

/* ── the glass primitive ──────────────────────────────────────────────────
   Both panels, one rule. `backdrop-filter` is what makes them read as glass rather than
   as flat translucent boxes; where it is unsupported the fill alone still carries
   contrast, which is why the fill is 60% and not 25%. */
.glass {
  position: relative;
  background: var(--glass);
  border: 1px solid var(--edge);
  border-radius: var(--r-panel);
  box-shadow: var(--lift);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
}
/* The specular top edge — one inner hairline, brighter than the border, fading out by the
   sides. It is what stops a large radius looking like a flat rounded rectangle. */
.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(160deg, var(--spec), rgba(255, 255, 255, .04) 34%,
                              transparent 60%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* The mark as a control. No chrome of its own — it should look like the logo, and behave
   like a link — but it does take a focus ring, and it dims when home is not reachable. */
.home-btn {
  appearance: none;
  display: block;
  padding: 0;
  border: 0;
  background: transparent;
  border-radius: 8px;
  cursor: pointer;
}
.home-btn:disabled { cursor: not-allowed; opacity: .55; }
.home-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 3px; }

/* ── the mark ─────────────────────────────────────────────────────────────
   The navy original on light glass; the white knockout on dark. Swapped with `content`
   rather than by two <img> elements or a JS src change, so the markup carries one mark and
   the theme decides which plate to draw. */
.logo {
  display: block;
  width: 148px;
  height: auto;
}
:root[data-theme="dark"] .logo { content: url("pisces-logo-light.png"); }
/* THE DEVELOPMENT SITE'S MARK: Pisces Central rather than Pisces Healthcare, navy on light and
   a white knockout on dark, exactly as the pair above. `site-labelled` is set by
   showSiteLabel() only when the server names a deployment (PC_SITE_LABEL), so production —
   which names none — never draws it.

   VECTOR, NOT A BITMAP. The first versions were PNGs cut from the supplied image, and at
   136px they read as fuzzy: a 1200px bitmap squeezed 4-9x by the browser, from an image with
   soft, noisy edges. These are the three shapes traced into outlines with flat brand colours
   (cross #01ea7f, PISCES #003d95, CENTRAL white), so they are sharp at every size and density.
   The light file carries the glows as SVG blurs under the crisp letters. */
/* ON EVERY SITE since 2026-09-29 (Brad: "the pisces central logo didnt come over") — it began as
   the development site's mark and is now the hub's own, so these rules no longer wait for
   `site-labelled`. The Healthcare pair above stays as the fallback the <img> itself carries. */
.logo { content: url("pisces-central-logo.svg"); }
:root[data-theme="dark"] .logo { content: url("pisces-central-logo-light.svg"); }
/* WIDER, because this lockup is about 3.6:1 against the Healthcare mark's 2.4:1: at the same
   width it would stand barely two-thirds as tall. 168px is the most the rail takes beside the
   menu button before the logo wraps onto a line of its own. */
.logo { width: 200px; }
/* In the rail: smaller than the room allows and pulled in toward the menu button, and the
   menu button itself moved left by dropping the head's left padding — asked for on
   2026-09-24 ("make the logo smaller and move it to the left", then again, with the three
   lines too). Development only, like the mark: production's rail is untouched. */
/* And room above and below it (2026-09-24, "add more spacing above and below the logo"). */
/* :root for the specificity `site-labelled` used to give: the plain .rail-head rules further
   down would otherwise win. */
:root .rail-head .logo { width: 136px; margin: 8px 0 8px -3px; }
:root .rail-head { padding-left: 0; }

/* NO "Dev" WORD ON THE DEVELOPMENT SITE ANY MORE — asked for on 2026-09-24. The Pisces Central
   mark now says which site this is, on the gate and in the rail, so the label under it had
   become a second way of saying the same thing. Hidden rather than removed: showSiteLabel()
   still reads PC_SITE_LABEL, because that is what sets `site-labelled` and so draws this
   site's mark at all, and the browser tab still names the site. */
:root.site-labelled .site-label,
:root.site-labelled .site-label-inline { display: none !important; }

.eyebrow {
  margin: 0;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--navy);
}

/* ── sign-in gate ───────────────────────────────────────────────────────── */
.gate {
  min-height: 100dvh;
  display: grid;
  /* OFF TO THE LEFT, not centred: this is the one screen where the whole photograph is
     visible, and a card in the middle of it sits squarely on the mountain. Centred again
     when the window is too narrow to give the picture any room anyway. */
  place-items: center start;
  padding: calc(var(--step) * 6) clamp(20px, 7vw, 130px);
}
.gate-inner {
  max-width: 30rem;
  width: 100%;
  padding: calc(var(--step) * 9) calc(var(--step) * 8) calc(var(--step) * 8);
}
.gate-inner .logo { margin-bottom: calc(var(--step) * 7); }
.gate-inner .eyebrow { margin-bottom: calc(var(--step) * 3); }
.gate-inner h1 {
  margin: 0 0 calc(var(--step) * 3);
  font-size: clamp(1.45rem, 4vw, 1.8rem);
  font-weight: 650;
  letter-spacing: -.025em;
  line-height: 1.15;
}
.gate-inner .btn { margin-top: calc(var(--step) * 6); }
.gate-error {
  margin-top: calc(var(--step) * 5);
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  border-left: 3px solid var(--charge);
  border-radius: 0 var(--r-ctl) var(--r-ctl) 0;
  background: var(--charge-dim);
  color: var(--ink);
  font-size: 14px;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE STAGE — two panels, floating
   ══════════════════════════════════════════════════════════════════════════
   Fixed height and no page scroll: the SCREEN scrolls its own body, so the rail and the
   top bar never leave. A hub whose navigation scrolls away is a hub people navigate with
   the back button. */
.stage {
  position: fixed;
  inset: 0;
  display: grid;
  grid-template-columns: 15.75rem minmax(0, 1fr);
  gap: clamp(10px, 1.4vw, 22px);
  /* The frame of photograph around the panels is the whole reason there is a photograph.
     Capped in px as well as vh so a very tall window does not spend half of itself on
     margin, and a very wide one still shows the picture down both sides. */
  padding: clamp(12px, 3vh, 40px) clamp(12px, 2.4vw, 48px);
  /* Centred beyond this, so the hub does not stretch into a letterbox on a wide monitor —
     and the picture gets the room instead. */
  max-width: 132rem;
  margin: 0 auto;
}
/* Both live in the second column, one at a time: the screen panel, or the home screen
   where it would have been. Placed explicitly because auto-placement would put the second
   one on a row of its own. */
.screen, .home { grid-column: 2; grid-row: 1; }
body.app-home .screen { display: none; }

/* Hiding the rail gives its width to the screen — the toggle is a real focus mode, not a
   narrower rail. */
body.rail-off .stage { grid-template-columns: minmax(0, 1fr); }
body.rail-off .screen, body.rail-off .home { grid-column: 1; }
body.rail-off .rail { display: none; }
.icon-btn.rail-show { display: none; }
body.rail-off .icon-btn.rail-show { display: grid; }

/* ── the rail, collapsed to its icons ─────────────────────────────────────
   For an app that brings its own sidebar. The hub's rail stays on screen at the width of its
   icons so Central is still one click away, and the app's own navigation gets the room.

   ONE COLUMN WIDTH DRIVES BOTH the grid and the rail, because they are the same measurement
   said twice — the stage's first column IS the rail. Getting them out of step is how a rail
   ends up overlapping the panel beside it. */
body.rail-mini .stage { grid-template-columns: 3.5rem minmax(0, 1fr); }
body.rail-mini .rail { padding: calc(var(--step) * 5) calc(var(--step) * 1.5) calc(var(--step) * 2.5); }

/* The mark becomes the way home and nothing else: at this width the wordmark cannot be read,
   and a logo squeezed to 3rem reads as a smudge rather than a brand. */
body.rail-mini .rail-head {
  flex-direction: column;
  gap: calc(var(--step) * 2);
  padding: 0 0 calc(var(--step) * 2.5);
  margin-bottom: calc(var(--step) * 2);
}
/* The lockup gives way to the mark. Two images, one shown at a time — see the note in
   index.html on why this is not a crop. */
.logo-mark { display: none; }
body.rail-mini .rail-head .logo { display: none; }
body.rail-mini .rail-head .logo-mark { display: block; width: 1.75rem; height: 1.75rem; }

/* Departments become their icons. The name and the chevron are hidden rather than removed so
   the markup is identical in both states and nothing has to be re-rendered to change width —
   which also means the open/closed state survives collapsing and expanding. */
body.rail-mini .dept-head {
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  padding: calc(var(--step) * 2.5) 0;
}
body.rail-mini .dept-name,
body.rail-mini .dept-chev,
body.rail-mini .dept-empty { display: none; }
/* The app list too: there is no width to read an app name in, and the department icon is the
   way back to a rail that has one. */
body.rail-mini .dept-apps { display: none; }
body.rail-mini .rail-foot #whoami { display: none; }
body.rail-mini .rail-foot {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}
body.rail-mini .rail-foot #signout { font-size: 0; }
/* The word is gone but the button is not: a dot the size of the icons above it, labelled for
   anything that reads the page aloud. */
body.rail-mini .rail-foot #signout::before {
  content: "⏻";
  font-size: 15px;
}

/* Only ever visible while the rail is a layer — see the narrow block at the bottom. */
.rail-scrim {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 25;
  border: 0;
  padding: 0;
  background: var(--scrim);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  cursor: pointer;
}

/* ── the rail ─────────────────────────────────────────────────────────────── */
.rail {
  /* Overrides the .glass fill deliberately — see --glass-chrome. Later in the file than
     .glass and at the same specificity, which is what makes it win. */
  background: var(--glass-chrome);
  display: grid;
  /* minmax(0, …) so the column can be NARROWER than its widest child. Without it a grid
     column is sized by content that refuses to shrink: the foot (a long address next to Sign
     out) widened the whole rail, and the department chevrons ended up outside the panel. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  padding: calc(var(--step) * 5) calc(var(--step) * 3.5) calc(var(--step) * 3.5);
  min-height: 0;
}
.rail-head {
  display: flex;
  align-items: center;
  /* WRAPS SO THE DEPLOYMENT LABEL GETS ITS OWN LINE. Without this the label became a third
     column beside the lockup — "PISCES HEALTHCARE  Dev" on one row — which is not what was
     asked for and reads as part of the brand rather than as a warning about the site. */
  flex-wrap: wrap;
  gap: calc(var(--step) * 2);
  padding: 0 calc(var(--step) * 1.5) calc(var(--step) * 3.5);
  /* A rule under the mark, matching the one above the address at the other end of the rail —
     so the departments sit in a bounded list rather than floating under the logo. */
  margin-bottom: calc(var(--step) * 3);
  border-bottom: 1px solid var(--rule);
}
.rail-head .logo { width: 132px; }

.icon-btn {
  appearance: none;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease;
}
.icon-btn:hover { background: var(--hover); color: var(--ink); }
.icon-btn svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.modes {
  display: grid;
  align-content: start;
  gap: 2px;
  overflow-y: auto;
  min-height: 0;
  /* A scrollbar drawn on the glass edge looks like damage. The rail is short enough that
     overflow is rare, and the wheel still works. */
  scrollbar-width: none;
}
.modes::-webkit-scrollbar { display: none; }

/* ── a department ─────────────────────────────────────────────────────────
   The head is a disclosure button; the apps under it are the tabs. Only the apps are
   `.tab`, which is what keeps applyPermissions() and syncTabs() in app.js working
   unchanged — they have always addressed `#modes .tab`. */
.dept-head {
  appearance: none;
  width: 100%;
  display: grid;
  grid-template-columns: 1.375rem minmax(0, 1fr) 0.75rem;
  align-items: center;
  gap: calc(var(--step) * 2.5);
  padding: calc(var(--step) * 2.5) calc(var(--step) * 2.5);
  border: 0;
  border-radius: var(--r-ctl);
  background: transparent;
  color: var(--ink-soft);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -.005em;
  text-align: left;
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease;
}
.dept-head:hover { background: var(--hover); color: var(--ink); }
/* Open, or holding the app you are in: named plainly rather than filled, because the
   department is not the thing you selected — one of its apps is. */
.dept-head[aria-expanded="true"] { color: var(--ink); }
.dept.is-current > .dept-head { color: var(--ink); }

.dept-icon { display: grid; place-items: center; color: var(--ink-faint); }
.dept-head:hover .dept-icon,
.dept-head[aria-expanded="true"] .dept-icon { color: var(--navy); }
.dept-icon svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.dept-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dept-chev {
  width: .75rem;
  color: var(--ink-faint);
  transition: transform .16s ease;
}
.dept-chev svg {
  width: 11px; height: 11px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.dept-head[aria-expanded="true"] .dept-chev { transform: rotate(90deg); }

/* The apps. Indented under their department by the same amount the icon takes, so the
   labels line up with the department name above them. */
.dept-apps {
  display: grid;
  gap: 1px;
  padding: 2px 0 calc(var(--step) * 2) calc(var(--step) * 8);
}

.tab {
  appearance: none;
  display: block;
  width: 100%;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--ink-soft);
  padding: calc(var(--step) * 2) calc(var(--step) * 3);
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -.005em;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease;
}
.tab:hover:not([aria-selected="true"]):not(:disabled) {
  background: var(--hover);
  color: var(--ink);
}
/* THE SELECTED APP. Navy fill with dark ink on it — rule 1 gives navy structure and
   identity and never an outcome, so the open app can be the strongest thing in the rail
   without being mistaken for a result. */
.tab[aria-selected="true"] {
  background: var(--navy);
  color: var(--navy-ink);
  font-weight: 650;
  box-shadow: 0 1px 2px rgba(38, 26, 16, .18), 0 6px 14px -6px rgba(0, 54, 128, .5);
}
/* Marks an app that lives at its own address, so "this leaves the hub" is visible before
   it is clicked rather than after. */
.tab.is-ext::after {
  content: "↗";
  float: right;
  margin-left: calc(var(--step) * 2);
  font-size: 11px;
  opacity: .6;
}

/* ── A BACKGROUND JOB, ON ITS TAB ────────────────────────────────────────────────────
   Green when a run nobody was watching has finished, red when it did not finish cleanly,
   and a quiet pulse while it works. Added TO a tab, never instead of it: the selected
   tab's navy fill has the higher specificity and keeps it, which is correct — a mark
   exists for the tabs you are NOT looking at, and it is cleared the moment you arrive.

   NOT ON AN EXTERNAL TAB, whose ::after is the ↗ that says the app leaves the hub. The
   four request tabs are never external, so this is a guard against a future one rather
   than a live collision — but a silently replaced arrow would be a poor way to find out. */
/* ── WHICH SITE THIS IS ──────────────────────────────────────────────────────────────
   Shown only when the server names the deployment (PC_SITE_LABEL). Deliberately loud: the
   development copy submits to the SAME bot and the SAME store as production, so somebody
   who thinks they are on a sandbox can charge a real customer's card from it.

   A hairline down the whole page as well as the chip, because a badge in the corner is
   exactly what somebody stops seeing after a day. */
/* Asked for as "a black Dev text below the pisces logo in the top left" (2026-09-18).
   Quiet on purpose: it is there to answer "which site am I on", which a reader asks once on
   arriving, not to warn them on every glance. Hidden entirely on production, where the
   server names no deployment. */
.site-label {
  /* A ROW OF ITS OWN inside .rail-head, which is why this is a flex basis rather than a
     display value: the head is a flex row, so `display: block` alone left it inline beside
     the logo. Full basis + wrap puts it on the line underneath. */
  flex: 0 0 100%;
  display: block;
  margin: -.35rem 0 0;
  color: #000;
  /* BOLD, asked for on 2026-09-18 — it was 600, which reads as emphasis rather than as a
     warning. This is the only thing on screen that says "the card you are about to charge
     is real but the data is not", so it is allowed to be the heaviest text in the rail. */
  font: 700 13px/1.2 system-ui, -apple-system, "Segoe UI", sans-serif;
  letter-spacing: .02em;
  text-align: center;
  white-space: nowrap;
}

/* ── THE SAME WORD ON THE SIGN-IN SCREEN ────────────────────────────────────

   A REGRESSION FIXED, not a new feature. The label used to live in the top bar, where the
   gate could be seen behind it; moving it under the logo put it inside the rail, and the
   rail is covered by the sign-in gate. So from that moment the development site's sign-in
   screen was pixel-identical to production's — the one screen where knowing which site you
   are on decides whether you type a real password into it. Measured with
   scripts/verify-render.py, which screenshots the gate: no label anywhere on it.

   Sits in the eyebrow beside "Pisces Central", which is the line that already names the
   application. */
.eyebrow .site-label-inline {
  display: inline;
  margin-left: .45em;
  color: #000;
  font-weight: 800;
  letter-spacing: .12em;
}
/* Narrowed to the icon, the word would push the rail wider than the mark, so it goes. */
body.rail-mini .site-label { display: none; }

.oe-pick-col { width: 2.2rem; text-align: center; }
/* Staged and waiting for somebody to place it; and stopped on a question. Both are states
   a reviewer looks for, so they read as findable rather than as warnings. */
.oe-read.is-staged { color: #157a4a; font-weight: 600; }
.oe-read.is-asking { color: #7a4f00; font-weight: 600; }
.oe-read.is-staging { color: #7a4f00; font-weight: 600; }
tr.is-staging { background: rgba(122, 79, 0, .06); }

/* ── A ROW BEING READ ────────────────────────────────────────────────────────────────
   A pulsing dot and a word, on the cell that would otherwise say "not read yet" — which is
   the wording that makes somebody read it a second time. Deliberately quiet: this is
   information, not a warning, and the queue is mostly rows nobody is touching. */
.oe-read.is-reading { color: #0b6ea8; font-weight: 600; }
tr.is-reading { background: rgba(11, 110, 168, .05); }
.oe-reading-dot {
  display: inline-block;
  width: .5em;
  height: .5em;
  margin-right: .4em;
  border-radius: 50%;
  background: currentColor;
  animation: oe-reading-pulse 1.2s ease-in-out infinite;
}
@keyframes oe-reading-pulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .oe-reading-dot { animation: none; opacity: .7; }
}

.tab.is-working { color: var(--ink-2, #46607f); }
.tab.is-finished { color: #157a4a; font-weight: 600; }
.tab.is-broken { color: #b3261e; font-weight: 600; }

.tab.is-working:not(.is-ext)::after,
.tab.is-finished:not(.is-ext)::after,
.tab.is-broken:not(.is-ext)::after {
  content: "";
  display: inline-block;
  width: .5em;
  height: .5em;
  margin-left: .45em;
  border-radius: 50%;
  background: currentColor;
}
.tab.is-working:not(.is-ext)::after { animation: tab-pulse 1.4s ease-in-out infinite; }

@keyframes tab-pulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
/* A dot, still, for somebody who has asked the system for less movement. */
@media (prefers-reduced-motion: reduce) {
  .tab.is-working:not(.is-ext)::after { animation: none; opacity: .7; }
}

/* A DISABLED TAB IS NO LONGER WHAT A RUNNING JOB LOOKS LIKE. syncTabs() locked the whole
   strip while a request ran, because the four tabs shared one job's variables; each tab
   owns its own now, so leaving one mid-run is safe and the strip stays usable. These rules
   remain for a tab a person has no grant for. The SELECTED app keeps its full fill while
   disabled — which app a request was sent from is part of reading the result, so it must
   not fade out with the control. */
.tab:disabled { cursor: not-allowed; }
.tab:disabled:not([aria-selected="true"]) { opacity: .4; }

.dept-empty {
  margin: 0;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 3);
  font-size: 12.5px;
  color: var(--ink-faint);
}

.rail-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--step) * 2);
  margin: 0;
  padding: calc(var(--step) * 3) calc(var(--step) * 2.5) 0;
  border-top: 1px solid var(--rule);
  font-size: 11.5px;
  color: var(--ink-faint);
}
.rail-foot { min-width: 0; }
.rail-foot #whoami {
  /* The address is the flex item that gives way — never Sign out, which people look for. */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-foot #signout { flex: 0 0 auto; font-size: 11.5px; }

/* ══════════════════════════════════════════════════════════════════════════
   THE HOME SCREEN
   ══════════════════════════════════════════════════════════════════════════
   Type on a photograph, with no panel under it. That only works if the type carries its own
   legibility, so the greeting is white with a layered shadow — the treatment a lock screen
   uses, and the only one that survives both a bright sunrise and a dark aurora. The search
   box is a control rather than decoration, so it does get a ground. */
.home {
  position: relative;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: calc(var(--step) * 6);
  padding: calc(var(--step) * 6);
  min-height: 0;
}
.home-greet {
  margin: 0;
  text-align: center;
  font-size: clamp(1.9rem, 4.2vw, 3.4rem);
  font-weight: 300;
  line-height: 1.1;
  letter-spacing: -.02em;
  color: #FFFFFF;
  /* Two shadows: a tight one for edge definition on pale cloud, and a wide soft one that
     darkens whatever is immediately behind the letters. */
  text-shadow: 0 1px 2px rgba(12, 10, 8, .45), 0 8px 40px rgba(12, 10, 8, .55);
}
.home-greet b { font-weight: 650; }

/* Wider than the one in the top bar, and with a real surface: it is the only thing to press
   on this screen. */
.search.home-search { width: min(30rem, 100%); }
.home-search input {
  padding: calc(var(--step) * 3) calc(var(--step) * 5) calc(var(--step) * 3)
           calc(var(--step) * 10);
  border: 1px solid var(--edge);
  background: var(--glass-chrome);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  box-shadow: var(--lift);
  font-size: 15px;
}
.home-search .search-icon { left: calc(var(--step) * 4); width: 17px; height: 17px; }
/* Only when the rail is a layer — otherwise the rail is right there and this is clutter. */
.icon-btn.home-menu {
  display: none;
  position: absolute;
  top: calc(var(--step) * 3);
  left: calc(var(--step) * 3);
  color: #FFFFFF;
  filter: drop-shadow(0 1px 3px rgba(12, 10, 8, .6));
}
.icon-btn.home-menu:hover { background: rgba(255, 255, 255, .16); }

.home-hint {
  margin: 0;
  font-size: 13px;
  color: rgba(255, 255, 255, .88);
  text-shadow: 0 1px 2px rgba(12, 10, 8, .5), 0 4px 20px rgba(12, 10, 8, .5);
}

/* ── the screen ───────────────────────────────────────────────────────────── */
.screen {
  display: grid;
  /* Top bar, scrolling body, action bar. The two bars are chrome and hold still; only the
     middle row moves. */
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  /* Clips the scrolling body to the panel's radius. Nothing may paint outside the glass. */
  overflow: hidden;
}

.screen-bar {
  background: var(--glass-chrome);
  display: grid;
  /* Three tracks, so the search sits on the CENTRE of the bar rather than in the space the
     crumb and the account block leave over — which changes width with the length of a
     department name and an app name. */
  grid-template-columns: minmax(0, 1fr) minmax(11rem, 26rem) minmax(0, 1fr);
  align-items: center;
  gap: calc(var(--step) * 4);
  padding: calc(var(--step) * 3.5) calc(var(--step) * 6);
  border-bottom: 1px solid var(--rule);
}
.screen-bar .rail-show { grid-column: 1; grid-row: 1; margin-right: calc(var(--step) * -2); }

.crumb {
  grid-column: 1;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: calc(var(--step) * 2);
  min-width: 0;
  font-size: 13px;
}
/* With the rail hidden its button appears in the bar, in the same track as the crumb. */
body.rail-off .crumb { padding-left: calc(var(--step) * 8); }
.who { grid-column: 3; justify-self: end; }
.crumb-dept { color: var(--ink-faint); white-space: nowrap; }
.crumb-sep { color: var(--rule-firm); }
.crumb-app {
  color: var(--ink);
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The search pill, as in the design: centred in the bar, its own translucent ground. */
.search {
  position: relative;
  display: flex;
  align-items: center;
  justify-self: center;
  width: 100%;
}
.search-icon {
  position: absolute;
  left: calc(var(--step) * 3.5);
  width: 15px;
  height: 15px;
  fill: none;
  stroke: var(--ink-faint);
  stroke-width: 1.8;
  stroke-linecap: round;
  pointer-events: none;
}
.search input {
  width: 100%;
  padding: calc(var(--step) * 2) calc(var(--step) * 4) calc(var(--step) * 2)
           calc(var(--step) * 9);
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--field);
  font-family: var(--sans);
  font-size: 13.5px;
}
.search input:focus {
  border-color: var(--rule-firm);
  background: var(--field-lift);
  box-shadow: none;
  outline: none;
}
.search input::-webkit-search-cancel-button { filter: invert(1) opacity(.5); }

/* ── what the box is searching ────────────────────────────────────────────
   A chip inside the field's right-hand end, naming the scope. It exists because one box that
   quietly changes meaning is a box people stop trusting: with it, "why did nothing match?"
   is answerable by looking at the box rather than by guessing. */
.search-scope {
  position: absolute;
  right: calc(var(--step) * 2);
  padding: 1px calc(var(--step) * 1.5);
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  background: var(--field-lift);
  color: var(--ink-faint);
  font-size: 10.5px;
  white-space: nowrap;
  pointer-events: none;
}
/* Room for the chip, so a long query never slides under it. */
.search-scoped .search input { padding-right: calc(var(--step) * 14); }

/* The list of places a NopC search could mean. Positioned like the Ask offer beneath it,
   because it is the same kind of thing: a suggestion under the box rather than an action
   taken on your behalf. */
.search-hits {
  position: absolute;
  top: calc(100% + var(--step) * 1.5);
  left: 0;
  z-index: 31;
  display: grid;
  width: max(100%, 20rem);
  padding: calc(var(--step) * 1);
  border: 1px solid var(--card-edge);
  border-radius: 9px;
  background: var(--glass-chrome);
  backdrop-filter: blur(18px) saturate(1.4);
}
.search-hit {
  padding: calc(var(--step) * 2) calc(var(--step) * 2.5);
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.search-hit:hover,
.search-hit.is-on { background: var(--field-lift); }
.search-hit:focus-visible { outline: 2px solid var(--rule-firm); outline-offset: -2px; }

.who {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 3);
  min-width: 0;
  font-size: 13px;
  color: var(--ink-soft);
}
.greet { white-space: nowrap; }
.greet b { color: var(--ink); font-weight: 650; }
/* Initials, not a photograph: the hub has no picture of anybody, and a fetched one would
   be an extra Graph call on every load for decoration. */
.avatar {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  flex: 0 0 auto;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: var(--navy-tint);
  color: var(--navy);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
}

.screen-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(var(--pad-y) * 1.2) var(--pad-x) var(--pad-y);
}
/* An external app fills the panel edge to edge — a frame with a 24px margin inside a
   rounded panel reads as a picture of a website rather than the website. */
body.app-ext .screen-body { padding: 0; overflow: hidden; }

.page-head { margin-bottom: calc(var(--step) * 5); }
.page-head .eyebrow { margin-bottom: calc(var(--step) * 2); }
.page-head h1 {
  margin: 0 0 calc(var(--step) * 1.5);
  font-size: clamp(1.4rem, 2.4vw, 1.7rem);
  font-weight: 650;
  letter-spacing: -.03em;
  line-height: 1.12;
}
.lede {
  margin: 0;
  max-width: 40rem;
  font-size: 14.5px;
  color: var(--ink-soft);
}

/* ── the document ───────────────────────────────────────────────────────── */
/* A grid with no named areas on purpose: items flow in DOM order, so a hidden card
   panel or notice is removed outright instead of leaving an empty row and its gaps. */
.doc {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: calc(var(--step) * 5);
  /* No ground and no border: it is a LAYOUT, not a card. A second rounded rectangle inside
     the screen panel read as a dialog sitting on the app rather than as the app. */
  padding: 0;
}
/* A statement, not a working surface: no raised fill, so it does not look like something
   to fill in. */
.doc-quiet { background: transparent; }

/* ── fields ─────────────────────────────────────────────────────────────── */
.field { display: grid; gap: calc(var(--step) * 1.5); align-content: start; }
.field + .field { margin-top: calc(var(--step) * 4); }

label {
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .11em;
  text-transform: uppercase;
  /* Navy, not grey: every small-caps line above a box is structure, and structure is what
     navy carries here. It also puts some colour back on a page that had gone entirely
     neutral apart from the mark. */
  color: var(--label);
}
/* A hint sits on the PANEL, above its card, so it takes the mid ink. Inside a card the
   faint ink is fine and is used as-is — see .card-panel .hint below. */
.hint { margin: 0; font-size: 13px; color: var(--ink-soft); }

textarea, input[type="text"], input[type="email"], input:not([type]), select {
  width: 100%;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  box-shadow: var(--card-lift);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  transition: border-color .14s ease, box-shadow .14s ease, background-color .14s ease;
}
/* The native dropdown arrow is drawn by the platform in the platform's colour, which on a
   dark ground comes out near-black on near-black. Drawn here instead. */
select {
  appearance: none;
  padding-right: calc(var(--step) * 8);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%),
    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-position:
    calc(100% - 18px) calc(50% + 1px),
    calc(100% - 13px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
/* The options list is a native popup and cannot be glass; make it legibly dark instead of
   letting it inherit a translucent fill it will render as white. */
select option { background: var(--menu-bg); color: var(--ink); }
textarea { resize: vertical; min-height: 3.4rem; line-height: 1.55; }
textarea:focus, input:focus, select:focus {
  border-color: var(--navy);
  box-shadow: 0 0 0 3px var(--navy-tint);
  outline: none;
}
::placeholder { color: var(--ink-faint); opacity: 1; }

/* Shut means LOOKS shut. Email & CC closes these boxes until Begin has read the order, and
   again while a saved card is chosen — and the browser's own disabled styling is nearly
   invisible here, which would leave a box that looks typeable and is not. */
textarea:disabled, input:disabled, select:disabled {
  background: var(--disabled);
  border-color: var(--rule);
  color: var(--ink-faint);
  cursor: not-allowed;
}
/* The label dims with its control, so a shut field reads as one shut thing rather than a
   live heading over a dead box. */
.field:has(:disabled) > label { color: var(--ink-faint); }

/* ── THE LOCKED ORDERS BOX — the signature ──────────────────────────────
   The same box you typed into, after you commit. It REPLACES the textarea rather than
   overlaying it, because a textarea cannot colour individual lines of its own value —
   so the box stays the same size, in the same place, in the same face, and the numbers
   you approved are the numbers that turn green. Nothing migrates to another part of the
   panel to report back.

   The numbered spine is the ledger that used to have its own section. The numbers are a
   CSS counter: the identifiers are the content, their position is a fact about the
   rendering. */
.orders-view {
  position: relative;
  counter-reset: oline;
  list-style: none;
  margin: 0;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 3);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  box-shadow: var(--card-lift);
  overflow: hidden;
}
.orders-view::before {
  content: "";
  position: absolute;
  left: 1.85rem; top: calc(var(--step) * 3); bottom: calc(var(--step) * 3);
  width: 1px;
  background: var(--rule);
}

.oline {
  position: relative;
  display: grid;
  grid-template-columns: 1.625rem auto 1fr auto;
  align-items: baseline;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 2) 0;
}
.oline + .oline { border-top: 1px solid var(--rule); }
.oline::before {
  counter-increment: oline;
  content: counter(oline, decimal-leading-zero);
  align-self: center;
  width: 1.625rem;
  padding: 3px 0;
  border-radius: 6px;
  background: var(--navy-tint);
  color: var(--navy);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 600;
  text-align: center;
}
.oline-kind {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
/* Rule 3: the number a person must actually read. */
.oline-id {
  font-family: var(--mono);
  font-size: 18px;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "zero" 1;
  font-weight: 600;
  letter-spacing: -.02em;
  color: var(--ink-faint);
  transition: color .2s ease;
}
.oline-state {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* Queued: legible but plainly not started. */
.oline.is-wait .oline-id { color: var(--ink-faint); }

.oline.is-working .oline-id { color: var(--ink); }
.oline.is-working .oline-state { color: var(--navy); }
.oline.is-working::before { background: var(--navy); color: var(--navy-ink); }

/* A wash as well as coloured type: this is the moment the person is waiting for, and it
   should be readable from across a desk. */
.oline.is-ok { background: linear-gradient(90deg, var(--ok-dim), transparent 55%); }
.oline.is-ok .oline-id { color: var(--ok); }
.oline.is-ok .oline-state { color: var(--ok); }
.oline.is-ok::before { background: var(--ok); color: var(--state-ink); }

.oline.is-bad { background: linear-gradient(90deg, var(--charge-dim), transparent 55%); }
.oline.is-bad .oline-id { color: var(--charge); }
.oline.is-bad .oline-state { color: var(--charge); }
.oline.is-bad::before { background: var(--charge); color: var(--state-ink); }

/* A dry run is neither — saying it failed trains people to ignore the word. */
.oline.is-dry .oline-id { color: var(--ink); }
.oline.is-dry .oline-state { color: var(--ink-soft); }

.orders-note {
  margin: calc(var(--step) * 1.5) 0 0;
  min-height: 1.1rem;
  font-size: 12.5px;
  /* On bare panel, like .foot — the faint ink is for text inside a card. */
  color: var(--ink-soft);
}
.orders-note.is-bad { color: var(--charge); }

/* ── the side column ────────────────────────────────────────────────────
   In card mode this is the right-hand column: the card panel at its natural height with
   the message strip filling whatever is left, so the strip finishes exactly level with
   the steps box beside it and the blank space under the card panel is put to work.

   In capture mode there is no second column and no card panel, so this collapses to
   nothing more than a wrapper around the strip. */
.side {
  display: flex;
  flex-direction: column;
  gap: calc(var(--step) * 4);
  min-height: 0;
}

/* ── card panel ─────────────────────────────────────────────────────────── */
.card-panel {
  margin: 0;
  padding: calc(var(--step) * 4);
  /* A card like the others, with the faintest navy in its ground so the half of the form that
     touches a card is distinguishable from the half that does not. */
  background:
    linear-gradient(var(--navy-tint), var(--navy-tint)),
    var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-left: 3px solid var(--navy);
  border-radius: 4px var(--r-card) var(--r-card) 4px;
  box-shadow: var(--card-lift);
  align-content: start;
}
.card-head h2 {
  margin: 0 0 calc(var(--step) * 1.5);
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: -.01em;
}
.card-panel .hint { color: var(--ink-faint); }
/* The card number takes the full width; expiry and code share the row under it. This
   holds up in the narrow side column AND full width, so it is the only card layout. */
.card-grid {
  margin-top: calc(var(--step) * 4);
  display: grid;
  grid-template-columns: 1fr 6.25rem;
  gap: calc(var(--step) * 3) calc(var(--step) * 4);
  align-items: end;
}
/* Named, not positional: the email row is only present in Email & CC mode, so
   :first-child would move to the card number in the other one and these are the rows
   that must span regardless. The saved-cards dropdown spans too — left to auto-placement
   it landed in the 1fr column, about 175px in the side panel, and clipped the card label
   that is the one thing a person has to read to pick the right card. */
.card-grid .field-email,
.card-grid .field-saved,
.card-grid .field-pan { grid-column: 1 / -1; }
.card-grid .field:first-child { grid-column: 1 / -1; }
.card-grid .field-exp { grid-column: 1; }
.card-grid .field-cvv { grid-column: 2; }
.card-grid .field + .field { margin-top: 0; }
.exp { display: flex; align-items: center; gap: calc(var(--step) * 1.5); }
.exp select { padding-inline: calc(var(--step) * 2) calc(var(--step) * 6); }
.exp select { background-position: calc(100% - 13px) calc(50% + 1px),
                                   calc(100% - 8px) calc(50% + 1px); }
.exp-sep { color: var(--ink-faint); }

/* ── live step progress ─────────────────────────────────────────────────
   The bot publishes each step as it takes it and this renders what arrived. A step is
   ticked ONLY because the pipeline said so — there is no timer here and no estimate.

   The bar at the top is a real fraction (steps closed / steps planned). The bar under
   the active step is indeterminate on purpose: the bot cannot know how long the gateway
   will take, and a bar that pretends to would be a lie told in animation. */
.progress {
  margin: 0;
  padding: calc(var(--step) * 3.5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-left: 3px solid var(--navy);
  border-radius: 4px var(--r-card) var(--r-card) 4px;
  box-shadow: var(--card-lift);
}
.progress-head {
  display: flex;
  align-items: baseline;
  gap: calc(var(--step) * 2);
}
.progress-label {
  font-size: 11.5px; font-weight: 650; letter-spacing: .11em;
  text-transform: uppercase; color: var(--navy);
}
#progress-what { font-size: 13.5px; font-weight: 600; }
.progress-count {
  margin-left: auto;
  font-size: 11.5px;
  color: var(--ink-soft);
}

.progress-track {
  margin-top: calc(var(--step) * 2.5);
  height: 3px;
  border-radius: 999px;
  background: var(--rule);
  overflow: hidden;
}
.progress-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--navy);
  transition: width .3s ease;
}

.psteps {
  list-style: none;
  margin: calc(var(--step) * 2.5) 0 0;
  padding: 0;
  display: grid;
}
.pstep {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  align-items: baseline;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 0.75) 0;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--ink-faint);
}
/* The status glyph. Drawn from content so it needs no icon font and no inline SVG. */
.pstep-mark {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.35;
  text-align: center;
  color: var(--rule-firm);
}
.pstep-body { min-width: 0; }
.pstep-note {
  display: block;
  margin-top: 1px;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--ink-faint);
  overflow-wrap: anywhere;
}

.pstep.is-done { color: var(--ink); }
.pstep.is-done .pstep-mark { color: var(--ok); font-weight: 700; }

.pstep.is-now { color: var(--ink); font-weight: 600; }
.pstep.is-now .pstep-mark { color: var(--navy); }

.pstep.is-fail { color: var(--ink); }
.pstep.is-fail .pstep-mark { color: var(--charge); font-weight: 700; }
.pstep.is-fail .pstep-note { color: var(--charge); }

/* Was running when the answer arrived, so its outcome was never polled. Deliberately NOT
   a tick and NOT a failure: the form does not know, and says so. Its note carries the
   weight, so it is set in the readable ink rather than the faint one. */
.pstep.is-unconfirmed .pstep-note { color: var(--ink-soft); }

/* The indeterminate bar on the step actually running right now. */
.pstep-bar {
  display: none;
  margin-top: calc(var(--step) * 1.5);
  height: 2px;
  border-radius: 999px;
  background: var(--navy-tint);
  overflow: hidden;
}
.pstep.is-now .pstep-bar { display: block; }
.pstep-bar::after {
  content: "";
  display: block;
  height: 100%;
  width: 40%;
  background: var(--navy);
  animation: pstep-sweep 1.1s ease-in-out infinite;
}
@keyframes pstep-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(250%); }
}

/* ── notice ─────────────────────────────────────────────────────────────── */
.notice {
  margin: 0;
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  border: 1px solid var(--card-edge);
  border-left: 3px solid var(--charge);
  border-radius: 4px var(--r-ctl) var(--r-ctl) 4px;
  background:
    linear-gradient(var(--charge-dim), var(--charge-dim)),
    var(--glass-sunk);
  box-shadow: var(--card-lift);
  font-size: 13.5px;
  /* One line per order, so the reasons stay separate and readable. */
  white-space: pre-line;
}
.notice.is-quiet {
  border-left-color: var(--label);
  background: var(--glass-sunk);
  color: var(--ink-soft);
}

/* ── commit ─────────────────────────────────────────────────────────────
   PINNED TO THE BOTTOM OF THE SCREEN PANEL, the way the reference design pins its
   controls. This is a safety property, not a flourish.

   The rule it replaces was "the whole form fits on one screen, so the commit button is
   never below the fold". That held on a full-page white document; inside a panel, with a
   rail beside it and a bar above it, it stopped holding — measured, not guessed: on a
   1366×768 laptop the Email & CC button sat 252px under the edge, and on 1536×864 it sat
   162px under. Trimming padding bought tens of pixels against a shortfall of hundreds.

   Pinning is strictly better than fitting: the button is in view at EVERY window size,
   including ones nobody measured, and the orders it applies to are directly above it.
   It needs its own ground because content scrolls underneath — the same glass family as
   the panels, so it is not a third material. */
/* A ROW OF THE PANEL, symmetric with .screen-bar: same material, same hairline, same inline
   padding. It was a sticky element inside the scrolling form until 2026-08-18, which meant it
   sat wherever the form happened to end — mid-panel on a short app, with a stray rule above it
   and a hundred pixels of empty panel below. As a row it is simply the bottom of the panel,
   nothing scrolls under it, and it needs neither a blur nor a conditional fill. */
.commit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--step) * 2) calc(var(--step) * 5);
  padding: calc(var(--step) * 3.5) var(--pad-x);
  border-top: 1px solid var(--rule);
  background: var(--glass-chrome);
}
/* An external app owns the whole panel; there is nothing here to act on. */
body.app-ext .commit { display: none; }
.commit-note { margin: 0; font-size: 13px; color: var(--ink-faint); }
/* The note doubles as the reason a disabled commit button will not light up. Red only for
   that: a faint grey sentence beside a dead button is not read as the explanation. */
.commit-note.is-bad { color: var(--charge); }

/* EVERY BUTTON IS A FILLED CHIP, not an outline on glass. An outlined button over a 62%
   panel over a photograph has no interior of its own — it reads as a rectangle drawn on the
   picture, and its label picks up whatever is behind it. The secondary gets a near-white
   chip and a 1.5px navy edge; the primary gets solid navy. Both then carry their own
   contrast, which is what buys the thinner glass. */
.btn {
  appearance: none;
  border: 1.5px solid var(--navy);
  background: rgba(255, 254, 252, .80);
  color: var(--navy);
  padding: calc(var(--step) * 2.5) calc(var(--step) * 6);
  border-radius: var(--r-ctl);
  font-size: 14px;
  font-weight: 650;
  letter-spacing: -.005em;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(38, 26, 16, .12);
  transition: background-color .14s ease, color .14s ease, border-color .14s ease,
              box-shadow .14s ease;
}
.btn:hover:not(:disabled) {
  background: var(--navy);
  color: var(--navy-ink);
  box-shadow: 0 2px 6px rgba(0, 54, 128, .28);
}
.btn-primary {
  background: var(--navy);
  color: var(--navy-ink);
  box-shadow: 0 1px 2px rgba(38, 26, 16, .18), 0 6px 16px -6px rgba(0, 54, 128, .45);
}
.btn-primary:hover:not(:disabled) {
  background: var(--navy-deep);
  border-color: var(--navy-deep);
  box-shadow: 0 2px 4px rgba(38, 26, 16, .22), 0 10px 22px -8px rgba(0, 35, 86, .5);
}
/* A flat grey chip, not a faded navy. At 32% opacity a filled button over light glass came
   out ghostly — it read as broken chrome rather than as "not yet", which is exactly the
   wrong impression to give about the button that charges a card. */
.btn:disabled {
  background: rgba(32, 26, 22, .13);
  border-color: transparent;
  color: rgba(32, 26, 22, .55);
  opacity: 1;
  box-shadow: none;
  cursor: not-allowed;
}
:root[data-theme="dark"] .btn {
  background: rgba(255, 255, 255, .10);
  box-shadow: none;
}
:root[data-theme="dark"] .btn-primary { background: var(--navy); }
/* AND .btn-link, for the same reason .btn-primary needs a line here: the rule above is two
   selectors to their one, so a filled button declared in the light block loses its fill in
   dark mode and keeps its ink — which for .btn-link meant near-black type on the translucent
   white chip over a dark panel. Caught by reading the computed colours; it is invisible in a
   stylesheet and obvious in a browser. */
:root[data-theme="dark"] .btn-link {
  background: var(--navy);
  color: var(--navy-ink);
}
:root[data-theme="dark"] .btn-link:hover:not(:disabled) { background: var(--navy-deep); }
:root[data-theme="dark"] .btn-ghost { background: rgba(255, 255, 255, .08); }
:root[data-theme="dark"] .btn:disabled {
  background: rgba(255, 255, 255, .10);
  color: rgba(255, 255, 255, .45);
}

/* Quiet enough to sit in a toolbar without competing with the action beside it. */
.btn-ghost {
  border-color: var(--rule-firm);
  background: rgba(255, 255, 255, .70);
  color: var(--ink-soft);
  font-weight: 550;
}
.btn-ghost:hover:not(:disabled) {
  background: rgba(255, 255, 255, .08);
  color: var(--ink);
  border-color: var(--rule-firm);
}

/* A SMALL FILLED BUTTON, not an underlined word.
 *
 * It used to zero the border and the padding and leave .btn's white fill behind — a white
 * highlight with nothing around it — and then REMOVE that fill on hover, so it looked
 * highlighted at rest and plain when the pointer was on it. Asked for on 2026-09-22:
 * "remove the highlight and turn them into blue buttons with white text".
 *
 * The colours are tokens rather than literals, so this is navy with white type in light mode
 * and a pale blue with dark type in dark mode — the same inversion .btn-primary uses. A
 * hard-coded #003680 with white on it would be unreadable in half the hub.
 *
 * Smaller and tighter than .btn because these sit inside table rows and a rail foot, where a
 * full-size button would set the row height. A disabled one still greys: .btn:disabled is
 * two classes to this one's one, so it wins wherever it applies. */
.btn-link {
  border: 0;
  padding: calc(var(--step) * 1.25) calc(var(--step) * 2.75);
  border-radius: 999px;
  font-weight: 600;
  font-size: 13px;
  background: var(--navy);
  color: var(--navy-ink);
  text-decoration: none;
  box-shadow: 0 1px 2px rgba(38, 26, 16, .14);
}
.btn-link:hover:not(:disabled) {
  background: var(--navy-deep);
  color: var(--navy-ink);
  box-shadow: 0 2px 6px rgba(0, 54, 128, .26);
}
#signout { white-space: nowrap; flex: 0 0 auto; }

/* Inside the form now, directly above the action bar — so it reads as the last word before
   the button rather than as something stranded underneath it.

   Set in --ink-soft rather than the faint ink: every box on this panel is an opaque card, so
   this is the only small text left with the photograph directly behind it, and at 12px faint
   it was the least readable thing on the page. */
.foot {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 40rem;
}

.muted { color: var(--ink-soft); }

/* ══════════════════════════════════════════════════════════════════════════
   THE DASHBOARD
   ══════════════════════════════════════════════════════════════════════════
   Cards on the panel, in the same material as every other box here: opaque, edged, lifted.
   The layout follows the reference design — tiles across the top, a wide chart with a
   narrower column of summaries beside it, tables underneath.

   THE PALETTE IS THE HUB'S, NOT THE REFERENCE'S. That design is violet throughout; navy is
   what carries structure on this site, and more importantly rule 1 at the top of this file
   reserves green and red for outcomes. A dashboard is the easiest place in the world to spend
   green on "revenue up" — so it spends none. Every bar, ring and figure here is navy or ink,
   and a day being down is stated in words rather than coloured like a failure. */
.dash { display: grid; gap: calc(var(--step) * 4); align-content: start; }
.dash-asof { margin: 0; font-size: 12.5px; color: var(--ink-soft); }

/* ── tiles ──────────────────────────────────────────────────────────────── */
.dash-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: calc(var(--step) * 3);
}
.kpi {
  padding: calc(var(--step) * 3.5) calc(var(--step) * 4);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
}
.kpi-label {
  margin: 0;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--label);
}
.kpi-value {
  margin: calc(var(--step) * 1.5) 0 0;
  font-family: var(--mono);
  font-size: clamp(1.35rem, 2.2vw, 1.75rem);
  font-weight: 600;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* Never coloured. A movement is a fact about arithmetic, not a verdict — see the note at the
   top of this section. */
.kpi-sub { margin: calc(var(--step) * 1) 0 0; font-size: 12px; color: var(--ink-soft); }

/* ── the two-column body ────────────────────────────────────────────────── */
.dash-grid { display: grid; gap: calc(var(--step) * 4); }
@media (min-width: 68rem) {
  .dash-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 19rem); align-items: start; }
}
.dash-side { display: grid; gap: calc(var(--step) * 4); align-content: start; }

.dash-card {
  padding: calc(var(--step) * 4);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
}
.dash-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  justify-content: space-between;
  gap: calc(var(--step) * 3);
  margin-bottom: calc(var(--step) * 3);
}
.dash-card-head h2 {
  margin: 0;
  font-size: 14.5px;
  font-weight: 650;
  letter-spacing: -.01em;
  color: var(--ink);
}
.dash-sub { margin: calc(var(--step) * 1) 0 0; font-size: 12.5px; color: var(--ink-soft); }
.dash-empty { margin: 0; font-size: 13px; color: var(--ink-soft); }

.dash-controls { display: flex; align-items: center; gap: calc(var(--step) * 2); }
.dash-select {
  width: auto;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 7) calc(var(--step) * 1.5)
           calc(var(--step) * 2.5);
  font-family: var(--sans);
  font-size: 12.5px;
  box-shadow: none;
}
/* A segmented control, because the windows are exclusive and few. */
.dash-toggle {
  display: flex;
  padding: 2px;
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  background: var(--hover);
}
.dash-toggle button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-soft);
  padding: calc(var(--step) * 1) calc(var(--step) * 3);
  font-size: 12px;
  font-weight: 650;
  cursor: pointer;
}
.dash-toggle button[aria-pressed="true"] { background: var(--navy); color: var(--navy-ink); }

/* ── the trend chart ────────────────────────────────────────────────────── */
.dash-chart { display: grid; gap: calc(var(--step) * 2); }
.trend-svg {
  width: 100%;
  height: clamp(9rem, 22vh, 14rem);
  display: block;
  overflow: visible;
}
.trend-bar { fill: var(--navy); opacity: .85; transition: opacity .12s ease; }
.trend-bar:hover { opacity: 1; }
/* Below the line is still navy-family, not red: a negative variance is a direction, not a
   failure. */
.trend-bar.is-down { fill: var(--ink-soft); opacity: .7; }
.trend-zero { stroke: var(--rule-firm); stroke-width: .12; }
.trend-axis {
  display: flex;
  justify-content: space-between;
  gap: calc(var(--step) * 2);
  font-size: 11.5px;
  color: var(--ink-faint);
}
.trend-peak { color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* ── the ring ───────────────────────────────────────────────────────────── */
.dash-gauge { display: grid; place-items: center; }
.gauge-svg { width: min(9.5rem, 60%); height: auto; }
.gauge-track { fill: none; stroke: var(--rule); stroke-width: 12; }
.gauge-fill {
  fill: none;
  stroke: var(--navy);
  stroke-width: 12;
  stroke-linecap: round;
}
.gauge-text {
  fill: var(--ink);
  font-family: var(--mono);
  font-size: 26px;
  font-weight: 600;
  text-anchor: middle;
  dominant-baseline: central;
}
.dash-pairs {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: calc(var(--step) * 1.5) calc(var(--step) * 3);
  margin: calc(var(--step) * 4) 0 0;
  font-size: 12.5px;
}
.dash-pairs dt { color: var(--ink-soft); }
.dash-pairs dd { margin: 0; text-align: right; color: var(--ink); }

/* ── bar rows, shared by reconciliation and warehouses ──────────────────── */
.dash-bars { display: grid; gap: calc(var(--step) * 2.5); }
.bar-row {
  display: grid;
  grid-template-columns: minmax(6rem, 9rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(var(--step) * 3);
  font-size: 12.5px;
}
.dash-bars-wide .bar-row { grid-template-columns: minmax(6rem, 11rem) minmax(0, 1fr) auto; }
/* In the narrow side column there is no room for label, bar and figure on one line — the bar
   ended up about 20px wide, which is not a bar. Stacked instead: the label and the figure share
   the top line, the bar gets the full width beneath them. */
.dash-side .bar-row {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: calc(var(--step) * 1) calc(var(--step) * 2);
}
.dash-side .bar-track { grid-column: 1 / -1; }
.bar-label {
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar-track {
  position: relative;
  height: 8px;
  border-radius: 999px;
  background: var(--hover);
  overflow: hidden;
}
.bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  background: var(--navy);
}
/* Two bars on one scale: what shipped, and beneath it what went out short. Same left edge,
   same scale, so the comparison needs no legend. */
.bar-stack { display: grid; gap: 3px; }
.bar-track.is-thin { height: 4px; }
.bar-fill.is-part { background: var(--ink-soft); opacity: .55; }
.bar-value { color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── the tables ─────────────────────────────────────────────────────────── */
.dash-tables { display: grid; gap: calc(var(--step) * 4); }
.dash-table-wrap { overflow-x: auto; min-width: 0; }
.dash-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.dash-table th {
  position: sticky;
  top: 0;
  text-align: left;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule-firm);
  background: var(--glass-sunk);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
  white-space: nowrap;
}
.dash-table td {
  padding: calc(var(--step) * 1.75) calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
.dash-table tr:last-child td { border-bottom: 0; }
.dash-table tr:hover td { background: var(--hover); }
.dash-table .is-right { text-align: right; }
/* Identifiers and money are monospaced for the reason everything else here is: these are
   numbers somebody compares against another screen, character by character. */
.dash-table .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.dash-table td:not(.mono) {
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-foot { margin: 0; font-size: 12px; color: var(--ink-soft); }

/* The action bar belongs to the request forms. A dashboard has nothing to commit, and
   neither does the permissions table — every change there saves itself. */
body.app-dash .commit,
body.app-admin .commit,
body.app-returns .commit,
body.app-oe .commit { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   OE REVIEW — a fax, what the OCR made of it, and how sure it was
   ══════════════════════════════════════════════════════════════════════════
   Two states in one panel. The queue is ordinary cards-and-a-table. The review state owns
   the whole panel and paints a ground of its own, for the same reason an external app does:
   the document IS the content, and a margin of glass around a scanned page reads as a
   picture of a fax rather than the fax.

   RULE 1 IS SPENT HERE, DELIBERATELY. The top of this file reserves red and green for
   outcomes and forbids them as decoration. A confidence score is not decoration: it is the
   OCR's own verdict on whether it read a character correctly, and triaging those verdicts is
   the entire job of this screen. Three fences keep the rule intact everywhere else:

     1. IT APPEARS IN TWO PLACES ONLY — the boxes over the document, and the chips that name
        the same number. Not on a button, not on a row, not on a status. The Review button is
        navy. There is no green button on this screen, because it approves nothing.
     2. IT IS THE SAME RED AND THE SAME GREEN — var(--charge) and var(--ok), so the site still
        has exactly one of each. The third hue is unavoidable: a two-colour scale forces the
        70-91 band to read as either "fine" or "failed", and that band being visibly NEITHER
        is the whole reason a human is looking at this.
     3. THE SELECTED BOX IS NAVY, not the source design's orange — navy is what marks the
        selected thing everywhere else in this hub.

   TWO SETS OF TOKENS, AND THE DARK THEME IS WHY. The chips sit on the panel's glass, so they
   follow the theme. The boxes sit on a SCANNED PAGE, which is white paper in both themes — so
   they must not, or dark mode puts a pale amber hairline on white paper. --oe-box-* are
   therefore absent from the dark block. */
.oe {
  height: 100%;
  min-height: 0;
  display: grid;

  --oe-box-low:  #9B1410;   --oe-box-low-fill:  rgba(155, 20, 16, .20);
  --oe-box-mid:  #8A5A00;   --oe-box-mid-fill:  rgba(138, 90, 0, .20);
  --oe-box-high: rgba(4, 89, 63, .55);
                            --oe-box-high-fill: rgba(4, 89, 63, .10);
  --oe-ink-low:  var(--charge);
  --oe-ink-mid:  #8A5A00;
  --oe-ink-high: var(--ok);
}
/* Chips only — the boxes keep the values above. See the note. */
:root[data-theme="dark"] .oe { --oe-ink-mid: #E8B84B; }

/* The review state goes edge to edge; the queue keeps the panel's own padding. */
body.app-oe .screen-body {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
body.app-oe .page-head { display: none; }

/* ── the queue ───────────────────────────────────────────────────────────── */
.oe-queue {
  display: grid;
  gap: calc(var(--step) * 4);
  align-content: start;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(var(--pad-y) * 1.2) var(--pad-x) var(--pad-y);
}
.oe-queue-head {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  justify-content: space-between;
  gap: calc(var(--step) * 3);
}
/* The filter and Refresh travel together on the right of the header, so the header keeps two
   ends rather than growing a third thing in the middle. */
.oe-queue-tools {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  flex-wrap: wrap;
}
.oe-filter {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
}
.oe-filter-label { font-size: 11px; color: var(--ink-faint); }
.oe-filter select {
  padding: calc(var(--step) * 1.25) calc(var(--step) * 2);
  border: 1px solid var(--card-edge);
  border-radius: 6px;
  background: var(--field);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 12.5px;
  max-width: 16rem;
}
.oe-filter select:focus { border-color: var(--rule-firm); outline: none; }

.oe-queue-head h2 {
  margin: 0 0 calc(var(--step) * 1);
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -.01em;
}
/* The reason is prose and may be long; the table scrolls inside its own wrapper rather than
   widening the panel. */
/* Narrowed when the Status column arrived. The table has nine columns now and the prose two
   are the only ones that can give: a PO that wraps mid-token and a Review button pushed off
   the right edge are both worse than a reason that takes three lines. */
.oe-why { max-width: 15rem; white-space: normal; }
.oe-read { max-width: 11rem; white-space: normal; font-size: 12px; }
/* A PO is one token and is read back character by character. It never wraps. */
.oe-table td.mono { white-space: nowrap; }
/* The store's own category. Short by construction — it is one of its dropdown values — so it
   gets a nowrap and stays out of the way of the reason beside it. */
.oe-status {
  font-size: 12px;
  color: var(--ink-soft);
  white-space: nowrap;
}
/* THREE STATES, THREE WEIGHTS. Held by somebody else is the one that has to catch the eye,
   because it is the one that changes what you can do; your own hold is reassurance; a lapsed
   one is history, and reads as history. */
.oe-held { font-size: 12px; color: var(--ink-faint); white-space: nowrap; }
.oe-held.is-held { color: var(--oe-ink-mid); font-weight: 600; }
.oe-held.is-mine { color: var(--oe-ink-high); font-weight: 600; }
.oe-held.is-lapsed { color: var(--ink-faint); font-style: italic; }
.oe-table td { vertical-align: middle; }
/* A held row is dimmed like a waiting one, and for the same reason: it is not yours to work
   right now. Not hidden — knowing an order exists and who has it is the point. */
.oe-table tr:has(.oe-held.is-held) td { opacity: .72; }
.oe-open {
  padding: calc(var(--step) * 1.25) calc(var(--step) * 3);
  font-size: 12.5px;
  white-space: nowrap;
}
/* A row nobody can work yet — no card, or the pipeline could not read it. */
.oe-table tr.is-waiting td { opacity: .62; }

/* ── one order: bar, split, bar ──────────────────────────────────────────── */
.oe-review {
  display: grid;
  /* THREE CHILDREN, THREE TRACKS: bar, split, actions. The staging result is NOT one of
     them — it lives inside the split's right-hand column, so it can never take height from
     the document. It was a fourth row once, against a three-track template, and helped
     itself to whatever its content wanted; see .oe-side. */
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100%;
  min-height: 0;
}
.oe-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 2.5) calc(var(--step) * 5);
  border-bottom: 1px solid var(--rule);
  background: var(--glass-chrome);
}
/* The identifier is the largest thing in the bar — it is what somebody reads out loud. */
.oe-po {
  margin: 0;
  font-size: 16px;
  font-weight: 650;
  letter-spacing: -.02em;
}
.oe-file {
  font-size: 11.5px;
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 18rem;
}
.oe-lock { font-size: 11.5px; color: var(--oe-ink-mid); }
.oe-dirty { margin-left: auto; font-size: 11.5px; color: var(--ink-soft); }
.oe-keys { font-family: var(--mono); font-size: 10.5px; color: var(--ink-faint); }
.oe-zoom { display: flex; align-items: center; gap: calc(var(--step) * 1.5); }
.oe-zoom .btn { padding: calc(var(--step) * 1) calc(var(--step) * 2.5); font-size: 13px; }
.oe-zoom span {
  font-size: 11px;
  color: var(--ink-soft);
  min-width: 3.25rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* THE SPLIT. One column below 72rem — the same threshold the request form's side column
   uses, and for the same reason: the rail and two lots of panel padding take about 19rem off
   the viewport before this sees any of it. */
.oe-split {
  display: grid;
  grid-template-rows: minmax(0, 1fr) minmax(0, 17rem);
  min-height: 0;
}
/* The right-hand column: the result on top, the fields filling what is left. A column
   rather than two grid rows because the result is sometimes absent, and a flex column
   collapses to nothing when it is — no empty track to style around. */
.oe-side {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.oe-side .oe-fields { flex: 1 1 auto; }

@media (min-width: 72rem) {
  .oe-split {
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr) minmax(0, 27rem);
  }
  .oe-side { border-left: 1px solid var(--rule); }
  .oe-side .oe-fields { border-top: 0; border-left: 0; }
}

/* The document pane scrolls both ways on its own. Sideways scroll INSIDE a pane is not the
   screen scrolling sideways — it is what zoom is for. */
.oe-doc {
  min-width: 0;
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
  padding: calc(var(--step) * 4);
  background: var(--hover);
}
.oe-pages { display: grid; gap: calc(var(--step) * 4); justify-items: center; }

/* ONE PAGE. The wrapper IS the image's box — no padding, no border-box arithmetic — because
   every overlay box is a percentage of it. */
.oe-page {
  position: relative;
  width: calc(100% * var(--oe-zoom, 1));
  max-width: none;
  border: 1px solid var(--card-edge);
  box-shadow: var(--card-lift);
  background: #FFFFFF;      /* a scan is paper, and paper is white in both themes */
}
.oe-page img {
  display: block;
  width: 100%;
  height: auto;
  /* Set from card.json's per-page pixel size, so a page reserves its exact height before its
     image arrives and the scroll position never jumps. */
  aspect-ratio: var(--oe-page-ar, 0.7727);
}
.oe-page-no {
  position: absolute;
  top: 0;
  left: calc(var(--step) * -1.5);
  transform: translateX(-100%);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink-faint);
}

/* A BOX. Geometry comes from four custom properties set in JS. The bboxes are normalised 0-1
   against the very image underneath, so a percentage of this wrapper IS the right place — at
   every zoom, with nothing recomputed. The 2px outset stops a hairline sitting on the glyphs
   it points at. A <button>, not a <div>, because it is clickable. */
.oe-box {
  position: absolute;
  left: calc(var(--x0) * 100% - 2px);
  top: calc(var(--y0) * 100% - 2px);
  width: calc((var(--x1) - var(--x0)) * 100% + 4px);
  height: calc((var(--y1) - var(--y0)) * 100% + 4px);
  padding: 0;
  border: 1px solid transparent;
  border-radius: 2px;
  background: transparent;
  cursor: pointer;
}
.oe-box.is-low  { border-color: var(--oe-box-low);  background: var(--oe-box-low-fill); }
.oe-box.is-mid  { border-color: var(--oe-box-mid);  background: var(--oe-box-mid-fill); }
.oe-box.is-high { border-color: var(--oe-box-high); background: var(--oe-box-high-fill); }
.oe-box.is-on   { outline: 2px solid var(--navy); outline-offset: 1px; z-index: 2; }

/* THE CHIP, in both places it appears — the queue's confidence column and a field row. One
   rule, so the two can never disagree about which band a number is in. */
.oe-chip {
  padding: 1px calc(var(--step) * 1.5);
  border: 1px solid currentColor;
  border-radius: 5px;
  font-size: 10.5px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.oe-chip.is-low  { color: var(--oe-ink-low); }
.oe-chip.is-mid  { color: var(--oe-ink-mid); }
.oe-chip.is-high { color: var(--oe-ink-high); }
.oe-chip.is-none { color: var(--ink-faint); }

/* ── a Power BI report ────────────────────────────────────────────────────
   Laid out exactly like .ext and for the same reasons — a grid of bar + stage with an
   explicit height, because .screen-body is a scroll container and a flex child inside it
   collapses to the height of its content. The first attempt used flex without height:100%
   and the panel came out 54px tall: the bar, and a zero-height report under it.

   .pbi-stage is position:relative because #pbi-blocked sits over it, and because
   powerbi-client injects its iframe into #pbi-host and sizes it from the box it is given. */
.pbi {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 100%;
  min-height: 0;
}
.pbi-stage { position: relative; min-height: 0; background: var(--card); }
.pbi-host { position: absolute; inset: 0; }
/* !important because powerbi-client writes width/height ATTRIBUTES on the iframe it
   creates, and an attribute beats a plain rule. */
.pbi-host iframe { display: block; width: 100% !important; height: 100% !important;
                   border: 0; }
/* The same three rules the external panel needs, for the same reasons: an embedded report
   fills the panel edge to edge, the hub must not scroll behind it, and .commit is the
   form's sticky "Capture N orders" bar — which otherwise sits under the report,
   offering to charge cards from inside a sales dashboard. */
body.app-pbi .screen-body { padding: 0; overflow: hidden; }
body.app-pbi .commit { display: none; }

/* ── the field pane ──────────────────────────────────────────────────────── */
.oe-fields {
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-top: 1px solid var(--rule);
}
.oe-group + .oe-group { border-top: 1px solid var(--rule); }
.oe-group-head {
  padding: calc(var(--step) * 2) calc(var(--step) * 4);
  background: var(--hover);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
}
.oe-group-body {
  display: grid;
  gap: calc(var(--step) * 2.5);
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
}
.oe-row {
  display: grid;
  gap: calc(var(--step) * 1);
  padding: calc(var(--step) * 1.5);
  margin: calc(var(--step) * -1.5);
  border-radius: var(--r-ctl);
}
.oe-row.is-on { background: var(--navy-tint); box-shadow: inset 0 0 0 1px var(--navy); }
.oe-row-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 2);
}
/* The dotted path is an identifier, not a heading — so the global label treatment
   (uppercase, letter-spaced) is undone here. */
.oe-path {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 500;
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
}
.oe-row input,
.oe-row select {
  width: 100%;
  font-family: var(--mono);
  font-size: 12.5px;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2.5);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  color: var(--ink);
}
.oe-row input:focus-visible,
.oe-row select:focus-visible {
  outline: 2px solid var(--navy);
  outline-offset: 1px;
}
.oe-row.is-edited input { border-color: var(--navy); }
.oe-src {
  font-size: 11px;
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A validator flag. Hard is the failed-outcome red, soft the middle band — the same two inks
   the chips use, because they are the same two facts. */
.oe-flag {
  padding: 0 calc(var(--step) * 1.5);
  border: 1px solid currentColor;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 9.5px;
}
.oe-flag.is-hard { color: var(--oe-ink-low); }
.oe-flag.is-soft { color: var(--oe-ink-mid); }
.oe-loc {
  padding: 0 calc(var(--step) * 1.25);
  border: 1px solid var(--card-edge);
  border-radius: 4px;
  font-size: 9.5px;
  color: var(--ink-faint);
}

/* Suggestion chips. The best one is filled rather than outlined — this file's way of saying
   "this is the live one". */
.oe-sug-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--step) * 1.5);
}
.oe-sug-label { font-size: 10px; color: var(--ink-faint); }
.oe-sug {
  padding: 1px calc(var(--step) * 1.75);
  font-family: var(--mono);
  font-size: 10px;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  cursor: pointer;
}
.oe-sug:hover { border-color: var(--navy); color: var(--navy); }
.oe-sug.is-best,
.oe-sug[aria-expanded="true"] {
  border-color: var(--navy);
  color: var(--navy);
  background: var(--navy-tint);
  font-weight: 650;
}
/* ── the product on the line ──────────────────────────────────────────────
   Shown on every line item, so it must read as REFERENCE rather than as something asking to
   be chosen. Squarer than a suggestion pill, no hover-to-navy, and it carries the product's
   name rather than a score — a suggestion offers an alternative, this states a fact. */
.oe-match-row { margin-top: calc(var(--step) * 1); }
.oe-prod {
  max-width: 100%;
  padding: 1px calc(var(--step) * 1.75);
  font-family: var(--sans);
  font-size: 10.5px;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--card-edge);
  border-radius: 5px;
  cursor: pointer;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.oe-prod:hover { border-color: var(--rule-firm); }
.oe-prod[aria-expanded="true"] {
  border-color: var(--rule-firm);
  background: var(--field);
  font-weight: 600;
}
/* DISCONTINUED IS NOT A DETAIL. It changes what happens to the order, so it is said on the
   closed chip rather than only inside the disclosure. The mid band is the palette's
   "somebody has to look at this", which is exactly what it means here. */
.oe-prod.is-dead { color: var(--oe-ink-mid); border-color: var(--oe-ink-mid); }
.oe-prod-none { font-size: 10.5px; color: var(--ink-faint); font-style: italic; }

.oe-sku {
  margin: calc(var(--step) * 1) 0 0;
  padding: calc(var(--step) * 2);
  border: 1px solid var(--card-edge);
  border-radius: 8px;
  background: var(--glass-sunk);
  font-size: 11px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: calc(var(--step) * .5) calc(var(--step) * 2);
}
.oe-sku dt { color: var(--ink-faint); }
.oe-sku dd { margin: 0; font-family: var(--mono); overflow-wrap: anywhere; }

/* The bottom bar — the commit row's material and rhythm, because it is the same row of the
   same panel. .commit itself belongs to the request forms and is hidden here. */
.oe-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 2) calc(var(--step) * 5);
  padding: calc(var(--step) * 3.5) var(--pad-x);
  border-top: 1px solid var(--rule);
  background: var(--glass-chrome);
}
.oe-actions-note { margin: 0; font-size: 13px; color: var(--ink-faint); }

/* ── THE CONFIRMATION, BESIDE THE BUTTON THAT CAUSED IT ────────────────────────

   Asked for with the single button (2026-09-18): "i want the order confirmation and ORder #
   to pop up in the same bar to the right of the place order button."

   Heavier than the note beside it on purpose. An order number is the one thing a reviewer
   writes down or reads out, and it appears at the moment a real order came into existence.
   `--ok` and `--charge` are the palette's outcome colours and both have a dark-mode value,
   so this follows the theme rather than pinning a green of its own. */
.oe-outcome {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 1.5) calc(var(--step) * 3);
  border: 1px solid var(--ok-dim);
  border-radius: 999px;
  background: var(--ok-dim);
  color: var(--ok);
  font-size: 13.5px;
  font-weight: 650;
  white-space: nowrap;
}
.oe-outcome a { color: inherit; font-weight: 700; text-decoration: underline; }
.oe-outcome-tick { font-size: 15px; line-height: 1; }
/* The run stopped before the order existed. The same shape, so the bar does not move, and
   the detail stays in the panel — this only says which way it went. */
.oe-outcome.is-bad {
  border-color: var(--charge-dim);
  background: var(--charge-dim);
  color: var(--charge);
}

/* The one button on this screen that creates a real order, so it is the one button that
   does not look like the others. --charge is the token the request forms already use for a
   result that failed; here it means "this is the irreversible one". */
.btn-danger {
  background: var(--charge);
  border-color: var(--charge);
  color: #FFFEFC;
  box-shadow: 0 1px 2px rgba(38, 26, 16, .18), 0 6px 16px -6px rgba(155, 20, 16, .45);
}
.btn-danger:hover:not(:disabled) { filter: brightness(.92); }

/* ── WHAT THE STORE DID ────────────────────────────────────────────────────
   A staged order's report. Every stage the flow completed, with what the store echoed
   back — because the reviewer is checking the STORE's values, not the ones we sent. */
.oe-stage-panel {
  padding: calc(var(--step) * 3.5) var(--pad-x) calc(var(--step) * 5);
  border-top: 1px solid var(--rule);
  background: var(--glass-chrome);
  /* AT MOST HALF THE COLUMN, and it scrolls inside that. The fields keep the rest: a
     reviewer reading a blocker usually needs the field it is about, so neither may push the
     other off screen. The DOCUMENT is unaffected either way — it is in the other column. */
  max-height: 55%;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Full-bleed padding belonged to a full-width panel; in a 27rem column it wasted a third
     of the width. */
  padding: calc(var(--step) * 3) calc(var(--step) * 4) calc(var(--step) * 4);
  border-top: 0;
  border-bottom: 1px solid var(--rule);
}
/* Folded to its header, which hands the whole screen back to the document. The result is
   still one click away and the headline stays visible, so folding loses nothing. */
.oe-stage-panel.is-folded {
  flex: none;
  max-height: none;
  overflow: visible;
  padding-bottom: calc(var(--step) * 3);
}
.oe-stage-panel.is-folded > :not(.oe-stage-head) { display: none; }
.oe-stage-fold { margin-left: auto; flex: none; }
.oe-stage-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: calc(var(--step) * 2) calc(var(--step) * 3);
  margin-bottom: calc(var(--step) * 3);
}
.oe-stage-head h3 { margin: 0; font-size: 15px; letter-spacing: -.01em; }
.oe-stage-sub { font-size: 13px; color: var(--ink-faint); }

.oe-stage-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 1px; }
.oe-stage-list li {
  display: grid;
  /* A fixed mark column so the stage names line up down the page rather than wandering
     with the width of each tick. */
  grid-template-columns: calc(var(--step) * 5) minmax(0, auto) minmax(0, 1fr);
  align-items: baseline;
  gap: 0 calc(var(--step) * 3);
  padding: calc(var(--step) * 1.5) 0;
  font-size: 13px;
}
.oe-stage-mark { font-weight: 700; }
.oe-stage-list li.is-ok   .oe-stage-mark { color: var(--ok); }
.oe-stage-list li.is-bad  .oe-stage-mark { color: var(--charge); }
.oe-stage-name { font-weight: 650; }
.oe-stage-detail { color: var(--ink-faint); overflow-wrap: anywhere; }
/* The line that says an order now exists. It has earned the emphasis. */
.oe-stage-list li.is-placed {
  background: var(--ok-dim);
  border-radius: var(--r-ctl);
  padding-inline: calc(var(--step) * 2);
  margin-inline: calc(var(--step) * -2);
}
.oe-stage-list li.is-placed .oe-stage-name { color: var(--ok); }

.oe-stage-blockers {
  margin: calc(var(--step) * 3) 0 0;
  padding: calc(var(--step) * 3);
  border-radius: var(--r-ctl);
  background: var(--charge-dim);
  color: var(--charge);
  font-size: 13px;
  line-height: 1.45;
}
/* A question, not an error — so it is not painted like one. */
.oe-stage-needs {
  margin: calc(var(--step) * 3) 0 0;
  padding: calc(var(--step) * 3);
  border-radius: var(--r-ctl);
  border: 1px solid var(--rule);
  font-size: 13px;
}
.oe-stage-needs h4 { margin: 0 0 calc(var(--step) * 2); font-size: 13px; }
.oe-stage-needs label {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  margin-right: calc(var(--step) * 4);
  margin-bottom: calc(var(--step) * 2);
}
.oe-stage-needs select {
  font: inherit;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2);
  border: 1.5px solid var(--rule);
  border-radius: var(--r-ctl);
  background: rgba(255, 254, 252, .8);
  color: inherit;
}
:root[data-theme="dark"] .oe-stage-needs select { background: rgba(9, 19, 22, .5); }
.oe-stage-cards { margin: calc(var(--step) * 2) 0 0; padding-left: calc(var(--step) * 5); }
.oe-stage-cards li { font-family: var(--mono); }

.oe-stage-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
  margin: calc(var(--step) * 3) 0 0;
}
.oe-stage-interlock { font-size: 13px; color: var(--ink-faint); }

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

@media (max-width: 61.999rem) {
  .oe-keys, .oe-file { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   AN EXTERNAL APP inside the screen
   ══════════════════════════════════════════════════════════════════════════ */
.ext {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 100%;
  min-height: 0;
}
/* ONE ROW when the app has no bar — see `bare` on To-Do in DEPARTMENTS — and this rule is
   the whole
   reason that flag needs a body class rather than just hiding the bar.

   `display: none` takes the bar out of the grid ENTIRELY, so the frame becomes the first
   item and lands in the `auto` row above. An iframe has no intrinsic height, so `auto`
   resolved to the HTML default of 150px: the board rendered into a 150px letterbox with
   six hundred pixels of empty panel under it, and the 1fr row sat unused. Measured on the
   live site, which is where it was found. */
body.ext-bare .ext { grid-template-rows: minmax(0, 1fr); }
.ext-bar {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 2.5) calc(var(--step) * 5);
  border-bottom: 1px solid var(--rule);
}
.ext-name { font-size: 13.5px; font-weight: 650; }
.ext-url {
  font-size: 11.5px;
  color: var(--ink-faint);
  margin-right: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ext-hint { font-size: 11.5px; color: var(--ink-faint); white-space: nowrap; }
.ext-bar .btn { padding: calc(var(--step) * 1.5) calc(var(--step) * 4); font-size: 12.5px; }

.ext-frame { position: relative; min-height: 0; }
.ext-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  /* The embedded app paints its own ground; white is the honest default under it rather
     than letting the glass show through a page that expected to own the window. */
  background: #FFFFFF;
}
/* Unless it is one of ours, styled for this hub — see `glass` in DEPARTMENTS. Then the frame
   paints nothing and the app sits on the panel's glass like every other screen here. The
   embedded document must also leave its own body transparent, or this achieves nothing. */
body.ext-glass .ext-frame iframe { background: transparent; }
/* No ground of its own. It only ever appears with the frame hidden, so there is nothing
   behind it to cover — and a fill here composites on top of the panel's glass and comes out
   lighter than everything around it, the same trap the action bar fell into. */
.ext-blocked {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-items: start;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 10);
}
.ext-blocked h2 { margin: 0; font-size: 1.05rem; font-weight: 650; }

/* ══════════════════════════════════════════════════════════════════════════
   Side-panel modes, wide: the panel goes BESIDE the ledger
   ══════════════════════════════════════════════════════════════════════════
   This is what buys the height that keeps the commit button above the fold. Below this
   width the single-column flow above applies and the panel may scroll — a narrow window
   has no second column to give.

   72rem, not the 60rem this used on the paper version: the rail and two lots of panel
   padding take about 19rem off the viewport before the form sees any of it, so judging by
   viewport width alone put the form into two cramped columns at 60rem. */
@media (min-width: 72rem) {
  body.mode-card #form,
  body.mode-po #form,
  body.mode-ec #form {
    grid-template-columns: minmax(0, 1fr) minmax(0, 19rem);
    column-gap: calc(var(--step) * 6);
    align-items: start;
  }
  body.mode-card #form > .field-orders,
  body.mode-po   #form > .field-orders,
  body.mode-ec   #form > .field-orders { grid-column: 1; grid-row: 1; }
  body.mode-card #form > #progress,
  body.mode-po   #form > #progress,
  body.mode-ec   #form > #progress     { grid-column: 1; grid-row: 2; }
  /* Spanning both rows is what makes the column as tall as the orders box plus the
     steps box, and the flex child below is what makes the strip reach the bottom of it. */
  body.mode-card #form > .side,
  body.mode-po   #form > .side,
  body.mode-ec   #form > .side         { grid-column: 2; grid-row: 1 / span 2; }
  body.mode-card #form > .side > #notice,
  body.mode-po   #form > .side > #notice,
  body.mode-ec   #form > .side > #notice {
    flex: 1 1 auto;
    /* min-height:0 lets it shrink inside the flex column; without it a long result
       would push the column taller and put the panel back into scroll. */
    min-height: 0;
    overflow-y: auto;
  }
  /* No explicit row: auto-placement puts this after the block above. */
  body.mode-card #form > .foot,
  body.mode-po   #form > .foot,
  body.mode-ec   #form > .foot          { grid-column: 1 / -1; }
}

/* ── the Admin app ────────────────────────────────────────────────────────
   A table, because the question it answers is a grid: people down, apps across. Ticks
   rather than a multi-select per person — with four columns the whole grant is legible at
   a glance, and "who can charge a card" is a question somebody should be able to answer by
   looking rather than by opening four dropdowns.

   No red anywhere: rule 1 at the top of this file reserves it for a result that failed,
   and a person's permission is not an outcome. The only red on this panel is a write that
   did not save. */
.admin-head { margin-bottom: calc(var(--step) * 5); }

.admin-add {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: calc(var(--step) * 3);
  padding-bottom: calc(var(--step) * 5);
  margin-bottom: calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule);
}
.admin-add .field { flex: 1 1 22rem; max-width: 26rem; }

/* The grid scrolls inside itself rather than widening the panel — six columns in a narrow
   window has to go somewhere, and the screen must never scroll sideways.
   min-width:0 is what makes overflow-x actually contain the table: #admin-panel is a
   `.doc`, so this is a GRID ITEM, and a grid item's default min-width:auto refuses to
   shrink below its content. */
.admin-table-wrap { overflow-x: auto; min-width: 0; }
.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
.admin-table th {
  text-align: left;
  padding: calc(var(--step) * 2) calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule-firm);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* The tick headings WRAP, deliberately. They carry each app's exact name — an admin has to
   know which column is which app — and five unwrapped names plus an email column was wider
   than the content track, so the table scrolled and clipped the Remove button. */
.admin-table th:not(:first-child) {
  text-align: center;
  white-space: normal;
  max-width: 5.5rem;
  line-height: 1.25;
}
.admin-table th:first-child { white-space: nowrap; }
.admin-table td {
  padding: calc(var(--step) * 2.5) calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
}
.admin-table tr:hover td { background: var(--hover); }
.admin-tick { text-align: center; }
.admin-tick input {
  width: 16px; height: 16px; accent-color: var(--navy); cursor: pointer;
}
.admin-tick input:disabled { cursor: not-allowed; opacity: .45; }

/* The identifier is monospaced for the same reason every other identifier here is: these
   are addresses somebody could mistype, and a wrong one grants the wrong person. */
.admin-who {
  font-family: var(--mono);
  font-size: 13px;
  white-space: nowrap;
}
.admin-you {
  font-family: var(--sans);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--navy);
  background: var(--navy-tint);
  border-radius: 999px;
  padding: 1px calc(var(--step) * 2);
}
/* width:1% with nowrap is the "shrink to fit the content" idiom for a table cell: the
   person column takes the slack instead, and "Remove" stops being clipped to "Rem". */
.admin-table th:last-child,
.admin-table td:last-child { width: 1%; white-space: nowrap; text-align: right; }
.admin-remove { font-size: 12.5px; white-space: nowrap; }

/* ── the access editor ────────────────────────────────────────────────────
   Opens under a person's row. A column per department, its apps beneath it — the same shape
   as the rail, so what an admin ticks here looks like what that person will see. */
.admin-grant {
  font-size: 12.5px;
  color: var(--ink-soft);
  max-width: 28rem;
  white-space: normal;
}
.admin-editor-row > td {
  padding: 0 calc(var(--step) * 2) calc(var(--step) * 4);
  background: var(--hover);
  /* The editor's cell is its row's LAST child, and .admin-table td:last-child sets nowrap so
     the Remove button never breaks in two — which clipped the note at the bottom of this
     panel. Undone here rather than there. */
  white-space: normal;
}
.perm-tree {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: calc(var(--step) * 4);
  padding: calc(var(--step) * 4);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  background: var(--glass-sunk);
}
.perm-dept { display: grid; gap: calc(var(--step) * 1.5); align-content: start; }

/* The department, then its apps indented under it. */
/* These are <label> elements, and the global label rule sets them in uppercase small-caps
   for the form's field headings. Here they are app NAMES — "WAREHOUSE LOGISTIC…" shouted and
   clipped is not a name — so that typography is undone rather than inherited. */
.perm-dept-all, .perm-app {
  display: flex;
  align-items: baseline;
  gap: calc(var(--step) * 2);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  white-space: normal;
  cursor: pointer;
}
.perm-dept-all {
  font-weight: 650;
  color: var(--ink);
  padding-bottom: calc(var(--step) * 1);
  border-bottom: 1px solid var(--rule);
}
.perm-app { padding-left: calc(var(--step) * 4); color: var(--ink-soft); }
.perm-app input, .perm-dept-all input {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  accent-color: var(--navy);
  cursor: pointer;
}
/* Ticked BY the department: on, and plainly not yours to change here. */
.perm-app.is-implied { opacity: .62; cursor: default; }
.perm-app.is-implied input { cursor: not-allowed; }
.perm-none {
  margin: 0;
  padding-left: calc(var(--step) * 4);
  font-size: 12px;
  color: var(--ink-faint);
}
.perm-foot {
  grid-column: 1 / -1;
  margin: 0;
  padding-top: calc(var(--step) * 2);
  border-top: 1px solid var(--rule);
  font-size: 12px;
  color: var(--ink-soft);
}
.admin-empty { padding: calc(var(--step) * 4) calc(var(--step) * 1); }

.admin-note {
  margin: calc(var(--step) * 4) 0 0;
  min-height: 1.2em;
  font-size: 13px;
  color: var(--ink-soft);
}
.admin-note.is-bad {
  padding: calc(var(--step) * 2) calc(var(--step) * 3);
  border-left: 3px solid var(--charge);
  border-radius: 0 var(--r-ctl) var(--r-ctl) 0;
  background: var(--charge-dim);
  color: var(--ink);
}

/* The no-access panel. Deliberately plain: it is a statement, not a failure. */
#no-access { padding: calc(var(--step) * 7); }

/* These two carried inline `style` attributes until 2026-08-17. The CSP is style-src
   'self', which blocks style attributes outright — so they never applied in production and
   both headings had been rendering with the browser's default margins. Real rules now. */
.doc-title {
  margin: 0 0 calc(var(--step) * 2);
  font-size: 1.05rem;
  font-weight: 650;
}
.doc-title-tight { margin-bottom: calc(var(--step) * 1); }
.doc-lede { margin: 0 0 calc(var(--step) * 3); }

/* ══════════════════════════════════════════════════════════════════════════
   Narrow
   ══════════════════════════════════════════════════════════════════════════
   Under 62rem the rail stops being a column and becomes an overlay: at that width two
   panels side by side leave the form about 26rem, which is narrower than the card panel
   it has to hold. The hamburger in the screen's bar is what brings it back. */
@media (max-width: 61.999rem) {
  .stage { grid-template-columns: minmax(0, 1fr); }
  .screen, .home { grid-column: 1; }
  .rail {
    position: absolute;
    z-index: 30;
    top: clamp(10px, 1.4vh, 22px);
    left: clamp(10px, 1.1vw, 22px);
    bottom: clamp(10px, 1.4vh, 22px);
    width: min(17rem, calc(100% - 2 * clamp(10px, 1.1vw, 22px)));
  }
  /* On these widths the rail is over the screen, so it always offers the way out. */
  .icon-btn.rail-show { display: grid; }
  .icon-btn.home-menu { display: grid; }
  /* The rail is a layer here, so it gets the layer's shadow and its scrim. */
  .rail { box-shadow: 0 2px 8px rgba(38, 26, 16, .25),
                     0 40px 90px -20px rgba(30, 20, 12, .5); }
  body:not(.rail-off) .rail-scrim { display: block; }
  .screen-bar {
    grid-template-columns: auto minmax(0, 1fr) auto;
    padding-inline: calc(var(--step) * 4);
  }
  /* The greeting is the first thing to go: the crumb says where you are and the avatar
     says who you are, and neither of those is decoration. */
  .search { grid-column: 1 / -1; grid-row: 2; }
  .greet { display: none; }
}

@media (max-width: 40rem) {
  :root { --pad-x: 16px; }
  .gate { place-items: center; padding-inline: calc(var(--step) * 5); }
  .gate-inner { padding: calc(var(--step) * 7) calc(var(--step) * 5); }
  .logo, .rail-head .logo { width: 124px; }
  .logo, :root .rail-head .logo { width: 156px; }
  /* The identifier stays big — it is the thing being checked. The row number and the
     kind label are what give way. */
  .oline { grid-template-columns: 1.375rem auto 1fr; gap: calc(var(--step) * 2.5); }
  .oline::before { width: 1.375rem; }
  .oline-state { grid-column: 2 / -1; }
  .orders-view::before { left: 1.55rem; }
  .ext-bar { flex-wrap: wrap; }
  :root { --r-panel: 22px; }
}

/* ── short windows ───────────────────────────────────────────────────────
   A 768px-tall laptop has about 250px less to give than a 1080p monitor, and all of the
   difference comes out of the form. The bar above and the panel around it give up their
   generosity first — none of this changes what anything says. */
@media (max-height: 54rem) {
  :root { --pad-y: 14px; --pad-x: 20px; }
  .commit { padding-block: calc(var(--step) * 2.5); }
  .stage { padding-block: clamp(8px, 1.4vh, 16px); }
  .screen-bar { padding-block: calc(var(--step) * 2.5); }
  .page-head { margin-bottom: calc(var(--step) * 3); }
  .doc { gap: calc(var(--step) * 4); }
  .card-grid { margin-top: calc(var(--step) * 3); gap: calc(var(--step) * 2.5); }
  .progress { padding: calc(var(--step) * 3); }
}

/* ── Ask Anything ─────────────────────────────────────────────────────────
   The offer under the search box, then the panel an answer lands in.

   The panel's order is the argument: question, answer, then the receipt — what was read, over
   which dates, how many rows, and the query itself. The receipt is small but never hidden.
   A number whose derivation is one click away is a number people stop checking, and the whole
   point of this feature is that accounting can reproduce a figure rather than trust it. */

.ask-offer {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  width: 100%;
  margin: calc(var(--step) * 2) 0 0;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  color: var(--ink);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  cursor: pointer;
}
.ask-offer:hover { border-color: var(--navy); }
.ask-offer:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.ask-offer-icon {
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  fill: none;
  stroke: var(--navy);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The typed words, quoted. Truncated rather than wrapped: the button is a single line, and a
   long question growing it would shift the search box under the pointer. */
.ask-offer-text {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-soft);
}
.ask-offer-text b { font-weight: 600; color: var(--ink); }
.ask-offer-key {
  flex: 0 0 auto;
  padding: 1px calc(var(--step) * 1.5);
  font-size: 12px;
  color: var(--label);
  border: 1px solid var(--card-edge);
  border-radius: 5px;
}

/* In the screen bar the search box is one cell of a row, so the offer hangs beneath it rather
   than pushing the bar taller. */
.ask-offer-float {
  position: absolute;
  top: calc(100% + var(--step) * 1.5);
  left: 0;
  z-index: 30;
  width: max(100%, 20rem);
  background: var(--glass-chrome);
  backdrop-filter: blur(18px) saturate(1.4);
}
.screen-bar .search { position: relative; }

/* ── the panel ─────────────────────────────────────────────────────────── */
.ask { display: grid; gap: calc(var(--step) * 4); align-content: start; }

.ask-form { display: flex; gap: calc(var(--step) * 2); align-items: stretch; }
.ask-box { flex: 1 1 auto; min-width: 0; width: auto; }
.ask-send { flex: 0 0 auto; }
/* A QUESTION IS PROSE, not data. Every other text field on this site is monospaced on purpose
   — they hold order numbers and card numbers, which people read back digit by digit — and
   `input[type="text"]` claims all of them. This is the one field holding a sentence, so it
   opts out. Selected on two classes so it wins on specificity rather than on where it happens
   to sit in this file. */
.search.ask-box input {
  font-family: var(--sans);
  font-size: 15px;
  border-radius: 999px;
}

.ask-examples { display: grid; gap: calc(var(--step) * 2.5); }
.ask-examples-lede { margin: 0; font-size: 13px; color: var(--ink-soft); }
.ask-chips { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 2); }
.ask-chip {
  padding: calc(var(--step) * 1.75) calc(var(--step) * 3);
  font: inherit;
  font-size: 12.5px;
  color: var(--ink);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  cursor: pointer;
}
.ask-chip:hover { border-color: var(--navy); color: var(--navy); }
.ask-chip:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

.ask-thinking {
  display: grid;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 4);
  font-size: 13px;
  color: var(--ink-soft);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
}
.ask-sweep {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: var(--navy-tint);
  overflow: hidden;
}
.ask-sweep::after {
  content: "";
  display: block;
  height: 100%;
  width: 40%;
  background: var(--navy);
  animation: pstep-sweep 1.1s ease-in-out infinite;
}

/* ── the answer ────────────────────────────────────────────────────────── */
.ask-result {
  display: grid;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
}
.ask-question {
  margin: 0;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--label);
}
/* The largest thing on the panel, because it is the thing being asked for. */
.ask-answer {
  margin: 0;
  font-size: 19px;
  line-height: 1.45;
  font-weight: 500;
  letter-spacing: -.011em;
  color: var(--ink);
}
/* A refusal is the service working. Quieter than an answer, and pointedly not red — there is
   nothing to fix and nothing went wrong. */
.ask-answer.is-declined {
  font-size: 16px;
  font-weight: 450;
  color: var(--ink-soft);
}
.ask-needed, .ask-reading {
  margin: 0;
  font-size: 13px;
  color: var(--ink-soft);
}
.ask-reading { font-style: italic; }

/* The receipt. Four terms, on one line where there is room. */
.ask-facts {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--step) * 1) calc(var(--step) * 5);
  margin: 0;
  padding: calc(var(--step) * 3) 0 0;
  border-top: 1px solid var(--rule);
}
.ask-facts dt {
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
}
.ask-facts dd {
  margin: 0 0 0 calc(var(--step) * 2);
  font-size: 12.5px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ask-caveats, .ask-partial {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* ── the chart ─────────────────────────────────────────────────────────────
   Same family as the dashboard's trend chart: navy first, and everything after it drawn from
   the same palette rather than a rainbow. Colour here distinguishes series; it does not report
   an outcome, so nothing in a chart is red or green.

   Six series maximum, matching the server's ceiling. Past that a reader cannot tell one line
   from another, which is why the server refuses rather than adding a seventh colour. */
.ask-chart {
  margin: 0;
  padding: calc(var(--step) * 3) calc(var(--step) * 3) calc(var(--step) * 2);
  border: 1px solid var(--card-edge);
  border-radius: 12px;
  background: var(--glass-sunk);
  min-width: 0;
}
.ask-chart-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--step) * 2);
  margin-bottom: calc(var(--step) * 2.5);
}
.ask-legend { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 3); min-width: 0; }
.ask-legend-item {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  font-size: 12px;
  color: var(--ink-soft);
}
.ask-legend-dot { width: 9px; height: 9px; border-radius: 3px; background: currentColor; }

/* The type switch. The server picks a sensible default; whether bars or a line read better is
   rarely settled by the question, so the reader gets the last word. */
.ask-chart-types { display: flex; gap: 2px; flex: 0 0 auto; }
.ask-type {
  padding: 3px calc(var(--step) * 2);
  font: inherit;
  font-size: 11.5px;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
}
.ask-type:hover { color: var(--ink); background: var(--hover); }
.ask-type.is-on {
  color: var(--navy);
  background: var(--navy-tint);
  border-color: var(--navy-tint);
  font-weight: 600;
}
.ask-type:focus-visible { outline: 2px solid var(--navy); outline-offset: 1px; }

/* No height here, and no `width: 100%`. The SVG carries its own pixel width and height, set
   from a measurement of this container — stretching it with CSS is what turned the dots into
   ellipses. */
.ask-svg {
  display: block;
  overflow: visible;
  max-width: 100%;
}

/* Series colours. Navy leads because it is the brand and the dashboard's own bar colour; the
   rest are chosen to stay distinguishable in greyscale as well as in colour, since these get
   printed and pasted into emails. */
.ask-bar.s1, .ask-dot.s1 { fill: var(--navy); }
.ask-bar.s2, .ask-dot.s2 { fill: #4C8DBF; }
.ask-bar.s3, .ask-dot.s3 { fill: #8AA8C9; }
.ask-bar.s4, .ask-dot.s4 { fill: var(--ink-soft); }
.ask-bar.s5, .ask-dot.s5 { fill: #6E7F94; }
.ask-bar.s6, .ask-dot.s6 { fill: #B4C4D6; }
.ask-bar { opacity: .88; transition: opacity .12s ease; }
.ask-bar:hover { opacity: 1; }
/* Below the line stays in the navy family: a negative is a direction, not a failure. */
.ask-bar.is-down { opacity: .6; }

/* In pixels now that the viewBox is the pixel box. Two is a line you can see without it
   dominating the plot. */
.ask-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ask-line.s1 { stroke: var(--navy); }
.ask-line.s2 { stroke: #4C8DBF; }
.ask-line.s3 { stroke: #8AA8C9; }
.ask-line.s4 { stroke: var(--ink-soft); }
.ask-line.s5 { stroke: #6E7F94; }
.ask-line.s6 { stroke: #B4C4D6; }
.ask-area { opacity: .14; stroke: none; }
.ask-area.s1 { fill: var(--navy); }
.ask-area.s2 { fill: #4C8DBF; }
.ask-area.s3 { fill: #8AA8C9; }
.ask-area.s4 { fill: var(--ink-soft); }
.ask-area.s5 { fill: #6E7F94; }
.ask-area.s6 { fill: #B4C4D6; }
.ask-zero { stroke: var(--rule-firm); stroke-width: 1; }

/* Ticks are HTML, not SVG text: the viewBox is stretched to the panel width, which would
   stretch letters with it. */
.ask-plot-upright { min-width: 0; }
/* The scale printed beside the plot. Small, monospaced, and always there — "orders rose" and
   "orders rose from 1,200 to 1,458" are different statements. */
.ask-plot-body { display: flex; align-items: stretch; gap: calc(var(--step) * 2); min-width: 0; }
/* The plot's own space. Measured before anything is drawn into it, so the SVG is sized to the
   room it actually has rather than to the row's full width. */
.ask-plot-slot { flex: 1 1 auto; min-width: 0; }
.ask-scale {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 10px;
  color: var(--ink-faint);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ask-ticks {
  display: flex;
  /* Shifted to start where the plot starts, past the axis labels, so a tick sits under its
     own point rather than half a label to the left. */
  margin-left: var(--tick-offset, 0);
  margin-top: calc(var(--step) * 1.5);
  font-size: 10.5px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.ask-tick {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
}

/* Bars across, for labels that need a line of their own — facility names here run past forty
   characters and are unreadable rotated under a vertical axis. */
.ask-hbars { display: grid; gap: calc(var(--step) * 1.5); }
.ask-hbar-row {
  display: grid;
  grid-template-columns: minmax(6rem, 14rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(var(--step) * 2);
}
.ask-hbar-label {
  font-size: 12px;
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ask-hbar-track {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  position: relative;
}
/* Width comes from a custom property set in JS. Not a style attribute in the markup — the CSP
   has no 'unsafe-inline' for styles, and several inline widths in this project silently never
   applied in production before that was understood. */
.ask-hbar-fill {
  display: block;
  height: 9px;
  border-radius: 3px;
  width: var(--w, 0%);
  margin-left: var(--from, 0%);
  min-width: 1px;
}
.ask-hbar-fill.s1 { background: var(--navy); }
.ask-hbar-fill.s2 { background: #4C8DBF; }
.ask-hbar-fill.s3 { background: #8AA8C9; }
.ask-hbar-fill.s4 { background: var(--ink-soft); }
.ask-hbar-fill.s5 { background: #6E7F94; }
.ask-hbar-fill.s6 { background: #B4C4D6; }
.ask-hbar-fill.is-down { opacity: .6; }
.ask-hbar-value {
  font-size: 11.5px;
  color: var(--ink);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.ask-chart-axis {
  margin-top: calc(var(--step) * 1.5);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--label);
}
/* Said out loud rather than silently falling back to the table: being told "two hundred
   categories is too many to read" is useful; a table appearing where a graph was asked for
   looks like the question was ignored. */
.ask-chart-refused {
  margin: 0;
  font-size: 12.5px;
  color: var(--ink-soft);
}
.ask-series-item { margin-top: calc(var(--step) * 3); }
.ask-series-head {
  margin: 0 0 calc(var(--step) * 1) 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--label);
}
.ask-copy-data { align-self: start; }

@media (max-width: 720px) {
  .ask-hbar-row { grid-template-columns: minmax(4rem, 8rem) minmax(0, 1fr) auto; }
  .ask-chart-head { flex-direction: column; align-items: stretch; }
}

.ask-table-wrap {
  overflow-x: auto;
  min-width: 0;
  border: 1px solid var(--card-edge);
  border-radius: 10px;
}
.ask-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.ask-table th {
  position: sticky;
  top: 0;
  text-align: left;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule-firm);
  background: var(--glass-sunk);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
  white-space: nowrap;
}
.ask-table td {
  padding: calc(var(--step) * 1.75) calc(var(--step) * 2);
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
  max-width: 24rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ask-table tr:last-child td { border-bottom: 0; }
.ask-table tr:hover td { background: var(--hover); }
/* Figures are monospaced and right-aligned for the same reason they are everywhere else here:
   these are numbers somebody compares against another screen, digit by digit. */
.ask-table .is-num {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ask-table th.is-num { text-align: right; }
/* NetSuite omits a null column from a row rather than returning it as null, so an empty cell
   is the normal case and needs to read as empty rather than as missing. */
.ask-table .is-empty { color: var(--ink-faint); }

.ask-sql-wrap { font-size: 12.5px; }
.ask-sql-wrap summary {
  cursor: pointer;
  color: var(--label);
  font-weight: 600;
}
.ask-sql-wrap summary:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.ask-sql {
  margin: calc(var(--step) * 2) 0 0;
  padding: calc(var(--step) * 3);
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--navy-tint);
  border-radius: 10px;
  /* Wrapped rather than scrolled: this is meant to be read and pasted into NetSuite, and a
     query that needs horizontal scrolling to read is one nobody checks. */
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
}

.ask-history { display: grid; gap: calc(var(--step) * 2); }
.ask-history-title {
  margin: 0;
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
}
.ask-history-list { display: grid; gap: calc(var(--step) * 1.5); }
.ask-past {
  padding: calc(var(--step) * 2) calc(var(--step) * 3);
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--card-edge);
  border-radius: 10px;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ask-past:hover { color: var(--ink); border-color: var(--navy); }
.ask-past:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

/* The panel owns the screen while it is up, like the dashboard does. */
body.app-ask .page-head,
body.app-ask #form,
body.app-ask .commit { display: none !important; }

@media (max-width: 720px) {
  .ask-form { flex-wrap: wrap; }
  .ask-send { width: 100%; }
  .ask-result { padding: calc(var(--step) * 4); }
  .ask-answer { font-size: 17px; }
  .ask-offer-float { width: 100%; }
}

/* ══════════════════════════════════════════════════════════════════════════
   SQL BUILDER — a form, and the script it writes
   ══════════════════════════════════════════════════════════════════════════
   Two cards down one column: what you are asking for, and what came out. Rule 2 lands
   harder here than anywhere else on the site — the whole output IS data, so the script is
   monospaced end to end and the panel is built around a wide block of it.

   THE SCRIPT SCROLLS SIDEWAYS RATHER THAN WRAPPING, and that is the one place this panel
   disagrees with Ask Anything's SQL block. That one wraps because a wrapped SELECT is still
   readable prose. This one is an indented script with aligned DECLAREs and a boxed comment
   header, and wrapping it folds every long line into the next column, which is how a
   carefully lined-up UPDATE turns into something nobody can check. It is meant to be read
   as a script and then copied, so it keeps its shape.

   RULE 1 IS SPENT ONCE, on .sqlb-verdict.is-live. Whether a script writes to production is
   an outcome, not chrome, and it is the one thing somebody must not be wrong about before
   they press F5. Everything else on the panel is navy. */

.sqlb {
  display: grid;
  gap: calc(var(--step) * 5);
  align-content: start;
  min-width: 0;
}

.sqlb-title { margin: 0; font-size: 1.35rem; font-weight: 650; letter-spacing: -.01em; }

/* ── which query ────────────────────────────────────────────────────────── */
.sqlb-pick { display: grid; gap: calc(var(--step) * 2); }
.sqlb-pick-label {
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--label);
}
.sqlb-pills { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 2); }
/* Chips rather than a segmented bar: the library grows, and a bar that has to hold nine
   names either shrinks the type or scrolls. Chips wrap onto a second row and stay the same
   size. Selected is a filled navy chip, which is exactly how the rail marks the open app. */
.sqlb-pill {
  appearance: none;
  padding: calc(var(--step) * 2) calc(var(--step) * 4);
  font: inherit;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  box-shadow: var(--card-lift);
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease, border-color .14s ease;
}
.sqlb-pill:hover { border-color: var(--navy); color: var(--navy); }
.sqlb-pill:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.sqlb-pill[aria-pressed="true"] {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--navy-ink);
}

/* ── the form ───────────────────────────────────────────────────────────── */
/* --glass-sunk, the near-opaque card, and NOT the raised box the other panels use for a
   section. The rule at the top of this file says why: at 66% a bright cloud behind the
   panel came through the script as a diagonal streak, which reads as dirt rather than as
   glass. These two cards hold nothing but identifiers — order numbers, an email, address
   ids — so they get the one surface on the page with no weather in it. */
.sqlb-form, .sqlb-out {
  display: grid;
  gap: calc(var(--step) * 4);
  padding: calc(var(--step) * 5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
}
.sqlb-blurb { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-soft); }
.sqlb-fields { display: grid; gap: calc(var(--step) * 4); min-width: 0; }
/* The generic rule gives every .field a top margin when it follows another; inside this
   grid the gap already does that job. */
.sqlb-fields .field + .field { margin-top: 0; }
/* An email is prose-adjacent but it is still an identifier somebody reads back character by
   character, so it keeps the monospaced face the global input rule gives it. The list box
   too — these are order numbers. */
.sqlb-count {
  margin: 0;
  font-size: 12px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  min-height: 1.2em;   /* reserved, so the box below does not jump as you type */
}
.sqlb-choice { justify-self: start; }

.sqlb-actions { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 2.5); }

/* ── the script ─────────────────────────────────────────────────────────── */
.sqlb-out-head {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
  align-items: center;
  justify-content: space-between;
}
.sqlb-out-title { margin: 0; font-size: 1.05rem; font-weight: 650; }

.sqlb-verdict {
  margin: 0;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3.5);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft);
  background: var(--glass-sunk);
  border-left: 3px solid var(--label);
  border-radius: 4px var(--r-ctl) var(--r-ctl) 4px;
}
/* THE ONE OUTCOME COLOUR ON THIS PANEL. A script that will change production rows says so
   in the colour this site reserves for things that happened to real money. */
.sqlb-verdict.is-live {
  color: var(--charge);
  font-weight: 650;
  background:
    linear-gradient(var(--charge-dim), var(--charge-dim)),
    var(--glass-sunk);
  border-left-color: var(--charge);
}

.sqlb-sql {
  margin: 0;
  padding: calc(var(--step) * 3.5);
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--navy-tint);
  border-radius: 10px;
  /* SCROLLED, NOT WRAPPED — see the note at the top of this block. `pre` already does this;
     it is written out because it is a deliberate departure from .ask-sql. */
  white-space: pre;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* A script is read from the top and can be long. Capped and scrolled rather than pushing
     the notes under it off the panel entirely. */
  max-height: min(60vh, 40rem);
  overflow-y: auto;
  tab-size: 2;
}

.sqlb-notes {
  display: grid;
  gap: calc(var(--step) * 1.5);
  margin: 0;
  padding: calc(var(--step) * 3) 0 0 calc(var(--step) * 4);
  border-top: 1px solid var(--rule);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.sqlb-notes li::marker { color: var(--label); }

.sqlb-steps { font-size: 12.5px; }
.sqlb-steps summary { cursor: pointer; color: var(--label); font-weight: 600; }
.sqlb-steps summary:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.sqlb-steps-list { display: grid; gap: calc(var(--step) * 2); margin-top: calc(var(--step) * 3); }
.sqlb-step-title {
  margin: 0;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
}
.sqlb-sql-step { max-height: none; }

/* ── THE LIBRARY ──────────────────────────────────────────────────────────
   The shelf, and the door to it. Reuses .sqlb-form / .sqlb-out's card and .sqlb-sql's
   monospaced block wholesale, so a saved query and a generated one read as the same kind of
   thing. Only what the shelf adds is here. */

/* The heading row becomes two columns: the panel's name on the left, the way into the
   Library on the right. baseline rather than centre, so the button sits on the title's line
   rather than floating in the middle of a two-line lede. */
.sqlb-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--step) * 4);
}
.sqlb-head-text { display: grid; gap: calc(var(--step) * 1.5); min-width: 0; }
.sqlb-lib-btn { flex: 0 0 auto; }

.sqlb-lib { display: grid; gap: calc(var(--step) * 4); align-content: start; min-width: 0; }

.sqlb-lib-bar {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
  align-items: center;
  justify-content: space-between;
}
.sqlb-lib-back { justify-self: start; }

/* The add/edit form gets the same near-opaque card the builder's form has, and for the same
   reason: it holds a query somebody will read back character by character, and at 66% a
   bright cloud behind the panel comes through as a streak. */
.sqlb-lib-form {
  display: grid;
  gap: calc(var(--step) * 4);
  padding: calc(var(--step) * 5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
}
.sqlb-lib-form .field + .field { margin-top: 0; }
.sqlb-lib-form-title { margin: 0; font-size: 1.05rem; font-weight: 650; }
/* The query box is the one field here that is genuinely long, so it gets room and keeps the
   monospaced face the global input rule gives it. */
#sql-lib-sql { min-height: 11rem; line-height: 1.55; }

.sqlb-lib-list { display: grid; gap: calc(var(--step) * 4); min-width: 0; }
.sqlb-lib-empty {
  margin: 0;
  padding: calc(var(--step) * 5);
  font-size: 13.5px;
  color: var(--ink-soft);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
}

.sqlb-lib-card {
  display: grid;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
}
.sqlb-lib-card-head {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
  align-items: center;
  justify-content: space-between;
}
.sqlb-lib-name { margin: 0; font-size: 1rem; font-weight: 650; min-width: 0; }
.sqlb-lib-card-acts {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 3);
  flex: 0 0 auto;
}
/* Copy is the action a reader wants, so it is the chip; Edit and Delete are the author's
   housekeeping and stay as quiet links. */
.sqlb-lib-card-acts .btn:not(.btn-link) { padding: calc(var(--step) * 1.5) calc(var(--step) * 4); }
/* RULE 1 SPENT, and narrowly: red only once the press is armed. A Delete link that is
   already red is decoration on a button that has not done anything yet. */
.sqlb-lib-del[data-armed="1"] { color: var(--charge); font-weight: 650; }

.sqlb-lib-by {
  margin: 0;
  font-size: 11.5px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.sqlb-lib-purpose {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 46rem;
}
/* Shorter than a generated script's block: a shelf is for scanning, and a card that runs to
   sixty lines buries the next query. It scrolls inside itself, and Copy takes the whole
   thing whatever is on screen. */
.sqlb-lib-sql { max-height: 18rem; }

.sqlb-lib-note { margin: 0; font-size: 12.5px; color: var(--ink-soft); }
.sqlb-lib-note.is-bad { color: var(--charge); font-weight: 650; }

@media (max-width: 720px) {
  .sqlb-head { flex-direction: column; align-items: stretch; }
  .sqlb-lib-btn { width: 100%; }
  .sqlb-lib-form, .sqlb-lib-card { padding: calc(var(--step) * 4); }
}

/* The panel owns the screen while it is up, the way the dashboard and Ask Anything do:
   there is no request to commit here, and the footer's promise is about the payment bot. */
body.app-sql .page-head,
body.app-sql #form,
body.app-sql .commit { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════
   IT → ANNOUNCEMENTS — what the planes tow across the sky
   A form card and a list, in the SQL Builder's idiom. The preview is the sky's own
   .sky-banner held still over a strip of sky, so it cannot drift from what actually flies.
   ══════════════════════════════════════════════════════════════════════════ */
body.app-announce .page-head,
body.app-announce #form,
body.app-announce .commit { display: none !important; }

.ann { display: grid; gap: calc(var(--step) * 5); align-content: start; min-width: 0;
       max-width: 860px; }
.ann-head { display: grid; gap: calc(var(--step) * 1.5); }
.ann-title { margin: 0; font-size: 1.35rem; font-weight: 650; letter-spacing: -.01em; }
.ann-head .lede { margin: 0; }

.ann-form {
  display: grid;
  gap: calc(var(--step) * 4);
  padding: calc(var(--step) * 5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
}
.ann-form .field + .field { margin-top: 0; }
.ann-count { text-align: right; font-variant-numeric: tabular-nums; }
.ann-dates { display: grid; grid-template-columns: repeat(2, minmax(0, 220px));
             gap: calc(var(--step) * 4); }
/* The generic box rule names text-like inputs only; a date box gets the same body. */
.ann input[type="date"] {
  width: 100%;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  box-shadow: var(--card-lift);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
}
.ann input[type="date"]:focus { border-color: var(--navy); box-shadow: 0 0 0 3px var(--navy-tint);
                                outline: none; }
.ann-preview {
  overflow: hidden;
  padding: calc(var(--step) * 4) calc(var(--step) * 5);
  border-radius: var(--r-ctl);
  background: linear-gradient(180deg, #7f98b4, #b9c6d4 70%, #d8d4d0);
}
/* The editable banner: the sky's .sky-banner, larger, held still, and allowed to wrap on a
   narrow screen. pre-wrap so a typed space stays a space rather than becoming &nbsp;. */
.ann-editor {
  display: inline-flex;
  flex-wrap: wrap;
  min-width: min(100%, 16em);
  max-width: 100%;
  font-size: 22px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  cursor: text;
  animation: none;
  outline: none;
  border-radius: 2px;
}
.ann-editor:focus-visible, .ann-editor:focus { box-shadow: 0 0 0 3px var(--navy-tint); }
.ann-editor:empty::before { content: attr(data-placeholder); opacity: .45; }

/* The bar. Groups of small square buttons; a pressed one is navy. */
.ann-tools, .ann-look { display: flex; flex-wrap: wrap; align-items: center;
                        gap: calc(var(--step) * 2) calc(var(--step) * 3); }
.ann-tool-group { display: flex; flex-wrap: wrap; gap: 4px; }
.ann-tool {
  display: inline-grid;
  place-items: center;
  min-width: 32px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  font-size: 14px;
  cursor: pointer;
}
/* NOT on a swatch: a swatch's colour IS its fx-c-* class, and a colour here — same
   specificity, later in the file — painted every swatch the same ink. */
.ann-tool:not(.ann-swatch) { color: var(--ink); }
.ann-tool:hover { border-color: var(--navy); }
.ann-tool:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
/* Not on a swatch: its colour is its fx-c-* class, and this rule's ink — more specific —
   turned the chosen swatch white (2026-09-25 audit). Swatches mark "pressed" with a ring. */
.ann-tool:not(.ann-swatch):not(.ann-cloth)[aria-pressed="true"] {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--navy-ink);
}
.ann-tool-text { font-size: 13px; font-weight: 650; }
/* A colour swatch is the colour's own class painting currentColor, so the button cannot show
   a colour the banner would draw differently. */
.ann-swatch { width: 26px; min-width: 26px; height: 26px; padding: 0; border-radius: 50%;
              background: currentColor; border: 2px solid rgba(0, 0, 0, .12); }
.ann-swatch[aria-pressed="true"] { background: currentColor; border-color: var(--ink);
                                   box-shadow: 0 0 0 2px var(--navy-tint); }
.ann-swatch-auto { color: transparent;
                   background: linear-gradient(135deg, #f4f1eb 50%, #a3232b 50%); }
.ann-swatch-auto[aria-pressed="true"] {
  background: linear-gradient(135deg, #f4f1eb 50%, #a3232b 50%); }
.ann-cloth { width: 34px; min-width: 34px; height: 22px; padding: 0; background: var(--cloth);
             border: 2px solid rgba(0, 0, 0, .14); border-radius: 3px; }
.ann-cloth[aria-pressed="true"] { background: var(--cloth); border-color: var(--ink);
                                  box-shadow: 0 0 0 2px var(--navy-tint); }
.ann-look-label { font-size: 11.5px; font-weight: 650; letter-spacing: .11em;
                  text-transform: uppercase; color: var(--label); }
.ann-look .ann-count { margin-left: auto; }
.ann-count.is-over { color: var(--charge); font-weight: 650; }

/* A row's banner in the list: the real thing, small and still. */
.ann-row-banner { display: inline-flex; max-width: 100%; overflow: hidden; font-size: 13px;
                  animation: none; margin-bottom: 4px; }
.ann-actions { display: flex; gap: calc(var(--step) * 3); }
.ann-note { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
.ann-note.is-bad { color: var(--charge); font-weight: 650; }

.ann-listing { display: grid; gap: calc(var(--step) * 2.5); }
.ann-rate { margin: 0; font-size: 13.5px; font-weight: 650; color: var(--label); }
.ann-list { list-style: none; margin: 0; padding: 0; display: grid;
            gap: calc(var(--step) * 2); }
.ann-empty { font-size: 13.5px; color: var(--ink-soft); }
.ann-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: calc(var(--step) * 4);
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
}
.ann-row-text { min-width: 0; }
.ann-row-meta { margin: 0; font-size: 12.5px; color: var(--ink-soft); }
.ann-chip {
  padding: 2px calc(var(--step) * 2.5);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 650;
  white-space: nowrap;
  background: var(--navy-tint);
  color: var(--navy);
}
.ann-row.is-live .ann-chip { background: var(--navy); color: var(--navy-ink); }
.ann-row.is-ended { opacity: .6; }
.ann-row-actions { display: flex; gap: calc(var(--step) * 2); white-space: nowrap; }

@media (max-width: 720px) {
  .ann-form { padding: calc(var(--step) * 4); }
  /* Stacked: a native date box has a minimum width of its own, and two of them side by side
     pushed the second past the card's edge on a phone. */
  .ann-dates { grid-template-columns: minmax(0, 1fr); }
  .ann-row { grid-template-columns: minmax(0, 1fr) auto; }
  .ann-row-actions { grid-column: 1 / -1; }
}

@media (max-width: 720px) {
  .sqlb-form, .sqlb-out { padding: calc(var(--step) * 4); }
  .sqlb-actions .btn { flex: 1 1 auto; }
  .sqlb-sql { font-size: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
  /* A sweep frozen mid-travel reads as a hung page. Show a solid bar instead — the
     step is still marked as running, just without the movement. */
  .pstep-bar::after { width: 100%; animation: none !important; }
  .ask-sweep::after { width: 100%; animation: none !important; }

}

/* ═══════════════════════════════════════════════════════════════════════════
   THE SIGN-IN GATE — FORKED FROM THE DEPLOYED SITE. See docs/divergence.md.

   On Azure this screen is one button that goes to Microsoft, so it needed no styles of its
   own beyond .gate-inner. Signing in here means a form, and a form on this screen has one
   constraint the rest of the app does not: it sits on the photograph, in the glass card, at
   the one moment the person has no idea whether anything is working yet.

   So: the fields are the app's own fields (same tokens, same sunk glass, same focus ring),
   the steps are plain prose rather than a progress indicator, and NOTHING here is coloured
   except the error. Colour means money in this interface — see web/README.md — and a
   sign-in form is not money.
   ═══════════════════════════════════════════════════════════════════════════ */

.gate-form { margin-top: calc(var(--step) * 6); display: grid; gap: 0; }
.gate-form[hidden] { display: none; }

/* Reuses .field's shape rather than re-deriving it, so a change to the app's fields reaches
   this screen too. The label sits above the box in the same small navy caps as everywhere. */
.gate-field { display: grid; gap: calc(var(--step) * 1.5); align-content: start; }
.gate-field + .gate-field { margin-top: calc(var(--step) * 4); }
.gate-field > span {
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--label);
}

/* PASSWORD IS NOT IN THE APP'S OWN INPUT SELECTOR, because until now the app had no
   password field anywhere — the card number is a text input. Added here rather than to that
   list, so the fork stays confined to the gate. */
.gate-form input[type="password"],
.gate-form input[type="email"],
.gate-form input[type="text"] {
  width: 100%;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  box-shadow: var(--card-lift);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  transition: border-color .14s ease, box-shadow .14s ease, background-color .14s ease;
}

/* A 6-digit code is six characters wide and should look like it. Wide-tracked and centred,
   so a mistyped digit is obvious before the button is pressed. */
#gate-totp-code, #gate-enrol-code {
  max-width: 11rem;
  text-align: center;
  font-size: 1.35rem;
  letter-spacing: .38em;
  padding-left: calc(var(--step) * 2);
}

.gate-step {
  margin: 0 0 calc(var(--step) * 4);
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink-soft);
}
.gate-hint {
  margin: calc(var(--step) * 3) 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-faint);
}
.gate-hint a { color: var(--label); }

/* The enrolment key. Broken into groups of four by the script and laid out so it can be
   read down a line without losing the place — which is the whole reason there is no QR
   code here. Selectable, and wraps rather than scrolling. */
.gate-secret {
  margin: calc(var(--step) * 3) 0 0;
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.8;
  letter-spacing: .14em;
  word-spacing: .5em;
  overflow-wrap: anywhere;
  user-select: all;
}

/* The gate's own button margin already comes from `.gate-inner .btn`. Inside a form it needs
   saying again, because the form is the child now and the rule was written for a direct one. */
.gate-form .btn { margin-top: calc(var(--step) * 6); }

#gate-busy { margin-top: calc(var(--step) * 5); font-size: 14px; }

/* ══════════════════════════════════════════════════════════════════════════
   A STORE — orders, customers, products

   THE LAYOUT IS THE NOPCOMMERCE ADMIN'S, THE PALETTE IS THIS HUB'S. Every colour, radius and
   step below is a token defined at the top of this file, so these screens follow the glass and
   the dark theme with the rest of the hub while standing in the same SHAPE as the 4.90.6 admin
   at dev.pisces2-0.com: a content header, a collapsible search card, a grid with an Id column
   and a per-row Edit button, a page-length picker and a numbered pager, and one record as a
   stack of collapsible cards.

   NOT A SECOND DESIGN LANGUAGE. The card surface is .dash-card's, the type scale is the
   dashboard's, the buttons are .btn. The admin being mirrored already keeps the same
   discipline these tokens do — navy is structure, the failure red is the only red, green is a
   state rather than an action — which is why the two can look like one system rather than a
   skin over somebody else's.

   THE ONE ADDITION is a "waiting" colour for a status chip, which the hub had no token for: it
   only ever needed "failed" and "confirmed". Amber, matched to the review's own, and scoped
   here with the same two-theme treatment.
   ══════════════════════════════════════════════════════════════════════════ */
.store {
  height: 100%;
  min-height: 0;
  display: grid;
  /* THE CONSOLE'S OWN SIDEBAR, then the work. Two columns rather than the one this screen
     used to be: the sections are a property of the store console and belong beside it, not
     in the hub's rail, which is now collapsed to its icons for this app. */
  grid-template-columns: 13.5rem minmax(0, 1fr);
  gap: calc(var(--step) * 3);

  --st-wait: #8A5A00;
  --st-wait-dim: rgba(138, 90, 0, .16);
  /* The label column of every label-and-control row — the admin's col-md-4 beside its
     col-md-8. One token because the search card and the record cards must agree, and they
     drifted by 2rem when it was written twice. */
  --st-label-col: 11.5rem;
}
:root[data-theme="dark"] .store { --st-wait: #E8B84B; }

/* Both views scroll inside themselves; the panel does not. */
body.app-store .screen-body {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
body.app-store .page-head { display: none; }
/* AND THE CAPTURE FORM'S ACTION BAR, which is pinned to the bottom of the panel and is not
   inside #form. Hiding #form left "Capture 0 orders" sitting across the foot of the store
   screens, over the order notes — the same reason app-dash, app-ask, app-oe and app-sql all
   have this line. Found by screenshotting the screens rather than by reading the markup. */
body.app-store .commit { display: none !important; }

.st-list,
.st-record {
  display: grid;
  gap: calc(var(--step) * 4);
  align-content: start;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(var(--pad-y) * 1.2) var(--pad-x) var(--pad-y);
}

/* ── the content header ────────────────────────────────────────────────────
   Title and lede on the left, buttons on the right, wrapping to two rows when narrow —
   `content-header clearfix` with a float-left h1 and a float-right button group. */
.st-head {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  justify-content: space-between;
  gap: calc(var(--step) * 3);
}
.st-head-titles { min-width: 0; }
.st-head h2 {
  margin: 0 0 calc(var(--step) * 1);
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -.015em;
}
.st-head-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 2);
}
/* Smaller than a form button: these sit in a header, not at the end of a decision. */
.st-head-actions .btn {
  padding: calc(var(--step) * 1.75) calc(var(--step) * 3.5);
  font-size: 13px;
}
.st-backlink {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-size: 12.5px;
  color: var(--label);
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.st-backlink:hover { color: var(--ink); }

/* ── a card ───────────────────────────────────────────────────────────────── */
.st-card {
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
  overflow: hidden;
}

/* ── the search card ──────────────────────────────────────────────────────
   The header row is the admin's `search-row`: the word Search, a magnifier, and a chevron
   that turns. The whole strip is the button, because a 4px chevron is not a click target. */
.st-search-head { margin: 0; }
.st-search-row {
  appearance: none;
  width: 100%;
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  color: var(--label);
  text-align: left;
}
.st-search-row:hover { background: var(--hover); }
.st-search-text {
  flex: 1 1 auto;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.st-search-glass svg,
.st-search-chev svg {
  width: 15px;
  height: 15px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.st-search-chev { transition: transform .16s ease; }
.st-search-row[aria-expanded="false"] .st-search-chev { transform: rotate(-90deg); }

.st-search-body {
  padding: 0 calc(var(--step) * 4) calc(var(--step) * 4);
  border-top: 1px solid var(--rule);
}
/* Two columns of filter rows where there is room, one when there is not — the admin's
   col-md-5 and col-md-7 beside each other. */
.st-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: calc(var(--step) * 2) calc(var(--step) * 8);
  padding-top: calc(var(--step) * 4);
}
/* Centred, like `btn-search` under that form. */
.st-search-go {
  display: flex;
  justify-content: center;
  gap: calc(var(--step) * 2);
  margin-top: calc(var(--step) * 4);
}

/* ── a label-and-control row ──────────────────────────────────────────────
   `form-group row` with a label column and a control column. Stacks below 40rem, because a
   11.5rem label column beside a field on a phone leaves neither of them usable. */
.st-fgroup {
  display: grid;
  grid-template-columns: var(--st-label-col) minmax(0, 1fr);
  align-items: start;
  gap: calc(var(--step) * 2) calc(var(--step) * 3);
  padding: calc(var(--step) * 1.25) 0;
  min-width: 0;
}
.st-flabel {
  font-size: 12px;
  font-weight: 650;
  color: var(--label);
  padding-top: calc(var(--step) * 1.75);
  line-height: 1.35;
}
.st-req { color: var(--charge); margin-left: 2px; }
.st-fcontrol { min-width: 0; }
.st-fcontrol input[type="text"],
.st-fcontrol select,
.st-fcontrol textarea {
  width: 100%;
  box-sizing: border-box;
  padding: calc(var(--step) * 1.75) calc(var(--step) * 2.5);
  border: 1px solid var(--rule);
  border-radius: var(--r-ctl);
  background: var(--field-lift);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}
.st-fcontrol textarea { resize: vertical; min-height: 4.25rem; }
/* FLAT, because these are already inside a card. The hub gives every text input a
   `box-shadow: var(--card-lift)` (see the shared control rule near the top of this file),
   which is right for a field sitting on a glass panel and wrong for forty dense rows nested
   in a card that is already lifted: read-only facts came out looking like raised empty boxes.
   A border is enough at this density. Measured with getComputedStyle, not guessed. */
.st-fcontrol input[type="text"],
.st-fcontrol select,
.st-fcontrol textarea { box-shadow: none; }
.st-fcontrol input.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.st-fcontrol input[type="text"]:focus-visible,
.st-fcontrol select:focus-visible,
.st-fcontrol textarea:focus-visible {
  border-color: var(--rule-firm);
  outline: 2px solid var(--label);
  outline-offset: 1px;
}
/* A DISABLED FIELD MUST NOT LOOK BROKEN. Read-only facts — an order's total, a created date —
   are most of what the Orders record shows, so they read as printed values on the card rather
   than as boxes somebody has been locked out of. */
/* A DISABLED SELECT IS A READ-ONLY FACT TOO. This rule listed inputs and textareas only, so
   a read-only dropdown — an order's Store, a customer's registered store — kept its
   border and sat on the card as an empty-looking box while the value beside it read as
   printed text. Same treatment for all three. */
.st-fcontrol select:disabled,
.st-fcontrol input:disabled,
.st-fcontrol textarea:disabled {
  border-color: transparent;
  background: none;
  box-shadow: none;
  padding-left: 0;
  padding-right: 0;
  color: var(--ink-soft);
  opacity: 1;
  -webkit-text-fill-color: var(--ink-soft);
}
/* A value with no control behind it — the category and manufacturer names. Set to match a
   disabled field exactly, so the eye does not read it as a different kind of thing. */
.st-fstatic {
  margin: 0;
  padding: calc(var(--step) * 1.75) 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink-soft);
}
/* A SCROLLING LIST OF CHECKBOXES, for the customer's roles. Bounded in height because there
   are thirty-five of them and a card should not become a page; the ticked ones are what
   matters and they can be found by scrolling. */
.st-checks {
  max-height: 13rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--rule);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  padding: calc(var(--step) * 1.5);
}
.st-check-row {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 1) calc(var(--step) * 1.5);
  border-radius: calc(var(--r-ctl) - 4px);
  font-size: 13px;
  cursor: pointer;
}
.st-check-row:hover { background: var(--hover); }
.st-check-row input { width: 1rem; height: 1rem; accent-color: var(--label); margin: 0; }
.st-check-row input:disabled { cursor: default; }
.st-check-row:has(input:disabled) { cursor: default; color: var(--ink-soft); }

.st-check {
  width: 1.05rem;
  height: 1.05rem;
  margin: calc(var(--step) * 1.75) 0 0;
  accent-color: var(--label);
}
/* THE BOX THAT APPEARS UNDER THE COMPANY DROPDOWN when "add a new one" is picked. It sits
   below the list rather than replacing it, so the list is still there to change your mind
   with; the dropdown keeps holding the answer either way. */
.st-combo-new { margin-top: calc(var(--step) * 1.5); }

.st-fnote {
  margin: calc(var(--step) * 1.5) 0 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-faint);
}


/* ── the store console's sidebar ───────────────────────────────────────────
   Shaped like the admin it replaces — a column of sections, the current one lit — but built
   from this hub's own tokens, so it follows the glass and the dark theme rather than
   importing somebody else's chrome. */
.st-nav {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: calc(var(--step) * 2);
  min-height: 0;
  padding: calc(var(--step) * 3) calc(var(--step) * 2.5);
  border-radius: var(--r-card);
  background: var(--glass-chrome);
  border: 1px solid var(--rule);
}
.st-nav-store { display: grid; gap: calc(var(--step) * 1.25); }
.st-nav-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-left: calc(var(--step) * 1.5);
}
.st-store-pick {
  width: 100%;
  box-sizing: border-box;
  padding: calc(var(--step) * 1.75) calc(var(--step) * 2);
  border: 1px solid var(--rule);
  border-radius: var(--r-ctl);
  background: var(--field-lift);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  box-shadow: none;
}
.st-store-pick:focus-visible {
  border-color: var(--rule-firm);
  outline: 2px solid var(--label);
  outline-offset: 1px;
}

.st-nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  align-content: start;
  gap: 2px;
  overflow-y: auto;
  min-height: 0;
}
.st-nav-item {
  appearance: none;
  width: 100%;
  display: grid;
  grid-template-columns: 1.125rem minmax(0, 1fr);
  align-items: center;
  gap: calc(var(--step) * 2.5);
  padding: calc(var(--step) * 2.25) calc(var(--step) * 2);
  border: 0;
  border-radius: var(--r-ctl);
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease;
}
.st-nav-item:hover { background: var(--hover); color: var(--ink); }
.st-nav-item svg {
  width: 100%;
  height: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The section you are in. Filled rather than merely coloured, because this is the one thing
   on the screen that says where you are. */
.st-nav-item[aria-selected="true"] {
  /* The same navy-and-lift the rail gives its selected tab, so "where I am" looks the same
     in both menus rather than inventing a second idea of selection three inches apart. */
  background: var(--navy);
  color: var(--navy-ink);
  font-weight: 650;
  box-shadow: 0 1px 2px rgba(38, 26, 16, .18), 0 6px 14px -6px rgba(0, 54, 128, .5);
}
.st-nav-item[aria-selected="true"]:hover { background: var(--navy); color: var(--navy-ink); }
.st-nav-foot {
  margin: 0;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2) 0;
  border-top: 1px solid var(--rule);
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink-faint);
}

/* The work itself, in the second column. Its own grid, because the list and the record swap
   places inside it exactly as they did when this screen was one column. */
.st-main { display: grid; min-height: 0; min-width: 0; }

/* Narrow: the sidebar becomes a row across the top rather than a column, and the sections
   scroll sideways. A 13rem column plus a record card does not fit on a phone, and the first
   thing to give up is the one that is three words wide. */
@media (max-width: 61.999rem) {
  .store { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .st-nav { grid-template-rows: auto auto; }
  .st-nav-list {
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    overflow-x: auto;
    overflow-y: hidden;
  }
  .st-nav-foot { display: none; }
}

/* ── the grid ─────────────────────────────────────────────────────────────── */
.st-table-wrap { overflow-x: auto; min-width: 0; }
.st-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.st-table th {
  position: sticky;
  top: 0;
  text-align: left;
  padding: calc(var(--step) * 2) calc(var(--step) * 2.5);
  border-bottom: 1px solid var(--rule-firm);
  background: var(--glass-sunk);
  font-size: 10.5px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--label);
  white-space: nowrap;
}
.st-table td {
  padding: calc(var(--step) * 2) calc(var(--step) * 2.5);
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
}
.st-table tr:last-child td { border-bottom: 0; }
.st-table tbody tr:hover td { background: var(--hover); }
.st-table .is-right { text-align: right; }
.st-table .is-centre { text-align: center; }
.st-table .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* A long product name must not make the table wider than the panel. */
.st-table td:not(.mono) { max-width: 22rem; }
.st-edit {
  padding: calc(var(--step) * 1) calc(var(--step) * 2.5);
  font-size: 12px;
  border-radius: calc(var(--r-ctl) - 4px);
}
.st-empty {
  margin: 0;
  padding: calc(var(--step) * 4);
  font-size: 13px;
  color: var(--ink-soft);
}

/* A tick or a cross, which is what RenderBoolean draws in that admin's grids. */
.st-bool svg {
  width: 15px;
  height: 15px;
  display: inline-block;
  vertical-align: -2px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.st-bool.is-yes { color: var(--ok); }
.st-bool.is-no  { color: var(--ink-faint); }

/* ── a status chip ────────────────────────────────────────────────────────
   Tinted rather than filled: forty rows each carrying a solid colour block is a table nobody
   can read down. The word carries the colour and the tint only groups it. */
.st-chip {
  display: inline-block;
  padding: 1px calc(var(--step) * 1.75);
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 650;
  letter-spacing: .01em;
  white-space: nowrap;
}
.st-chip.is-good { color: var(--ok);        background: var(--ok-dim); }
.st-chip.is-wait { color: var(--st-wait);   background: var(--st-wait-dim); }
.st-chip.is-stop { color: var(--charge);    background: var(--charge-dim); }
.st-chip.is-none { color: var(--ink-faint); background: var(--hover); }

/* ── the grid footer ──────────────────────────────────────────────────────
   Page length on the left, the count in the middle, the pager on the right — the DataTables
   footer, in that order. */
.st-grid-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  border-top: 1px solid var(--rule);
}
.st-len {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  font-size: 12px;
  color: var(--ink-faint);
}
.st-len select {
  padding: calc(var(--step) * 1) calc(var(--step) * 2);
  border: 1px solid var(--rule);
  border-radius: calc(var(--r-ctl) - 4px);
  background: var(--field-lift);
  color: var(--ink);
  font: inherit;
  font-size: 12px;
}
.st-showing { font-size: 12px; color: var(--ink-faint); }
.st-pager { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.st-page {
  appearance: none;
  min-width: 2rem;
  padding: calc(var(--step) * 1) calc(var(--step) * 2);
  border: 1px solid var(--rule);
  background: var(--field);
  color: var(--ink-soft);
  border-radius: calc(var(--r-ctl) - 4px);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}
.st-page:hover:not(:disabled):not(.is-current) {
  background: var(--field-lift);
  color: var(--ink);
}
.st-page:disabled { opacity: .45; cursor: default; }
/* The page you are on is navy and filled: the one place in this footer that is not quiet. */
.st-page.is-current {
  background: var(--label);
  border-color: var(--label);
  color: #FFFFFF;
  cursor: default;
}
.st-page-gap { padding: 0 calc(var(--step) * 1); color: var(--ink-faint); font-size: 12px; }

.st-foot { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink-soft); }

/* ── one record: the card stack ───────────────────────────────────────────
   Each card is a heading that toggles and a body — `card card-secondary card-outline` with a
   `card-header with-border` and a `card-body`, the first open and the rest shut. */
.st-cards { display: grid; gap: calc(var(--step) * 3); min-width: 0; }
.st-rcard {
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  min-width: 0;
  overflow: hidden;
}
.st-rcard-head { margin: 0; }
.st-rcard-toggle {
  appearance: none;
  width: 100%;
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2.5);
  padding: calc(var(--step) * 3) calc(var(--step) * 4);
  border: 0;
  background: none;
  cursor: pointer;
  font: inherit;
  color: var(--label);
  text-align: left;
}
.st-rcard-toggle:hover { background: var(--hover); }
.st-rcard-icon svg,
.st-rcard-chev svg {
  width: 16px;
  height: 16px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.st-rcard-title {
  flex: 1 1 auto;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.st-rcard-chev { transition: transform .16s ease; color: var(--ink-faint); }
.st-rcard.is-shut .st-rcard-chev { transform: rotate(-90deg); }
.st-rcard-body {
  padding: 0 calc(var(--step) * 4) calc(var(--step) * 4);
  border-top: 1px solid var(--rule);
  min-width: 0;
}
.st-rcard.is-shut .st-rcard-body { display: none; }
/* A table inside a card body reaches the card's edges, like the grid does. */
.st-rcard-body > .st-table-wrap {
  margin: 0 calc(var(--step) * -4);
  padding-top: calc(var(--step) * 1);
}
.st-rcard-body .st-table th { background: var(--glass-sunk); }
.st-rcard-body .st-table th:first-child,
.st-rcard-body .st-table td:first-child { padding-left: calc(var(--step) * 4); }
.st-rcard-body .st-table th:last-child,
.st-rcard-body .st-table td:last-child { padding-right: calc(var(--step) * 4); }

/* TWO FIXED COLUMNS, not auto-fit. With auto-fit a card holding one field got a single
   full-width column, so "Stock quantity" came out as an 800px box for a number that is
   never more than four digits. The admin this mirrors puts each form-group inside a
   half-width column whatever else is in the card, so this does too. */
.st-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 calc(var(--step) * 8);
  padding-top: calc(var(--step) * 3);
}
/* A price, a quantity and a date do not need the width of a product name. */
.st-fcontrol input.st-narrow { max-width: 12rem; }
/* A long box takes the whole row: a short description in half a card is a box you cannot read
   your own sentence in. */
.st-fgroup:has(textarea) { grid-column: 1 / -1; }

.st-card-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 2);
  margin-top: calc(var(--step) * 3);
}
.st-note-add { padding-top: calc(var(--step) * 3); }

.st-msg { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-soft); }
.st-msg.is-good { color: var(--ok); font-weight: 600; }
.st-msg.is-bad  { color: var(--charge); font-weight: 600; }
.st-msg.is-warn { color: var(--st-wait); font-weight: 600; }

/* An armed Cancel or Mark as paid. It has already changed its own words into a question, so
   the colour only has to make clear that the next press is the one that counts. */
.btn.is-armed {
  background: var(--charge);
  border-color: var(--charge);
  color: #FFFFFF;
  font-weight: 700;
}

/* ── a record's header stays put ───────────────────────────────────────────
   THE SAVE BUTTON HAS TO BE REACHABLE FROM THE BOTTOM OF A 116-FIELD PRODUCT. The record
   scrolls inside its own panel, so the header sticks to the top of that scroller rather than
   the viewport: the title, the status chip, the unsaved count and both Save buttons stay on
   screen wherever you are in the stack. Without it, editing a field in "External systems"
   meant scrolling nineteen cards back up to save it. */
.st-record .st-head {
  position: sticky;
  top: calc(var(--pad-y) * -1.2);
  z-index: 3;
  margin: calc(var(--pad-y) * -1.2) calc(var(--pad-x) * -1) 0;
  padding: calc(var(--pad-y) * 1.2) var(--pad-x) calc(var(--step) * 3);
  background: var(--glass-chrome);
  backdrop-filter: var(--blur);
  border-bottom: 1px solid var(--rule);
}

/* How many fields are unsaved, beside the button that would save them. */
.st-dirty {
  padding: calc(var(--step) * 1) calc(var(--step) * 2.5);
  border-radius: 999px;
  background: var(--st-wait-dim);
  color: var(--st-wait);
  font-size: 12px;
  font-weight: 650;
  white-space: nowrap;
}

.st-record-tools {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: calc(var(--step) * 3);
}
.st-expand { display: flex; gap: calc(var(--step) * 2); }
.st-expand .btn {
  padding: calc(var(--step) * 1.25) calc(var(--step) * 3);
  font-size: 12px;
}

/* ── the card heading's two small marks ────────────────────────────────────
   The count says what a shut card is hiding; the dot says it holds something unsaved. */
.st-rcard-count {
  min-width: 1.5rem;
  padding: 0 calc(var(--step) * 1.25);
  border-radius: 999px;
  background: var(--hover);
  color: var(--ink-faint);
  font-size: 10.5px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.st-rcard-dirty {
  min-width: 1.35rem;
  padding: 0 calc(var(--step) * 1.25);
  border-radius: 999px;
  background: var(--st-wait);
  color: #FFFFFF;
  font-size: 10.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
/* A card with an unsaved change keeps a visible edge whether it is open or shut, so
   "Collapse all" cannot hide the fact that something in there was touched. */
.st-rcard.is-dirty {
  border-color: var(--st-wait);
  box-shadow: var(--card-lift), inset 3px 0 0 var(--st-wait);
}

/* An open card's heading is quieter than a shut one's: the content is the thing being read,
   and twenty identical loud headings is the noise this pass exists to remove. */
.st-rcard:not(.is-shut) > .st-rcard-head .st-rcard-toggle { color: var(--ink-faint); }
.st-rcard:not(.is-shut) > .st-rcard-head .st-rcard-title { color: var(--label); }

/* A field that wants the whole row even when it is a single line — a product name. */
.st-fgroup.is-wide { grid-column: 1 / -1; }

/* ── a product's pictures ─────────────────────────────────────────────────
   A row of thumbnails that wraps, each with its alt text and display order under it. The
   images come through /api/stores/<slug>/picture because the CSP is img-src 'self' and
   because the browser does not talk to the store. */
.st-pics {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: calc(var(--step) * 4);
  padding-top: calc(var(--step) * 3);
}
.st-pic {
  margin: 0;
  display: grid;
  gap: calc(var(--step) * 2);
  min-width: 0;
}
/* A FIXED BOX WITH THE IMAGE CONTAINED IN IT. Product photographs here are a mix of square
   product shots and wide lifestyle images; letting each set its own height made the row
   ragged and the page jump as they loaded. `contain` keeps the whole picture visible, which
   matters when the point of the thumbnail is to recognise which one it is. */
.st-pic a {
  display: block;
  aspect-ratio: 1 / 1;
  background: var(--glass-soft);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  overflow: hidden;
}
.st-pic img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.st-pic a:focus-visible { outline: 2px solid var(--label); outline-offset: 2px; }
.st-pic-meta { display: grid; gap: calc(var(--step) * 1.5); min-width: 0; }
.st-pic-meta input {
  width: 100%;
  box-sizing: border-box;
  padding: calc(var(--step) * 1.25) calc(var(--step) * 2);
  border: 1px solid var(--rule);
  border-radius: calc(var(--r-ctl) - 4px);
  background: var(--field-lift);
  box-shadow: none;
  color: var(--ink);
  font: inherit;
  font-size: 12px;
}
.st-pic-order { max-width: 5rem; }
.st-pic-actions { display: flex; gap: calc(var(--step) * 1.5); }

.st-pic-add { margin-top: calc(var(--step) * 4); }
/* THE FILE INPUT IS HIDDEN INSIDE ITS LABEL. A bare <input type="file"> cannot be styled to
   match the buttons beside it in any browser, and a label wrapping it is the one technique
   that keeps the keyboard and screen-reader behaviour of a real input. */
.st-file { position: relative; overflow: hidden; display: inline-block; cursor: pointer; }
.st-file input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* ── a product's attributes ─────────────────────────────────────────────── */
.st-attr {
  padding-top: calc(var(--step) * 3);
  min-width: 0;
}
.st-attr + .st-attr { border-top: 1px solid var(--rule); margin-top: calc(var(--step) * 3); }
.st-attr-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: calc(var(--step) * 2);
  margin-bottom: calc(var(--step) * 2);
}
.st-attr-name {
  margin: 0;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}
/* The remove button sits at the far end of the heading, away from the name it would remove. */
.st-attr-head .btn-danger { margin-left: auto; }

@media (max-width: 62rem) {
  .st-filters { grid-template-columns: minmax(0, 1fr); }
  .st-fields { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 40rem) {
  .st-list,
  .st-record { padding-left: calc(var(--step) * 4); padding-right: calc(var(--step) * 4); }
  /* The label column goes away rather than squeezing the field to nothing. */
  .st-fgroup { grid-template-columns: minmax(0, 1fr); }
  .st-flabel { padding-top: 0; }
  .st-grid-foot { justify-content: flex-start; }
  .st-rcard-body > .st-table-wrap { margin: 0; }
  .st-rcard-body .st-table th:first-child,
  .st-rcard-body .st-table td:first-child { padding-left: calc(var(--step) * 2.5); }
}
/* ── reading one document, on request ─────────────────────────────────────
   The offer sits inside #oe-doc-note, which is a `.dash-empty` paragraph — so the button
   needs to sit on a line of its own rather than inherit that paragraph's muted, centred
   treatment, and the progress line under it has to read as status rather than as more of
   the same sentence. */
.oe-read-now {
  display: inline-flex;
  margin-top: calc(var(--step) * 2);
}
.oe-read-progress {
  display: inline-block;
  margin-top: calc(var(--step) * 1.5);
  font-size: 12.5px;
  color: var(--ink-soft);
}

/* ── the order that was created ───────────────────────────────────────────
   The one thing on this panel that reports a success rather than a step, so it is the one
   thing given a ground of its own. Green, because everything else in this panel that is
   green is a stage that went right and this is all of them at once. */
.oe-placed {
  margin: calc(var(--step) * 2) 0 calc(var(--step) * 3);
  padding: calc(var(--step) * 3) calc(var(--step) * 3.5);
  border: 1px solid var(--ok-line, rgba(22, 130, 74, .28));
  border-radius: var(--r-card);
  background: var(--ok-wash, rgba(22, 130, 74, .07));
}
.oe-placed-head {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
}
.oe-placed-tick {
  display: grid;
  place-items: center;
  width: 1.35rem;
  height: 1.35rem;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--ok, #16824A);
  color: #FFFFFF;
  font-size: 12px;
}
/* THE NUMBER IS THE HEADLINE. It is what gets written on a note, read down a phone and
   searched for in the store, so it is the biggest thing in the box. */
.oe-placed-num { margin: calc(var(--step) * 2) 0 0; }
.oe-placed-num a {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -.01em;
  font-family: var(--mono);
}
.oe-placed-num a:not([href]) { color: var(--ink); text-decoration: none; cursor: default; }
.oe-placed-rpa {
  margin: calc(var(--step) * 1.5) 0 0;
  font-size: 12px;
  color: var(--ink-soft);
}
.oe-placed-rpa.is-bad { color: var(--bad, #A3261F); font-weight: 600; }

/* ══ RETURNS PORTAL (a mock) ═══════════════════════════════════════════════════════════
   Asked for on 2026-09-22 as a visual only. The shape is from the mockup Brad sent; the
   colours are this sheet's tokens, so the screen follows the theme like every other panel
   rather than being right in light mode and wrong in dark. */

.returns { display: block; }

/* THE LIST TAKES THE ROOM AND THE PANEL IS A SIDEBAR.
   The list was 1.55fr against 1fr, which made the right-hand panel a second column rather
   than a sidebar and left the left one stopping short of the bottom. It is now a fixed
   sidebar width and everything else is the list.

   FULL HEIGHT COMES FROM THE SCROLLING BODY, not from a viewport sum: .screen-body is the
   row that scrolls, so it becomes a flex column while this app is open and the panel takes
   what is left under the heading. A calc() against 100vh would be wrong the moment the
   heading wrapped to two lines. */
.rp {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 21rem;
  gap: calc(var(--step) * 3);
  align-items: stretch;
  height: 100%;
  min-height: 0;
}

body.app-returns .screen-body { display: flex; flex-direction: column; }
body.app-returns .screen-body > .returns { flex: 1 1 auto; min-height: 0; }
.returns { min-height: 0; }

/* The list fills its half and the ROWS scroll inside it, so the heading and the filters
   hold still and the card's bottom edge stays on the bottom of the panel. */
.rp-list { display: flex; flex-direction: column; min-height: 0; }
.rp-tablewrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* A long order scrolls in the sidebar rather than pushing its own total off the bottom. */
.rp-detail { overflow-y: auto; }

/* The two panes. Same fill, edge and lift the hub gives a card inside a panel. */
.rp-list,
.rp-detail {
  background: var(--paper);
  border: 1px solid var(--card-edge);
  border-radius: 18px;
  box-shadow: var(--card-lift);
  overflow: hidden;
}

.rp-list__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--step) * 2);
  padding: calc(var(--step) * 3) calc(var(--step) * 3) calc(var(--step) * 1.5);
}

.rp-title { margin: 0; font-size: 1.35rem; font-weight: 700; color: var(--ink); }

.rp-iconbar { display: flex; gap: calc(var(--step)); }

.rp-icon {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--card-edge);
  background: var(--surface);
  color: var(--ink-soft);
  position: relative;
}

.rp-icon svg {
  width: 1.05rem;
  height: 1.05rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* the unread dot on the bell */
.rp-icon--dot::after {
  content: "";
  position: absolute;
  top: .32rem;
  right: .42rem;
  width: .42rem;
  height: .42rem;
  border-radius: 50%;
  background: var(--label);
}

.rp-filters {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  flex-wrap: wrap;
  padding: 0 calc(var(--step) * 3) calc(var(--step) * 2);
}

.rp-spacer { flex: 1 1 auto; }

/* A dropdown that does not drop: the mock has no lists to choose from. */
.rp-select {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step));
  padding: .42rem .85rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: .88rem;
  color: var(--ink-soft);
  background: var(--field);
}

.rp-select svg {
  width: .85rem;
  height: .85rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.rp-btn { border-radius: 999px; font-size: .88rem; padding: .45rem 1.05rem; }

/* The mockup's green call to action. --ok is the hub's own "confirmed" green, so the two
   agree instead of introducing a second one. */
.rp-btn--go {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--state-ink);
}

.rp-btn--sm { padding: .3rem .8rem; font-size: .82rem; }

/* The mock's controls are all disabled, and the usual dimming would make the whole screen
   look broken rather than inert. They keep their colour and their cursor says no. */
.rp .btn:disabled,
.rp-icon:disabled {
  opacity: 1;
  cursor: not-allowed;
}

/* EVERY CONTROL HERE IS DISABLED, so the sheet's disabled styling would repaint the whole
   screen grey - including the one green call to action the mockup is built around, which
   would read as broken rather than as inert. These two put the colours back; the cursor and
   the missing handler are what say the screen does nothing. */
.rp .btn.rp-btn:disabled {
  background: var(--surface);
  border-color: var(--card-edge);
  color: var(--ink);
}

.rp .btn.rp-btn--go:disabled {
  background: var(--ok);
  border-color: var(--ok);
  color: var(--state-ink);
}

.rp-tablewrap { overflow-x: auto; }

.rp-table { width: 100%; border-collapse: collapse; font-size: .9rem; }

.rp-table th {
  text-align: left;
  font-weight: 600;
  font-size: .78rem;
  letter-spacing: .04em;
  color: var(--ink-faint);
  padding: calc(var(--step)) calc(var(--step) * 1.5);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}

.rp-table th:first-child,
.rp-table td:first-child { padding-left: calc(var(--step) * 3); }

.rp-table th:last-child,
.rp-table td:last-child { padding-right: calc(var(--step) * 3); }

.rp-table td {
  padding: calc(var(--step) * 1.35) calc(var(--step) * 1.5);
  border-bottom: 1px solid var(--card-edge);
  color: var(--ink);
  white-space: nowrap;
}

.rp-row { cursor: pointer; }
.rp-row:hover { background: var(--hover); }

/* The row the panel beside it is showing. A tint plus a bar, because a tint alone is the
   same signal as hover. */
.rp-row.is-open { background: var(--navy-tint); }
.rp-row.is-open td:first-child { box-shadow: inset 3px 0 0 var(--label); }

.rp-col-check { width: 1px; }
.rp-col-price { text-align: right; }
.rp-col-menu { width: 1px; }

.rp-mono { font-variant-numeric: tabular-nums; }
.rp-quiet { color: var(--ink-faint); }

/* the checkbox */
.rp-check {
  width: 1.15rem;
  height: 1.15rem;
  display: block;
  border-radius: 6px;
  border: 1.5px solid var(--rule-firm);
  background: transparent;
  cursor: pointer;
  position: relative;
}

.rp-check.is-on {
  background: var(--ok);
  border-color: var(--ok);
}

.rp-check.is-on::after {
  content: "";
  position: absolute;
  left: .33rem;
  top: .13rem;
  width: .3rem;
  height: .58rem;
  border: solid var(--state-ink);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* the header's "some of them" box */
.rp-check--some {
  background: var(--ink-soft);
  border-color: var(--ink-soft);
}

.rp-check--some::after {
  content: "";
  position: absolute;
  left: .2rem;
  right: .2rem;
  top: .48rem;
  height: 2px;
  background: var(--state-ink);
  border-radius: 1px;
}

.rp-dots {
  display: block;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--ink-faint);
  box-shadow: 0 -5px 0 var(--ink-faint), 0 5px 0 var(--ink-faint);
  margin: 0 auto;
}

/* the status pills */
.rp-pill {
  display: inline-block;
  padding: .2rem .7rem;
  border-radius: 999px;
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
}

.rp-pill--paid { background: rgba(4, 89, 63, .14); color: var(--ok); }
.rp-pill--done { background: var(--navy-tint); color: var(--label); }
.rp-pill--out { background: rgba(155, 90, 16, .16); color: #7A4708; }
.rp-pill--back { background: var(--charge-dim); color: var(--charge); }

/* ── the one order ─────────────────────────────────────────────────────────────── */
.rp-detail { padding: calc(var(--step) * 2.5); }

.rp-detail__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(var(--step) * 2);
}

.rp-detail__label { margin: 0; font-size: .8rem; color: var(--ink-faint); }

.rp-detail__id {
  margin: .1rem 0 0;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.rp-detail__right { text-align: right; }
.rp-detail__right .rp-quiet { margin: .35rem 0 0; font-size: .85rem; }

.rp-person {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  margin-top: calc(var(--step) * 2.5);
  padding: calc(var(--step) * 1.5);
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--card-edge);
}

.rp-avatar {
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--label);
  color: var(--navy-ink);
  font-weight: 700;
  font-size: .9rem;
}

.rp-person__body { min-width: 0; }
.rp-person__name { margin: 0 0 .4rem; font-weight: 600; color: var(--ink); }
.rp-person__acts { display: flex; gap: calc(var(--step)); }

.rp-section {
  margin: calc(var(--step) * 2.5) 0 calc(var(--step));
  text-align: center;
  font-size: .82rem;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--card-edge);
  padding-bottom: calc(var(--step));
}

.rp-items { list-style: none; margin: 0; padding: 0; }

.rp-item {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 1.2) 0;
}

/* A SQUARE WHERE THE PICTURE WOULD GO, and deliberately empty: this is a mock with no
   catalogue behind it, and a stock photograph of somebody else's drill would be the one
   thing on the screen pretending to be real. */
.rp-item__thumb {
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--card-edge);
}

.rp-item__body { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.rp-item__name { font-size: .88rem; color: var(--ink); }
.rp-item__qty { font-size: .78rem; color: var(--ink-faint); }

.rp-item__price {
  margin-left: auto;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.rp-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-top: calc(var(--step) * 2);
  padding-top: calc(var(--step) * 1.5);
  border-top: 1px solid var(--rule);
  color: var(--ink-faint);
}

.rp-total strong {
  font-size: 1.45rem;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.rp-detail__acts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* At sidebar width two buttons side by side clip their own labels. */
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: calc(var(--step) * 1.5);
  margin-top: calc(var(--step) * 2);
}

.rp-detail__acts .btn { justify-content: center; }

.rp-detail__link {
  margin: calc(var(--step) * 1.5) 0 0;
  text-align: center;
  font-size: .85rem;
  color: var(--ink-soft);
}

.rp-detail__link span { border-bottom: 1px solid var(--rule-firm); padding-bottom: 1px; }

/* One column once the two panes stop fitting side by side. The order being looked at goes
   FIRST there: on a narrow screen the detail is what somebody opened the row for. */
@media (max-width: 62rem) {
  .rp {
    grid-template-columns: minmax(0, 1fr);
    height: auto;
  }
  .rp-detail { order: -1; overflow: visible; }
  .rp-tablewrap { overflow-y: visible; }
  body.app-returns .screen-body { display: block; }
}
/* The badge that says this screen is not wired to anything. Quiet, but not hidden. */
.rp-mock {
  display: inline-block;
  margin-left: calc(var(--step) * 1.5);
  padding: .12rem .55rem;
  border-radius: 999px;
  border: 1px dashed var(--rule-firm);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  vertical-align: middle;
}

/* ══════════════════════════════════════════════════════════════════════════
   ACCOUNTING → INTERNAL SHIPMENTS
   The dashboard's cards, tiles and bars, the Ask table for the log, and one colour of its own:
   amber for "waiting on somebody". Rule 1 still holds — green and red are the two OUTCOMES,
   approved and rejected, and nothing else here is coloured like one.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
  --ship-wait: #8a5a00;
  --ship-wait-dim: rgba(138, 90, 0, .14);
  --ship-wait-ink: #FFFFFF;
}
:root[data-theme="dark"] {
  --ship-wait: #E8B84B;
  --ship-wait-dim: rgba(232, 184, 75, .16);
  --ship-wait-ink: #1C1400;
}

body.app-ship .page-head,
body.app-ship #form,
body.app-ship .commit { display: none !important; }

.ship { display: grid; gap: calc(var(--step) * 4); align-content: start; min-width: 0; }
.ship-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: calc(var(--step) * 3) calc(var(--step) * 6);
}
.ship-head-text { display: grid; gap: calc(var(--step) * 1.5); max-width: 46rem; }
.ship-title { margin: 0; font-size: 1.35rem; font-weight: 650; letter-spacing: -.01em; }
.ship-head .lede { margin: 0; }
.ship-sync { display: flex; flex-wrap: wrap; align-items: center; gap: calc(var(--step) * 3); }
.ship-sync-text { margin: 0; max-width: 28rem; font-size: 12.5px; color: var(--ink-soft); }

.ship-grid { display: grid; gap: calc(var(--step) * 4); }
/* Name and amount on one line, the bar the full width beneath — side by side, a half-width card
   left the bar about twenty pixels long. The dashboard's side column does the same. */
.ship-grid .bar-row { grid-template-columns: minmax(0, 1fr) auto; gap: calc(var(--step) * 1) calc(var(--step) * 2); }
.ship-grid .bar-value { grid-row: 1; grid-column: 2; }
.ship-grid .bar-track { grid-row: 2; grid-column: 1 / -1; }
@media (min-width: 60rem) {
  /* stretch: the two cards stand the same height side by side. */
  .ship-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
}

/* ── waiting on you ─────────────────────────────────────────────────────── */
.ship-todo {
  display: grid;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 4);
  border: 1px solid var(--card-edge);
  border-left: 3px solid var(--ship-wait);
  border-radius: 4px var(--r-card) var(--r-card) 4px;
  background: var(--glass-sunk);
  box-shadow: var(--card-lift);
}
.ship-todo-title { margin: 0; font-size: 14.5px; font-weight: 650; color: var(--ink); }
.ship-todo-list { display: grid; gap: calc(var(--step) * 3); }
.ship-todo-card {
  display: grid;
  gap: calc(var(--step) * 3);
  padding: calc(var(--step) * 3.5);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-soft);
}
.ship-todo-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--step) * 2);
}
.ship-summary { margin: 0; font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }
.ship-summary .is-empty { color: var(--ink-faint); font-weight: 500; }

/* ── the forms ──────────────────────────────────────────────────────────── */
.ship-form { display: grid; gap: calc(var(--step) * 3); max-width: 44rem; }
.ship-form .field + .field { margin-top: 0; }
/* Prose, not identifiers: the monospace the order box wants is wrong for a sentence. */
.ship-form select,
.ship-form textarea { font-family: var(--sans); font-size: 14px; }
.ship-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--step) * 2) calc(var(--step) * 3);
}
.ship-route { font-size: 12.5px; }
.ship-form-error { margin: 0; font-size: 13px; font-weight: 600; color: var(--charge); }

/* ── status chips ───────────────────────────────────────────────────────── */
.ship-chip {
  display: inline-block;
  padding: .1rem .55rem;
  border-radius: 999px;
  background: var(--hover);
  color: var(--ink-soft);
  font-size: 11.5px;
  font-weight: 650;
  white-space: nowrap;
}
.ship-chip.is-needs { background: var(--ship-wait-dim); color: var(--ship-wait); }
.ship-chip.is-pending { background: var(--navy-tint); color: var(--navy); }
.ship-chip.is-approved { background: var(--ok-dim); color: var(--ok); }
.ship-chip.is-rejected { background: var(--charge-dim); color: var(--charge); }
.ship-chip.is-voided { text-decoration: line-through; }

/* ── the log ────────────────────────────────────────────────────────────── */
.ship-controls { flex-wrap: wrap; justify-content: flex-end; }
.ship-search {
  width: 14rem;
  max-width: 100%;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 3);
  border: 1px solid var(--card-edge);
  border-radius: 999px;
  background: var(--glass-sunk);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 12.5px;
}
.ship-search:focus { border-color: var(--navy); box-shadow: 0 0 0 3px var(--navy-tint); outline: none; }
/* A container, so the open detail can be held to the VISIBLE width of the log rather than the
   full width of a table that scrolls sideways — otherwise its sentences run off the right. */
.ship-table-wrap { max-height: 70vh; overflow: auto; container-type: inline-size; }
/* Above the sticky detail, which is later in the document and would otherwise paint over. */
.ship-table th { z-index: 2; }
.ship-row { cursor: pointer; }
.ship-row:focus-visible { outline: 2px solid var(--navy); outline-offset: -2px; }
.ship-row.is-open td { background: var(--navy-tint); }
.ship-row.is-voided td:not(:last-child) { color: var(--ink-faint); }
.ship-table td.is-empty { color: var(--ink-faint); }
/* The detail is prose and a form, so the table's one-line cells let go of it. */
.ship-detail-row td,
.ship-detail-row:hover td {
  max-width: none;
  overflow: visible;
  white-space: normal;
  padding: calc(var(--step) * 4);
  background: var(--hover);
  cursor: default;
}
.ship-detail {
  position: sticky;
  left: calc(var(--step) * 4);
  display: grid;
  gap: calc(var(--step) * 3);
  max-width: min(48rem, calc(100cqw - var(--step) * 8));
}
.ship-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: calc(var(--step) * 1) calc(var(--step) * 4);
  margin: 0;
  font-size: 13px;
}
.ship-facts dt {
  padding-top: 2px;
  color: var(--label);
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .09em;
  text-transform: uppercase;
}
.ship-facts dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.ship-facts a { color: var(--navy); }
.ship-reason {
  display: grid;
  gap: calc(var(--step) * 1);
  padding: calc(var(--step) * 3);
  border-left: 3px solid var(--navy);
  border-radius: 4px var(--r-ctl) var(--r-ctl) 4px;
  background: var(--glass-sunk);
}
.ship-reason-cat { margin: 0; font-size: 13.5px; font-weight: 650; color: var(--ink); }
.ship-reason-note { margin: 0; font-size: 13.5px; color: var(--ink); white-space: pre-wrap; }
.ship-where { margin: 0; font-size: 13px; color: var(--ink-soft); }

/* ── label makers without an account ────────────────────────────────────── */
.ship-accounts-list { display: grid; gap: calc(var(--step) * 1); margin: 0; padding: 0; list-style: none; }
.ship-account {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--step) * 2) calc(var(--step) * 4);
  padding: calc(var(--step) * 2.5) 0;
  border-bottom: 1px solid var(--rule);
}
.ship-account:last-child { border-bottom: 0; }
.ship-account-who { display: grid; gap: 2px; font-size: 13px; }
.ship-account-made { flex-basis: 100%; margin: 0; font-size: 13px; color: var(--ink); }

/* ── shipped against a deleted IF ───────────────────────────────────────── */
.ship-review { white-space: normal; min-width: 16rem; }
.ship-review-form { display: flex; gap: calc(var(--step) * 2); align-items: center; }
.ship-review-form .ship-search { width: auto; flex: 1 1 12rem; }
.ship-table tr.is-reviewed td { color: var(--ink-faint); }

/* ── approver roles, in IT → Permissions ────────────────────────────────── */
.perm-roles-head {
  margin: 0;
  padding-bottom: calc(var(--step) * 1);
  border-bottom: 1px solid var(--rule);
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}

/* ── the count on the rail ──────────────────────────────────────────────────
   Drawn from data-count, so the tab's own words — what the rail search matches — are
   untouched. On the tab, and on the department name so it shows with Accounting shut. */
.tab[data-count]::after,
.dept-name[data-count]::after {
  content: attr(data-count);
  display: inline-block;
  min-width: 1.4em;
  margin-left: .5em;
  padding: 0 .4em;
  border-radius: 999px;
  background: var(--ship-wait);
  color: var(--ship-wait-ink);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.5;
  text-align: center;
  font-variant-numeric: tabular-nums;
  vertical-align: 1px;
}
.tab[aria-selected="true"][data-count]::after { background: var(--navy-ink); color: var(--navy); }

@media (max-width: 720px) {
  .ship-facts { grid-template-columns: minmax(0, 1fr); }
  .ship-facts dd { margin-bottom: calc(var(--step) * 1.5); }
  .ship-search { width: 100%; }
  .ship-controls { justify-content: flex-start; }
}

/* ══════════════════════════════════════════════════════════════════════════
   IT → ACTIVITY LOG
   The dashboard's cards and the Ask table. Green is "active" and red is an error — both are
   states of something real, which is what rule 1 keeps those two colours for.
   ══════════════════════════════════════════════════════════════════════════ */
body.app-act .page-head,
body.app-act #form,
body.app-act .commit { display: none !important; }

.act { display: grid; gap: calc(var(--step) * 4); align-content: start; min-width: 0; }
.act-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: calc(var(--step) * 3) calc(var(--step) * 6);
}
.act-head .lede { margin: calc(var(--step) * 1.5) 0 0; max-width: 46rem; }
.act-views button { display: inline-flex; align-items: center; gap: .4em; }
.act-count {
  display: inline-block;
  min-width: 1.4em;
  padding: 0 .35em;
  border-radius: 999px;
  background: var(--hover);
  font-size: 11px;
  line-height: 1.5;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.act-count.is-alert { background: var(--charge); color: var(--state-ink); }

.act-table td { vertical-align: middle; }
.act-sub { display: block; font-size: 11.5px; color: var(--ink-faint); overflow: hidden; text-overflow: ellipsis; }
.act-person { cursor: pointer; }
.act-person:focus-visible { outline: 2px solid var(--navy); outline-offset: -2px; }
.act-chip {
  display: inline-block;
  padding: .1rem .55rem;
  border-radius: 999px;
  background: var(--hover);
  color: var(--ink-soft);
  font-size: 11.5px;
  font-weight: 650;
  white-space: nowrap;
}
.act-chip.is-active { background: var(--ok-dim); color: var(--ok); }
.act-chip.is-idle { background: var(--ship-wait-dim); color: var(--ship-wait); }
.act-chip.is-server, .act-chip.is-job { background: var(--charge-dim); color: var(--charge); }
.act-chip.is-browser { background: var(--navy-tint); color: var(--navy); }

.act-h3 { margin: calc(var(--step) * 4) 0 calc(var(--step) * 2); font-size: 13px; font-weight: 650; color: var(--ink); }
.act-recent { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 2); margin: 0; padding: 0; list-style: none; }
.act-recent li {
  padding: calc(var(--step) * 1.5) calc(var(--step) * 3);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-soft);
  font-size: 13px;
  cursor: pointer;
}

.act-filters { flex-wrap: wrap; justify-content: flex-end; }
.act-day {
  padding: calc(var(--step) * 1.5) calc(var(--step) * 2.5);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  color: var(--ink);
  font-size: 12.5px;
}
.act-log-wrap { max-height: 70vh; overflow: auto; }
.act-path { max-width: 22rem; }
.act-row.is-action td:nth-child(3) { color: var(--ink); font-weight: 600; }
.act-row.is-refused td:last-child { color: var(--charge); }
.act-more { margin-top: calc(var(--step) * 3); }

.act-check {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-soft);
}
.act-error-list { display: grid; gap: calc(var(--step) * 3); }
.act-error {
  display: grid;
  gap: calc(var(--step) * 1.5);
  padding: calc(var(--step) * 3.5);
  border: 1px solid var(--card-edge);
  border-left: 3px solid var(--charge);
  border-radius: 4px var(--r-ctl) var(--r-ctl) 4px;
  background: var(--glass-soft);
}
.act-error.is-resolved { border-left-color: var(--rule-firm); opacity: .7; }
.act-error-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: calc(var(--step) * 2); }
.act-error-msg { font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }
.act-error-actions { display: flex; flex-wrap: wrap; gap: calc(var(--step) * 2); }
.act-stack {
  margin: 0;
  max-height: 22rem;
  overflow: auto;
  padding: calc(var(--step) * 3);
  border-radius: var(--r-ctl);
  background: var(--glass-sunk);
  font-family: var(--mono);
  font-size: 11.5px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 720px) {
  .act-filters { justify-content: flex-start; }
}

/* ══════════════════════════════════════════════════════════════════════════
   IT → SYSTEM STATUS
   A light per app. Green and red are the hub's two outcome colours (rule 1), and here they
   mean exactly that: running, or not. Grey is "the dashboard could not be asked".
   ══════════════════════════════════════════════════════════════════════════ */
body.app-status .page-head,
body.app-status #form,
body.app-status .commit { display: none !important; }

.status { display: grid; gap: calc(var(--step) * 5); align-content: start; min-width: 0; }
.status-head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: calc(var(--step) * 3) calc(var(--step) * 6);
}
.status-checked { margin: calc(var(--step) * 1.5) 0 0; font-size: 12.5px; color: var(--ink-soft); }
.status-head-right { display: flex; flex-wrap: wrap; align-items: center; gap: calc(var(--step) * 3); }
.status-summary {
  margin: 0;
  padding: calc(var(--step) * 1.5) calc(var(--step) * 3.5);
  border-radius: 999px;
  font-size: 13.5px;
  font-weight: 650;
  background: var(--hover);
  color: var(--ink-soft);
}
.status-summary.is-good { background: var(--ok-dim); color: var(--ok); }
.status-summary.is-bad { background: var(--charge-dim); color: var(--charge); }

.status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: calc(var(--step) * 3);
}
.status-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(var(--step) * 3.5);
  padding: calc(var(--step) * 4) calc(var(--step) * 4.5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
}
.status-light {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--ink-faint);
}
.status-card.is-up .status-light { background: #16A34A; box-shadow: 0 0 0 4px rgba(22, 163, 74, .18), 0 0 10px rgba(22, 163, 74, .55); }
.status-card.is-down .status-light { background: #DC2626; box-shadow: 0 0 0 4px rgba(220, 38, 38, .18), 0 0 10px rgba(220, 38, 38, .55); }
.status-card.is-down { border-color: color-mix(in srgb, var(--charge) 45%, transparent); }
.status-text { min-width: 0; }
.status-name { margin: 0; font-size: 14.5px; font-weight: 650; color: var(--ink); }
.status-detail { margin: 2px 0 0; font-size: 12.5px; color: var(--ink-soft); overflow-wrap: anywhere; }
.status-meta { margin: 2px 0 0; font-size: 11.5px; color: var(--ink-faint); overflow-wrap: anywhere; }
.status-word { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-faint); }
.status-card.is-up .status-word { color: var(--ok); }
.status-card.is-down .status-word { color: var(--charge); }
@media (prefers-reduced-motion: no-preference) {
  .status-card.is-down .status-light { animation: status-pulse 1.6s ease-in-out infinite; }
}
@keyframes status-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* The top row: five headed columns (VA, Innovations, Central, TMS, WMS), each heading a coloured
   square and a rule — the Fulfillment Analytics Overview's source columns — then a separator. */
.status-top {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: calc(var(--step) * 4);
}
.status-col { min-width: 0; display: flex; flex-direction: column; }
/* The logo card: logo on a white plate (the brands are drawn for white, in either theme), one
   short line, and the light with its word. */
.status-feature {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--step) * 2.5);
  padding: calc(var(--step) * 4);
  text-align: center;
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
  cursor: default;
}
.status-feature.is-down { border-color: color-mix(in srgb, var(--charge) 45%, transparent); }
.status-plate {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 76px;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3);
  background: #FFFFFF;
  border: 1px solid rgba(15, 30, 60, .08);
  border-radius: calc(var(--r-card) - 4px);
}
a.status-plate { transition: box-shadow .15s ease; }
a.status-plate:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--st-central) 25%, transparent); }
/* A logo that opens two pages: two invisible halves over it, each tinted and named on hover. */
.status-plate.is-split { position: relative; overflow: hidden; }
.status-half {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 3px;
  text-decoration: none;
  transition: background .15s ease;
}
.status-half:first-of-type { left: 0; }
.status-half:last-of-type { right: 0; }
.status-half:hover,
.status-half:focus-visible { background: color-mix(in srgb, var(--st-shiphawk) 12%, transparent); }
.status-half-tag {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  color: var(--st-shiphawk);
  opacity: 0;
  transition: opacity .15s ease;
}
.status-half:hover .status-half-tag,
.status-half:focus-visible .status-half-tag { opacity: 1; }
/* One height for every logo (each file is trimmed to its artwork), so they read the same size. */
.status-logo { height: 42px; width: auto; max-width: 88%; object-fit: contain; }
/* Two brands carry their own ground: Central's grey (its CENTRAL is drawn in white) and
   Innovation's blue, taken from their logos. */
.status-plate.is-central { background: #ADB6BD; }
.status-plate.is-innovations { background: #003680; }
.status-short { margin: 0; font-size: 12.5px; color: var(--ink-soft); overflow-wrap: anywhere; }
.status-state { display: flex; align-items: center; gap: 8px; margin: 0; }
.status-feature .status-light { width: 12px; height: 12px; }
.status-feature.is-up .status-light { background: #16A34A; box-shadow: 0 0 0 4px rgba(22, 163, 74, .18), 0 0 10px rgba(22, 163, 74, .55); }
.status-feature.is-down .status-light { background: #DC2626; box-shadow: 0 0 0 4px rgba(220, 38, 38, .18), 0 0 10px rgba(220, 38, 38, .55); }
.status-feature.is-up .status-word { color: var(--ok); }
.status-feature.is-down .status-word { color: var(--charge); }
@media (prefers-reduced-motion: no-preference) {
  .status-feature.is-down .status-light { animation: status-pulse 1.6s ease-in-out infinite; }
}
.status-colhead {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 calc(var(--step) * 3);
  padding-bottom: 7px;
  border-bottom: 2px solid currentColor;
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.status-colhead::before { content: ""; width: 11px; height: 11px; border-radius: 3px; background: currentColor; flex: none; }
.status-colhead.is-va { color: var(--st-va); }
.status-colhead.is-innovations { color: var(--st-innovations); }
.status-colhead.is-central { color: var(--st-central); }
.status-colhead.is-shiphawk { color: var(--st-shiphawk); }
.status-colhead.is-netsuite { color: var(--st-netsuite); }
.status-link { color: inherit; text-decoration: none; }
.status-link:hover { text-decoration: underline; }

/* The services strip: five thin cards heading Apps & jobs — light, icon, name over one line. */
.status-strip {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: calc(var(--step) * 4);
}
.status-svc {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 3);
  min-width: 0;
  padding: calc(var(--step) * 2.5) calc(var(--step) * 3.5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
}
.status-svc .status-light { width: 10px; height: 10px; flex: none; }
.status-svc.is-up .status-light { background: #16A34A; box-shadow: 0 0 0 3px rgba(22, 163, 74, .18), 0 0 8px rgba(22, 163, 74, .5); }
.status-svc.is-minor .status-light { background: #F59E0B; box-shadow: 0 0 0 3px rgba(245, 158, 11, .2), 0 0 8px rgba(245, 158, 11, .5); }
.status-svc.is-down .status-light { background: #DC2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, .18), 0 0 8px rgba(220, 38, 38, .5); }
.status-svc.is-idle .status-light { background: var(--ink-faint); }
.status-svc.is-down { border-color: color-mix(in srgb, var(--charge) 45%, transparent); }
.status-svc.is-minor { border-color: rgba(245, 158, 11, .5); }
@media (prefers-reduced-motion: no-preference) {
  .status-svc.is-down .status-light { animation: status-pulse 1.6s ease-in-out infinite; }
}
.status-svc-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex: none;
  padding: 3px;
  background: #FFFFFF;
  border: 1px solid rgba(15, 30, 60, .08);
  border-radius: 8px;
}
.status-svc-icon img { max-width: 100%; max-height: 100%; object-fit: contain; }
.status-svc-icon.is-emoji { font-size: 20px; line-height: 1; }
.status-svc-text { display: flex; flex-direction: column; min-width: 0; }
.status-svc-name {
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
a.status-svc-name:hover { text-decoration: underline; }
.status-svc-line {
  font-size: 11.5px;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.status-sep {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 4);
  margin-top: calc(var(--step) * 2);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.status-sep::before,
.status-sep::after { content: ""; flex: 1; height: 1px; background: var(--card-edge); }

@media (max-width: 1200px) {
  .status-top,
  .status-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  .status-top,
  .status-strip { grid-template-columns: minmax(0, 1fr); }
}

/* Permissions: the administrator makes the password (2026-10-02) — on Add, and per row with
   Set password; disabled people stay listed, marked, with Re-enable. */
.admin-must-change { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 500; letter-spacing: normal; text-transform: none; color: var(--ink-soft); white-space: nowrap; }
.admin-actions { white-space: normal; }
.admin-actions .btn { margin: 2px 4px 2px 0; }
.admin-pw-msg { font-size: 12.5px; font-weight: 600; color: var(--ink-soft); }
.admin-pw-msg.is-bad { color: var(--charge); }
.admin-pw-msg.is-good { color: var(--ok); }
.admin-pw { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 6px 0 10px; }
.admin-pw label:first-child { font-size: 12.5px; font-weight: 600; color: var(--ink-soft); }
.admin-pw input[type="text"] { min-width: 16rem; }
.admin-disabled-tag { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--charge); }
tr.is-disabled .admin-grant { opacity: .55; }

/* IT → TV Wall (2026-10-02): set the office TV's Did You Know? facts and Upcoming events. */
body.app-tvwall .page-head,
body.app-tvwall #form,
body.app-tvwall .commit { display: none !important; }
.tvw { display: grid; gap: calc(var(--step) * 4); align-content: start; min-width: 0; }
.tvw-seconds { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; font-size: 13.5px; color: var(--ink-soft); }
.tvw-seconds input { width: 6rem; }
.tvw-note { margin: 0; min-height: 1.2em; font-size: 13px; font-weight: 600; color: var(--ok); }
.tvw-note.is-bad { color: var(--charge); }
/* The TV links strip (2026-10-06): one row per open TV page. */
.tvw-links { display: grid; gap: 8px; }
.tvw-link { display: grid; grid-template-columns: 220px 1fr auto auto; gap: 12px; align-items: center; padding: 10px 14px;
  border: 1px solid var(--card-edge); border-radius: 12px; background: var(--glass-sunk); }
.tvw-link b { color: var(--ink); font-size: 14px; }
.tvw-link span { color: var(--ink-faint); font-size: 12.5px; min-width: 0; }
.tvw-link a.btn { text-decoration: none; }
@media (max-width: 720px) { .tvw-link { grid-template-columns: 1fr auto auto; } .tvw-link span { grid-column: 1 / -1; grid-row: 2; } }
:root[data-contrast="high"] .tvw-link { background: #000; border-color: var(--hc-border); }
.tvw-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr)); gap: calc(var(--step) * 5); align-items: start; }
.tvw-col {
  min-width: 0;
  padding: calc(var(--step) * 5);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
}
.tvw-col h2 { margin: 0; font-size: 17px; font-weight: 700; color: var(--ink); }
.tvw-col > .hint { margin: 4px 0 12px; }
.tvw-form { display: grid; gap: 10px; margin-bottom: 14px; }
.tvw-form textarea { width: 100%; resize: vertical; font: inherit; }
.tvw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.tvw-grid .tvw-wide { grid-column: 1 / -1; }
.tvw-form-acts { display: flex; gap: 8px; align-items: center; }
.tvw-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.tvw-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--card-edge);
  border-radius: 12px;
  background: var(--hover);
}
.tvw-item.is-paused, .tvw-item.is-past { opacity: .6; }
.tvw-text { min-width: 0; }
.tvw-body { margin: 0; font-size: 14px; color: var(--ink); overflow-wrap: anywhere; }
.tvw-meta { margin: 3px 0 0; font-size: 12px; color: var(--ink-soft); }
.tvw-onwall { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ok); }
.tvw-acts { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; flex: none; }
.tvw-arrow { min-width: 2rem; }
.tvw-empty { padding: 6px 2px; }
.tvw-past { margin-top: 12px; }
.tvw-past summary { cursor: pointer; font-size: 13px; color: var(--ink-soft); margin-bottom: 8px; }
.tvw-inline { margin: 0 0 12px; }
/* A plane banner, drawn on its own cloth (the sky's .sky-cloth-* colours). */
.tvw-flag {
  display: inline-block;
  margin: 0;
  padding: 2px 10px;
  border-left: 2px solid #3a3a3a;
  background: var(--cloth, #f4f1eb);
  color: var(--cloth-ink, #a3232b);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  font-size: 13px;
  overflow-wrap: anywhere;
}
.tvw-eom-now { margin: 4px 0 6px; padding: 12px 14px; border-radius: 12px; background: var(--hover); border: 1px solid var(--card-edge); }
.tvw-eom-label { margin: 0; font-size: 11.5px; font-weight: 800; letter-spacing: .14em; color: var(--ok); }
.tvw-eom-name { margin: 2px 0; font-size: 20px; font-weight: 800; color: var(--ink); }

/* IT → TV Wall as ONE box with four parts (2026-10-02): facts, events, plane banners, truck. */
.tvw-all {
  min-width: 0;
  padding: calc(var(--step) * 5) calc(var(--step) * 6);
  background: var(--glass-sunk);
  border: 1px solid var(--card-edge);
  border-radius: var(--r-card);
  box-shadow: var(--card-lift);
}
.tvw-all-title { margin: 0 0 calc(var(--step) * 4); font-size: 20px; font-weight: 800; color: var(--ink); }
.tvw-parts { display: grid; grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr)); gap: 0 calc(var(--step) * 8); }
.tvw-part { min-width: 0; padding: calc(var(--step) * 4) 0; border-top: 1px solid var(--card-edge); }
.tvw-part h2 { margin: 0; font-size: 17px; font-weight: 700; color: var(--ink); }
.tvw-part > .hint { margin: 4px 0 12px; }

/* Logistics → Warehouse Manager (2026-10-02) — reuses the TV Wall tab's .tvw-* layout. */
body.app-whmgr .page-head,
body.app-whmgr #form,
body.app-whmgr .commit { display: none !important; }
.whm-lta-now { margin: 4px 0 12px; font-size: 15px; font-weight: 700; color: var(--ok); }
.whm-sites { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: calc(var(--step) * 5); margin-top: 12px; }
.whm-site {
  display: grid; gap: 12px; padding: 14px 16px; border-radius: 14px;
  background: var(--hover); border: 1px solid var(--card-edge);
}
.whm-site-head h3 { margin: 0; font-size: 17px; font-weight: 800; color: var(--ink); }
.whm-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.whm-field { display: grid; gap: 4px; font-size: 12.5px; font-weight: 600; color: var(--ink-soft); }
.whm-field input { width: 100%; }
.whm-goals { display: grid; grid-template-columns: 1.3fr 1fr 1fr 1fr; gap: 6px 10px; align-items: center; }
.whm-colhead { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft); }
.whm-goal-label { font-size: 13.5px; font-weight: 650; color: var(--ink); }
.whm-site .btn { justify-self: start; }

/* ── Customer Service → CS Email (2026-10-05) ───────────────────────────────────────────────
   Outlook's shape: a top bar (search, modes), then folders · message list · reading pane ·
   tools, edge to edge in the hub's panel, each column scrolling on its own. Quiet surfaces and
   hairline rules; colour only where it means something — the selected email, a lock, a group,
   a status. */
body.app-csmail .page-head,
body.app-csmail #form,
body.app-csmail .commit { display: none !important; }
body.app-csmail .screen-body { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.csm { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; min-width: 0; }

/* top bar */
.csm-top {
  display: flex; align-items: center; gap: 16px; padding: 12px 18px; flex: none;
  border-bottom: 1px solid var(--rule);
}
.csm-brand { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
.csm-brand h1 { margin: 0; font-size: 18px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); white-space: nowrap; }
.csm-sync { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-soft); white-space: nowrap; cursor: default; }
.csm-sync-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); }
.csm-sync.is-ok .csm-sync-dot { background: #30a14e; box-shadow: 0 0 0 3px rgba(48, 161, 78, .18); }
.csm-sync.is-bad { color: var(--charge); font-weight: 600; }
.csm-sync.is-bad .csm-sync-dot { background: var(--charge); }
.csm-searchbox { flex: 1; display: flex; justify-content: center; min-width: 8rem; }
.csm-searchbox input {
  width: 100%; max-width: 34rem; height: 36px; padding: 0 14px 0 36px; border-radius: 999px; font: inherit; font-size: 14px;
  border: 1px solid var(--card-edge); background: var(--glass-sunk) no-repeat 13px 50% / 15px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a93a3' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
  color: var(--ink);
}
.csm-modes { display: flex; padding: 3px; gap: 2px; border-radius: 10px; background: var(--hover); border: 1px solid var(--card-edge); flex: none; }
.csm-mode {
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; padding: 6px 12px; border-radius: 7px;
  border: 0; background: transparent; color: var(--ink-soft); white-space: nowrap;
}
.csm-mode:hover { color: var(--ink); }
.csm-mode.is-on { background: var(--glass-sunk); color: var(--ink); box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }

/* the toast */
.csm-toast {
  position: absolute; left: 50%; bottom: 18px; z-index: 5; margin: 0; max-width: min(36rem, 90%);
  transform: translate(-50%, 12px); opacity: 0; pointer-events: none; transition: opacity .2s ease, transform .2s ease;
  padding: 10px 16px; border-radius: 10px; font-size: 13.5px; font-weight: 600;
  background: #1d2433; color: #fff; box-shadow: 0 10px 30px rgba(0, 0, 0, .25);
}
.csm-toast.is-on { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.csm-toast.is-bad { background: #b3261e; }

/* the four columns */
.csm-work {
  flex: 1; min-height: 0; display: grid;
  grid-template-columns: 13.5rem minmax(19rem, 24rem) minmax(0, 1fr);
}
.csm.has-side .csm-work { grid-template-columns: 13.5rem minmax(19rem, 24rem) minmax(0, 1fr) 21rem; }
.csm-work > * { min-height: 0; min-width: 0; overflow-y: auto; overscroll-behavior: contain; }
.csm-folders, .csm-listcol { border-right: 1px solid var(--rule); }
.csm-side { border-left: 1px solid var(--rule); }

/* folders */
.csm-folders { padding: 12px 8px 16px; }
.csm-folders-h { margin: 6px 10px 6px; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint); }
.csm-folders-h + .csm-folder-list { margin-bottom: 14px; }
.csm-folder-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.csm-folder {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 10px; border-radius: 8px; border: 0;
  background: transparent; font: inherit; font-size: 13.5px; color: var(--ink); cursor: pointer; text-align: left;
}
.csm-folder:hover { background: var(--hover); }
.csm-folder.is-on { background: var(--navy-tint); color: var(--navy-ink, var(--navy)); font-weight: 650; }
.csm-folder-ico { width: 1.1rem; text-align: center; font-size: 13px; opacity: .75; filter: grayscale(1); flex: none; }
.csm-folder-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csm-folder-count { font-size: 12px; font-weight: 700; color: var(--navy); }
.csm-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--grp, #0a84ff); flex: none; display: inline-block; }
.csm-folder .csm-dot { margin: 0 3px; }

/* the list */
.csm-list-head {
  position: sticky; top: 0; z-index: 1; display: flex; align-items: baseline; gap: 8px; padding: 14px 16px 10px;
  background: var(--glass-chrome); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur);
  border-bottom: 1px solid var(--rule);
}
.csm-list-head h2 { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink); }
.csm-list-count { font-size: 12.5px; color: var(--ink-soft); }
.csm-list { list-style: none; margin: 0; padding: 0; }
.csm-row { border-bottom: 1px solid var(--rule); }
.csm-row-btn {
  position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; width: 100%; padding: 12px 16px;
  border: 0; background: transparent; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.csm-row-btn:hover { background: var(--hover); }
/* the SELECTED email (is-open is a status — "needs a reply" — and must not look selected) */
.csm-row.is-selected .csm-row-btn { background: rgba(10, 132, 255, .09); }
.csm.is-dark .csm-row.is-selected .csm-row-btn { background: rgba(120, 170, 255, .14); }
.csm-row.is-selected .csm-row-btn::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--navy); }
.csm-row-main { display: grid; gap: 2px; min-width: 0; }
.csm-row-l1, .csm-row-l2 { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.csm-row-who { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* needs a reply: bold, the way an unread email is */
.csm-row.is-open .csm-row-who, .csm-row.is-open .csm-row-subject { font-weight: 700; }
.csm-row-time { flex: none; font-size: 12px; color: var(--ink-soft); }
.csm-row-subject { flex: 1; min-width: 0; font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csm-row-wait { flex: none; font-size: 11.5px; font-weight: 700; color: var(--charge); }
.csm-row-preview { font-size: 12.5px; line-height: 1.4; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csm-row-meta { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.csm-row.is-complete .csm-row-who, .csm-row.is-complete .csm-row-subject { font-weight: 500; color: var(--ink-soft); }
.csm-list-empty { display: grid; justify-items: center; gap: 8px; padding: 48px 20px; color: var(--ink-soft); font-size: 13.5px; }

/* avatars and chips */
.csm-av {
  display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: 50%; flex: none;
  background: var(--av, #5b7ab0); color: #fff; font-size: 13px; font-weight: 700; letter-spacing: .02em;
}
.csm-av.is-small { width: 28px; height: 28px; font-size: 11px; }
.csm-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border-radius: 6px; white-space: nowrap;
  font-size: 11.5px; font-weight: 600; line-height: 1.6; background: var(--hover); color: var(--ink-soft);
}
.csm-chip-lock { background: rgba(255, 149, 0, .15); color: #a24a00; }
.csm-chip-lock.is-mine { background: rgba(48, 161, 78, .14); color: #1f7a3a; }
.csm-chip-grp::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--grp, #0a84ff); }
.csm-chip-po { font-family: var(--mono); font-weight: 600; }
.csm-chip-quiet { font-weight: 500; }
.csm-chip-out { background: rgba(10, 132, 255, .12); color: var(--navy); }
.csm-status.is-open { background: rgba(179, 38, 30, .1); color: var(--charge); }
.csm-status.is-waiting { background: var(--hover); color: var(--ink-soft); }
.csm-status.is-complete { background: rgba(48, 161, 78, .14); color: #1f7a3a; }
.csm.is-dark .csm-chip-lock { color: #ffb460; }
.csm.is-dark .csm-chip-lock.is-mine, .csm.is-dark .csm-status.is-complete { color: #6fd18c; }

/* reading pane */
.csm-reader { display: flex; flex-direction: column; overflow: hidden !important; }
.csm-empty { margin: auto; display: grid; justify-items: center; gap: 6px; padding: 40px; text-align: center; max-width: 26rem; }
.csm-empty-art { font-size: 40px; filter: grayscale(.2); opacity: .8; }
.csm-empty-big { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--ink); }
.csm-empty-sub { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-soft); }
.csm-open { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.csm-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px; padding: 8px 14px; flex: none;
  border-bottom: 1px solid var(--rule);
}
.csm-tb {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 11px; border-radius: 7px; border: 1px solid transparent;
  background: transparent; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--ink); cursor: pointer; white-space: nowrap;
}
.csm-tb:hover:not(:disabled) { background: var(--hover); }
.csm-tb:disabled { opacity: .45; cursor: default; }
.csm-tb-primary { background: var(--navy); color: #fff; }
.csm-tb-primary:hover:not(:disabled) { background: var(--navy); filter: brightness(1.1); }
.csm-tb.is-on:not(.csm-tb-primary) { background: var(--navy-tint); color: var(--navy); }
.csm-tb-icon { font-weight: 500; color: var(--ink-soft); }
.csm-tb-sep { width: 1px; height: 22px; background: var(--rule-firm); margin: 0 6px; }
.csm-tb-fill { flex: 1; }
.csm-tb-select { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-soft); padding: 0 4px; }
.csm-tb-select select {
  height: 30px; max-width: 11rem; padding: 0 26px 0 8px; border-radius: 7px; font: inherit; font-size: 13px;
  border: 1px solid var(--card-edge); background-color: var(--glass-sunk); color: var(--ink);
}
.csm-tb-select select:disabled { opacity: .8; }
.csm-lockbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding: 6px 18px; flex: none;
  font-size: 12.5px; font-weight: 600; border-bottom: 1px solid var(--rule);
}
.csm-lockbar .btn { padding: 0; min-height: 0; font-size: 12.5px; }
.csm-lockbar.is-mine { color: #1f7a3a; background: rgba(48, 161, 78, .08); }
.csm-lockbar.is-held { color: #a24a00; background: rgba(255, 149, 0, .1); }
.csm-lockbar.is-free { color: var(--ink-soft); }
.csm.is-dark .csm-lockbar.is-mine { color: #6fd18c; }
.csm.is-dark .csm-lockbar.is-held { color: #ffb460; }
.csm-reading { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 18px 26px 32px; }
.csm-reading > * { max-width: 60rem; }
.csm-subject-row { margin-bottom: 14px; }
.csm-subject-row h2 { margin: 0 0 8px; font-size: 20px; font-weight: 700; letter-spacing: -.01em; line-height: 1.3; color: var(--ink); overflow-wrap: anywhere; }
.csm-subject-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.csm-assign-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin-top: 12px; }
.csm-assign-field { display: inline-flex; align-items: center; gap: 8px; }
.csm-assign-k { font-size: 12.5px; color: var(--ink-soft); }
.csm-assign-field select {
  height: 30px; max-width: 13rem; padding: 0 26px 0 10px; border-radius: 7px; font: inherit; font-size: 13px;
  border: 1px solid var(--card-edge); background-color: var(--glass-sunk); color: var(--ink);
}
.csm-assign-field select:disabled { opacity: .85; }
.csm-messages { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.csm-msg { border-radius: 10px; border: 1px solid var(--card-edge); background: var(--glass-sunk); overflow: hidden; box-shadow: var(--card-lift); }
.csm-msg-head {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 12px; align-items: center; width: 100%; padding: 12px 16px;
  border: 0; background: transparent; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.csm-msg-head:hover { background: var(--hover); }
.csm-msg-who { min-width: 0; display: grid; gap: 1px; }
.csm-msg-l1 { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--ink); min-width: 0; }
.csm-msg-when { margin-left: auto; font-size: 12px; color: var(--ink-soft); white-space: nowrap; }
.csm-msg-l2 { font-size: 12.5px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csm-msg-status { margin: -4px 16px 8px 64px; font-size: 11.5px; font-weight: 600; color: var(--ink-faint); }
.csm-msg-status.is-done { color: #1f7a3a; }
.csm-attach { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 16px 10px 64px; }
.csm-attach-link {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 8px; font-size: 12.5px;
  border: 1px solid var(--card-edge); background: var(--hover); color: var(--ink); text-decoration: none;
}
.csm-attach-link:hover { border-color: var(--navy); }
.csm-attach-size { color: var(--ink-soft); }
.csm-msg-body { display: block; width: 100%; height: 160px; border: 0; border-top: 1px solid var(--rule); background: #fff; }

/* the reply, inline under the conversation */
.csm-compose { margin-top: 14px; border-radius: 10px; border: 1px solid var(--navy); background: var(--glass-sunk); box-shadow: 0 4px 18px rgba(0, 0, 0, .08); overflow: hidden; }
.csm-compose-line { display: flex; align-items: center; gap: 10px; padding: 0 16px; border-bottom: 1px solid var(--rule); }
.csm-compose-line label { width: 2rem; font-size: 13px; font-weight: 500; color: var(--ink-soft); flex: none;
  text-transform: none; letter-spacing: normal; margin: 0; }
.csm-compose-line input { flex: 1; min-width: 0; height: 38px; border: 0; background: transparent; box-shadow: none; padding: 0; font: inherit; font-size: 14px; color: var(--ink); outline: none; }
.csm-compose textarea {
  display: block; width: 100%; min-height: 12rem; padding: 14px 16px; border: 0; border-radius: 0; resize: vertical;
  background: transparent; box-shadow: none; font: inherit; font-size: 14.5px; line-height: 1.55; color: var(--ink); outline: none;
}
.csm-compose-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--rule); background: var(--hover); }
.csm-compose-foot .btn { min-height: 34px; padding: 0 14px; white-space: nowrap; font-size: 13.5px; }
.csm-compose-foot .btn-ghost { padding: 0 10px; }
.csm-compose-k { width: 2rem; font-size: 13px; color: var(--ink-soft); flex: none; }
.csm-compose-from { height: 38px; display: flex; align-items: center; font-size: 14px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csm-compose-line label, .csm-compose-k { width: 2.4rem; }
.csm-discard { border: 0; background: transparent; padding: 0 6px; font: inherit; font-size: 13.5px; color: var(--ink-soft); cursor: pointer; }
.csm-discard:hover { color: var(--charge); text-decoration: underline; }
.csm-history { margin-top: 20px; font-size: 13px; }
.csm-history summary { cursor: pointer; color: var(--ink-soft); font-weight: 600; }
.csm-events { list-style: none; margin: 10px 0 0; padding: 0 0 0 14px; border-left: 2px solid var(--rule); display: grid; gap: 6px; color: var(--ink-soft); }
.csm-ev-when { display: inline-block; min-width: 9.5rem; color: var(--ink-faint); }

/* tools */
.csm-side { display: flex; flex-direction: column; overflow: hidden !important; }
.csm-side-tabs { display: flex; gap: 2px; padding: 0 10px; border-bottom: 1px solid var(--rule); flex: none; }
.csm-tool-tab {
  position: relative; padding: 12px 8px 10px; border: 0; background: transparent; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); cursor: pointer; white-space: nowrap;
}
.csm-tool-tab:hover { color: var(--ink); }
.csm-tool-tab.is-on { color: var(--navy); }
.csm-tool-tab.is-on::after { content: ''; position: absolute; left: 6px; right: 6px; bottom: -1px; height: 2px; border-radius: 2px; background: var(--navy); }
.csm-side-close { margin-left: auto; align-self: center; width: 28px; height: 28px; border-radius: 7px; border: 0;
  background: transparent; color: var(--ink-soft); font: inherit; cursor: pointer; }
.csm-side-close:hover { background: var(--hover); color: var(--ink); }
.csm-badge { display: inline-block; min-width: 1.3em; padding: 0 5px; border-radius: 999px; background: var(--navy); color: #fff; font-size: 10.5px; line-height: 1.6; text-align: center; }
.csm-side-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; }
.csm-tool { display: grid; gap: 12px; align-content: start; }
.csm-tool-form { display: grid; gap: 8px; }
.csm-tool-form > label, .csm-tool-label { margin: 0; font-size: 13px; font-weight: 650; color: var(--ink);
  text-transform: none; letter-spacing: normal; }
.csm-tool-form .hint { font-weight: 500; }
.csm-tool textarea { width: 100%; resize: vertical; font: inherit; }
.csm-inline { display: flex; gap: 8px; align-items: center; }
.csm-inline input[type="text"] { flex: 1; min-width: 0; }
.csm-inline input[type="color"] { width: 2.6rem; height: 2.3rem; padding: 2px; flex: none; }
.csm-pos { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12px; }
.csm-pos:empty { display: none; }
.csm-po-chip { font: inherit; font-family: var(--mono); font-size: 12px; cursor: pointer; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--card-edge); background: var(--glass-sunk); color: var(--ink); }
.csm-po-chip:hover { border-color: var(--navy); color: var(--navy); }
.csm-tool-wait, .csm-tool-none { margin: 0; font-size: 13px; color: var(--ink-soft); }
.csm-tool-none.is-bad { color: var(--charge); }
.csm-track-out { display: grid; gap: 10px; }
.csm-order { padding: 12px; border-radius: 10px; border: 1px solid var(--card-edge); background: var(--glass-sunk); }
.csm-order-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 14px; color: var(--ink); }
.csm-order-sub { margin: 2px 0 0; font-size: 12.5px; color: var(--ink-soft); }
.csm-ship { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); display: grid; gap: 4px; }
.csm-ship-head { margin: 0; font-size: 12px; font-weight: 700; letter-spacing: .02em; color: var(--ink-soft); text-transform: uppercase; }
.csm-trk { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.csm-trk-num { font-family: var(--mono); font-size: 13.5px; font-weight: 700; color: var(--navy); }
.csm-copy { margin-left: auto; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--card-edge); background: transparent;
  font: inherit; font-size: 12px; font-weight: 600; color: var(--ink-soft); cursor: pointer; }
.csm-copy:hover { color: var(--ink); background: var(--hover); }
.csm-trk-items { margin: 0; font-size: 12px; color: var(--ink-soft); font-family: var(--mono); }
.csm-notes, .csm-tpl-pick { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.csm-note { padding: 10px 12px; border-radius: 10px; background: rgba(255, 204, 77, .16); border: 1px solid rgba(214, 160, 20, .35); }
.csm-note-body { margin: 0; font-size: 13.5px; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.csm-note-by { margin: 4px 0 0; font-size: 11.5px; color: var(--ink-soft); }
.csm-tpl-btn {
  display: grid; gap: 3px; width: 100%; padding: 10px 12px; text-align: left; font: inherit; cursor: pointer;
  border-radius: 10px; border: 1px solid var(--card-edge); background: var(--glass-sunk); color: var(--ink);
}
.csm-tpl-btn:hover { border-color: var(--navy); }
.csm-tpl-btn b { font-size: 13.5px; }
.csm-tpl-btn .csm-chip { justify-self: start; }
.csm-tpl-prev { font-size: 12px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Templates, Groups & rules, Statistics: scrolling pages of cards */
.csm-page { flex: 1; min-height: 0; overflow-y: auto; padding: 20px 22px 28px; display: grid; gap: 16px; align-content: start; }
.csm-page-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr)); gap: 16px; align-items: start; }
.csm-card { padding: 18px 20px; border-radius: 12px; border: 1px solid var(--card-edge); background: var(--glass-sunk); box-shadow: var(--card-lift); min-width: 0; }
.csm-card h2 { margin: 0 0 6px; font-size: 16px; font-weight: 700; color: var(--ink); }
.csm-card > .hint { margin: 0 0 14px; }
.csm-sub-h { margin: 20px 0 10px; font-size: 13px; font-weight: 700; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .06em; }
.csm-form { display: grid; gap: 12px; }
.csm-form .field + .field { margin-top: 0; }
.csm-form textarea { width: 100%; resize: vertical; font: inherit; }
.csm-form-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 12px 14px; }
.csm-form-acts { display: flex; gap: 8px; align-items: center; }
.csm-check { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--ink); cursor: pointer; }
.csm-rr { align-self: end; padding-bottom: 6px; }
.csm-addgroup { margin-bottom: 12px; }
.csm-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.csm-rowcard { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--card-edge); background: var(--hover); }
.csm-rowcard.is-off { opacity: .6; }
.csm-rowcard-text { min-width: 0; display: grid; gap: 4px; }
.csm-rowcard-h { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--ink); }
.csm-rowcard-sub { margin: 0; font-size: 12.5px; color: var(--ink-soft); }
.csm-rowcard-acts { display: flex; gap: 2px; flex: none; }
.csm-rowcard-acts .btn { min-height: 0; padding: 4px 6px; font-size: 13px; }
.csm-rule-n { display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--navy-tint); color: var(--navy); font-size: 11.5px; font-weight: 700; }
.csm-tpl-text { margin: 0; font-size: 12.5px; color: var(--ink-soft); white-space: pre-wrap; max-height: 6em; overflow: hidden; }
.csm-groups { display: grid; gap: 8px; }
.csm-group { border-radius: 10px; border: 1px solid var(--card-edge); background: var(--hover); }
.csm-group summary { display: flex; align-items: center; gap: 10px; cursor: pointer; padding: 10px 14px; font-size: 14px; color: var(--ink); }
.csm-group-n { margin-left: auto; font-size: 12.5px; color: var(--ink-soft); }
.csm-group-body { display: grid; gap: 12px; padding: 4px 14px 14px; }
.csm-people { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 6px 12px; max-height: 18rem; overflow-y: auto; }
.csm-arrow { min-width: 1.8rem; }
.csm-stats-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; font-size: 13.5px; }
.csm-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 12px; }
.csm-kpi { display: grid; gap: 2px; padding: 14px 16px; border-radius: 12px; background: var(--glass-sunk); border: 1px solid var(--card-edge); box-shadow: var(--card-lift); }
.csm-kpi b { font-size: 26px; font-weight: 700; letter-spacing: -.02em; color: var(--ink); }
.csm-kpi span { font-size: 12.5px; color: var(--ink-soft); }
.csm-table-wrap { overflow-x: auto; }
.csm-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.csm-table th { text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); padding: 8px; border-bottom: 1px solid var(--rule-firm); white-space: nowrap; }
.csm-table td { padding: 9px 8px; border-bottom: 1px solid var(--rule); color: var(--ink); white-space: nowrap; }

/* narrower screens: the folders thin out, then the tools float over the email, then one column at a time */
@media (max-width: 1899px) {
  .csm-work, .csm.has-side .csm-work { grid-template-columns: 12.5rem minmax(18rem, 23rem) minmax(0, 1fr); }
  /* the tools slide over the email as a drawer, solid so what is under it does not show through */
  .csm-work { position: relative; }
  .csm-side { position: absolute; right: 0; top: 0; bottom: 0; width: min(22rem, 92%); z-index: 4;
    background: var(--paper, #fff); box-shadow: -14px 0 34px rgba(0, 0, 0, .18); }
  /* nothing to look things up for until an email is open */
  .csm:not(.has-open) .csm-side { display: none; }
}
@media (max-width: 1100px) {
  .csm-work, .csm.has-side .csm-work { grid-template-columns: minmax(16rem, 20rem) minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  /* the folders become a strip across the top */
  .csm-folders { grid-column: 1 / -1; display: flex; gap: 2px; padding: 6px 8px; overflow-x: auto; overflow-y: hidden;
    border-right: 0; border-bottom: 1px solid var(--rule); }
  .csm-folders-h { display: none; }
  .csm-folder-list, .csm-folders-h + .csm-folder-list { display: flex; gap: 2px; margin: 0; }
  .csm-folder { width: auto; white-space: nowrap; }
  .csm-top { flex-wrap: wrap; }
  .csm-searchbox { order: 3; flex-basis: 100%; }
}
@media (max-width: 760px) {
  .csm-work, .csm.has-side .csm-work { grid-template-columns: minmax(0, 1fr); }
  .csm.has-open .csm-listcol { display: none; }
  .csm:not(.has-open) .csm-reader { display: none; }
  .csm-tb-select span { display: none; }
}

/* ── CS Email: the polish layer (Brad, 2026-10-05: "the ui/color theme needs some refinement so
   it is more polished looking and easier to read") ──────────────────────────────────────────
   A mail client is read for hours, so CS Email gets SOLID, quiet surfaces of its own instead of
   the hub's glass: the photograph no longer shows through at a different strength in every
   column. Three neutral greys tell the columns apart — folders darkest, the list and tools
   white, the reading pane between — with hairline rules, near-black ink, one readable blue for
   what is selected or actionable, and red only for "a customer is waiting". Everything below
   overrides the first pass above; the layout is unchanged. */
.csm {
  --c-folders: #eef1f5;
  --c-surface: #ffffff;
  --c-canvas: #f6f7f9;
  --c-line: #e2e6ec;
  --c-line-2: #d3d9e1;
  --c-ink: #17202c;
  --c-ink-2: #536072;
  --c-ink-3: #8691a1;
  --c-blue: #0b5cd5;
  --c-btn: #0b5cd5;
  --c-blue-ink: #0a4fb6;
  --c-blue-soft: #e8f0fd;
  --c-sel: #e3edfc;
  --c-hover: #f1f4f8;
  --c-red: #c4362c;
  --c-red-soft: #fdecea;
  --c-green: #1e7b3c;
  --c-green-soft: #e7f5ec;
  --c-amber: #9a4b00;
  --c-amber-soft: #fff3e2;
  --c-chip: #eef1f5;
  background: var(--c-surface);
  color: var(--c-ink);
}
.csm.is-dark {
  --c-folders: #141a21;
  --c-surface: #1a2129;
  --c-canvas: #12171d;
  --c-line: #29323d;
  --c-line-2: #35404c;
  --c-ink: #e7ebf0;
  --c-ink-2: #a6b0bd;
  --c-ink-3: #7b8695;
  --c-blue: #5f9dff;
  --c-btn: #2f6fdc;
  --c-blue-ink: #8ab8ff;
  --c-blue-soft: rgba(95, 157, 255, .14);
  --c-sel: rgba(95, 157, 255, .17);
  --c-hover: rgba(255, 255, 255, .045);
  --c-red: #ff7b72;
  --c-red-soft: rgba(255, 123, 114, .13);
  --c-green: #6fd18c;
  --c-green-soft: rgba(111, 209, 140, .12);
  --c-amber: #ffb460;
  --c-amber-soft: rgba(255, 180, 96, .12);
  --c-chip: rgba(255, 255, 255, .07);
}

/* surfaces and rules */
.csm-top { background: var(--c-surface); border-bottom-color: var(--c-line); }
.csm-folders { background: var(--c-folders); border-right-color: var(--c-line); }
.csm-listcol { background: var(--c-surface); border-right-color: var(--c-line); }
.csm-reader { background: var(--c-canvas); }
.csm-side { background: var(--c-surface); border-left-color: var(--c-line); }
.csm-page { background: var(--c-canvas); }
.csm-brand h1, .csm-list-head h2, .csm-subject-row h2, .csm-card h2 { color: var(--c-ink); }
.csm .hint { color: var(--c-ink-3); }

/* thin, quiet scrollbars */
.csm * { scrollbar-width: thin; scrollbar-color: var(--c-line-2) transparent; }
.csm ::-webkit-scrollbar { width: 10px; height: 10px; }
.csm ::-webkit-scrollbar-thumb { background: var(--c-line-2); border-radius: 10px; border: 3px solid transparent; background-clip: content-box; }
.csm ::-webkit-scrollbar-track { background: transparent; }

/* top bar */
.csm-sync { color: var(--c-ink-3); }
.csm-sync.is-bad { color: var(--c-red); }
.csm-searchbox input { background-color: var(--c-canvas); border-color: var(--c-line); color: var(--c-ink); }
.csm-searchbox input:focus { background-color: var(--c-surface); border-color: var(--c-blue); outline: none; box-shadow: 0 0 0 3px var(--c-blue-soft); }
.csm-searchbox input::placeholder { color: var(--c-ink-3); }
.csm-modes { background: var(--c-canvas); border-color: var(--c-line); }
.csm-mode { color: var(--c-ink-2); }
.csm-mode:hover { color: var(--c-ink); }
.csm-mode.is-on { background: var(--c-surface); color: var(--c-ink); box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px var(--c-line); }

/* folders */
.csm-folders-h { color: var(--c-ink-3); }
.csm-folder { color: var(--c-ink); font-size: 13.5px; }
.csm-folder:hover { background: rgba(23, 32, 44, .05); }
.csm.is-dark .csm-folder:hover { background: rgba(255, 255, 255, .05); }
.csm-folder.is-on { background: var(--c-sel); color: var(--c-blue-ink); font-weight: 650; }
.csm-folder-ico { opacity: .6; }
.csm-folder.is-on .csm-folder-ico { opacity: 1; filter: none; }
.csm-folder-count { color: var(--c-ink-2); font-weight: 650; font-size: 12px; }
.csm-folder.is-on .csm-folder-count { color: var(--c-blue-ink); }

/* the list */
.csm-list-head { background: var(--c-surface); -webkit-backdrop-filter: none; backdrop-filter: none; border-bottom-color: var(--c-line); }
.csm-list-count { color: var(--c-ink-3); }
.csm-row { border-bottom-color: var(--c-line); }
.csm-row-btn { padding: 12px 16px 12px 14px; }
.csm-row-btn:hover { background: var(--c-hover); }
.csm-row.is-selected .csm-row-btn, .csm.is-dark .csm-row.is-selected .csm-row-btn { background: var(--c-sel); }
.csm-row.is-selected .csm-row-btn::before { background: var(--c-blue); }
.csm-row-who { color: var(--c-ink); font-size: 14px; }
.csm-row-subject { color: var(--c-ink); }
.csm-row-preview { color: var(--c-ink-2); }
.csm-row-time { color: var(--c-ink-3); }
.csm-row-wait { color: var(--c-red); font-weight: 650; font-size: 11.5px; }
.csm-row.is-complete .csm-row-who, .csm-row.is-complete .csm-row-subject { color: var(--c-ink-2); }
.csm-list-empty { color: var(--c-ink-2); }
.csm-av { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .06); }

/* chips: flat, quiet, and readable */
.csm-chip { background: var(--c-chip); color: var(--c-ink-2); border-radius: 5px; font-weight: 600; }
.csm-chip-po { color: var(--c-ink); }
.csm-chip-lock { background: var(--c-amber-soft); color: var(--c-amber); }
.csm-chip-lock.is-mine, .csm.is-dark .csm-chip-lock.is-mine { background: var(--c-green-soft); color: var(--c-green); }
.csm.is-dark .csm-chip-lock { color: var(--c-amber); }
.csm-chip-out { background: var(--c-blue-soft); color: var(--c-blue-ink); }
.csm-status.is-open { background: var(--c-red-soft); color: var(--c-red); }
.csm-status.is-waiting { background: var(--c-chip); color: var(--c-ink-2); }
.csm-status.is-complete, .csm.is-dark .csm-status.is-complete { background: var(--c-green-soft); color: var(--c-green); }

/* reading pane */
.csm-toolbar { background: var(--c-surface); border-bottom-color: var(--c-line); padding: 8px 16px; }
.csm-tb { color: var(--c-ink); font-weight: 600; }
.csm-tb:hover:not(:disabled) { background: var(--c-hover); }
.csm-tb-primary { background: var(--c-btn); color: #fff; padding: 0 14px; }
.csm-tb-primary:hover:not(:disabled) { background: var(--c-btn); filter: brightness(1.08); }
.csm-tb.is-on:not(.csm-tb-primary) { background: var(--c-blue-soft); color: var(--c-blue-ink); }
.csm-tb-icon { color: var(--c-ink-2); }
.csm-lockbar { border-bottom-color: var(--c-line); padding: 7px 20px; font-weight: 600; }
.csm-lockbar.is-mine, .csm.is-dark .csm-lockbar.is-mine { background: var(--c-green-soft); color: var(--c-green); }
.csm-lockbar.is-held, .csm.is-dark .csm-lockbar.is-held { background: var(--c-amber-soft); color: var(--c-amber); }
.csm-lockbar.is-free { background: var(--c-surface); color: var(--c-ink-2); }
.csm-lockbar .btn { color: inherit; text-decoration: underline; }
.csm-reading { padding: 22px 28px 36px; }
.csm-subject-row h2 { font-size: 21px; font-weight: 700; letter-spacing: -.015em; }
.csm-assign-k { color: var(--c-ink-2); }
.csm-assign-field select { background-color: var(--c-surface); border-color: var(--c-line-2); color: var(--c-ink); }
.csm-msg { background: var(--c-surface); border-color: var(--c-line); box-shadow: 0 1px 2px rgba(16, 24, 40, .05); border-radius: 12px; }
.csm-msg.is-out { border-color: var(--c-line); box-shadow: inset 3px 0 0 var(--c-blue), 0 1px 2px rgba(16, 24, 40, .05); }
.csm-msg-head:hover { background: var(--c-hover); }
.csm-msg-l1 { color: var(--c-ink); }
.csm-msg-l2, .csm-msg-when { color: var(--c-ink-3); }
.csm-msg-status { color: var(--c-ink-3); font-weight: 500; }
.csm-msg-status.is-done { color: var(--c-green); }
.csm-msg-body { border-top-color: var(--c-line); }
.csm-attach-link { background: var(--c-canvas); border-color: var(--c-line); color: var(--c-ink); }
.csm-compose { background: var(--c-surface); border-color: var(--c-line-2); border-radius: 12px; box-shadow: 0 6px 24px rgba(16, 24, 40, .08); }
.csm-compose:focus-within { border-color: var(--c-blue); box-shadow: 0 0 0 3px var(--c-blue-soft), 0 6px 24px rgba(16, 24, 40, .08); }
.csm-compose-line { border-bottom-color: var(--c-line); }
.csm-compose-line label, .csm-compose-k, .csm-compose-from { color: var(--c-ink-3); }
.csm-compose-line input, .csm-compose textarea { color: var(--c-ink); }
.csm-compose-foot { background: var(--c-canvas); border-top-color: var(--c-line); }
.csm-compose-foot .btn-primary { background: var(--c-btn); border-color: var(--c-btn); }
.csm-history summary, .csm-events { color: var(--c-ink-2); }
.csm-events { border-left-color: var(--c-line); }
.csm-ev-when { color: var(--c-ink-3); }
.csm-empty-big { color: var(--c-ink); }
.csm-empty-sub { color: var(--c-ink-2); }

/* tools */
.csm-side-tabs { border-bottom-color: var(--c-line); }
.csm-tool-tab { color: var(--c-ink-2); }
.csm-tool-tab:hover { color: var(--c-ink); }
.csm-tool-tab.is-on { color: var(--c-blue-ink); }
.csm-tool-tab.is-on::after { background: var(--c-blue); }
.csm-badge { background: var(--c-btn); }
.csm-side-close { color: var(--c-ink-3); }
.csm-side-close:hover { background: var(--c-hover); color: var(--c-ink); }
.csm-tool-form > label, .csm-tool-label { color: var(--c-ink); }
.csm-tool-wait, .csm-tool-none { color: var(--c-ink-2); }
.csm-tool-none.is-bad { color: var(--c-red); }
.csm-order { background: var(--c-surface); border-color: var(--c-line); border-radius: 12px; padding: 14px; box-shadow: 0 1px 2px rgba(16, 24, 40, .05); }
.csm-order-head { color: var(--c-ink); }
.csm-order-sub { color: var(--c-ink-2); }
.csm-ship { border-top-color: var(--c-line); gap: 6px; }
.csm-ship-head { color: var(--c-ink-3); font-size: 11.5px; }
/* a tracking row on one line: number, carrier, Copy */
.csm-trk { flex-wrap: nowrap; gap: 8px; }
.csm-trk-num { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--c-blue-ink); font-size: 13px; text-decoration: none; }
a.csm-trk-num:hover { text-decoration: underline; }
.csm-trk .csm-chip { flex: none; }
.csm-copy { flex: none; border-color: var(--c-line-2); color: var(--c-ink-2); padding: 1px 8px; }
.csm-copy:hover { color: var(--c-blue-ink); border-color: var(--c-blue); background: var(--c-blue-soft); }
.csm-trk-items { color: var(--c-ink-3); margin-top: -2px; }
.csm-po-chip { background: var(--c-surface); border-color: var(--c-line-2); color: var(--c-ink); }
.csm-po-chip:hover { border-color: var(--c-blue); color: var(--c-blue-ink); }
.csm-note { background: #fff8e1; border-color: #f0dc9a; }
.csm.is-dark .csm-note { background: rgba(255, 204, 77, .1); border-color: rgba(255, 204, 77, .25); }
.csm-note-body { color: var(--c-ink); }
.csm-note-by { color: var(--c-ink-3); }
.csm-tpl-btn { background: var(--c-surface); border-color: var(--c-line); color: var(--c-ink); }
.csm-tpl-btn:hover { border-color: var(--c-blue); background: var(--c-blue-soft); }
.csm-tpl-prev { color: var(--c-ink-3); }

/* the pages: Templates, Groups & rules, Statistics */
.csm-card, .csm-kpi { background: var(--c-surface); border-color: var(--c-line); box-shadow: 0 1px 2px rgba(16, 24, 40, .05); }
.csm-kpi b { color: var(--c-ink); }
.csm-kpi span, .csm-sub-h, .csm-rowcard-sub, .csm-group-n, .csm-tpl-text { color: var(--c-ink-2); }
.csm-rowcard, .csm-group { background: var(--c-canvas); border-color: var(--c-line); }
.csm-rowcard-h, .csm-group summary, .csm-check { color: var(--c-ink); }
.csm-rule-n { background: var(--c-blue-soft); color: var(--c-blue-ink); }
.csm-table th { color: var(--c-ink-3); border-bottom-color: var(--c-line-2); }
.csm-table td { color: var(--c-ink); border-bottom-color: var(--c-line); }

/* the inputs inside CS Email sit on its own surfaces */
.csm input[type="text"], .csm input[type="search"], .csm select, .csm textarea { color: var(--c-ink); }
.csm .csm-tool input[type="text"], .csm .csm-tool textarea, .csm .csm-card input[type="text"], .csm .csm-card textarea,
.csm .csm-card select, .csm .csm-addgroup input[type="text"], .csm .csm-group-body input[type="text"] {
  background: var(--c-surface); border: 1px solid var(--c-line-2); box-shadow: none;
}
.csm .csm-tool input[type="text"]:focus, .csm .csm-tool textarea:focus, .csm .csm-card input:focus,
.csm .csm-card textarea:focus, .csm .csm-card select:focus {
  border-color: var(--c-blue); box-shadow: 0 0 0 3px var(--c-blue-soft); outline: none;
}
.csm .btn-primary { background: var(--c-btn); border-color: var(--c-btn); color: #fff; }
.csm.is-dark .btn-primary, .csm.is-dark .btn-primary:hover:not(:disabled) { background: var(--c-btn); border-color: var(--c-btn); color: #fff; }
.csm-toast { background: #1b2330; }
.csm-toast.is-bad { background: var(--c-red); }
.csm.is-dark .csm-toast:not(.is-bad) { background: #2c3644; }

/* ── CS Email has its OWN light and dark (Brad, 2026-10-05: "i like this for a dark theme (and we
   should add that toggle) but also we need a default light theme version of this"). Light is the
   default whatever the hub is set to; the ☾ / ☀ button in the top bar switches it and is
   remembered per browser. The hub's own tokens are remapped inside CS Email, so its buttons,
   fields and labels follow CS Email's theme instead of the hub's. ── */
.csm {
  --ink: var(--c-ink); --ink-soft: var(--c-ink-2); --ink-faint: var(--c-ink-3); --label: var(--c-ink-2);
  --rule: var(--c-line); --rule-firm: var(--c-line-2); --edge: var(--c-line); --hover: var(--c-hover);
  --glass-sunk: var(--c-surface); --glass-chrome: var(--c-surface); --glass-soft: var(--c-canvas); --paper: var(--c-surface);
  --card-edge: var(--c-line); --card-lift: 0 1px 2px rgba(16, 24, 40, .05);
  --field: var(--c-surface); --field-lift: var(--c-surface); --menu-bg: var(--c-surface); --disabled: var(--c-chip);
  --navy: var(--c-btn); --navy-deep: #0947a6; --navy-tint: var(--c-blue-soft); --navy-ink: #ffffff;
  --charge: var(--c-red); --ok: var(--c-green);
  color-scheme: light;
}
.csm.is-dark { --navy-deep: #2560c4; color-scheme: dark; }

/* buttons, by id so they win over the hub's light AND dark button rules alike */
#csmail .btn { background: var(--c-surface); color: var(--c-blue-ink); border-color: var(--c-line-2); box-shadow: none; }
#csmail .btn:hover:not(:disabled) { background: var(--c-blue-soft); color: var(--c-blue-ink); border-color: var(--c-blue); box-shadow: none; }
#csmail .btn-primary, #csmail .btn-link { background: var(--c-btn); color: #fff; border-color: var(--c-btn); }
#csmail .btn-primary:hover:not(:disabled), #csmail .btn-link:hover:not(:disabled) { background: var(--navy-deep); border-color: var(--navy-deep); color: #fff; }
#csmail .btn-ghost { background: transparent; color: var(--c-ink-2); border-color: var(--c-line-2); }
#csmail .btn-ghost:hover:not(:disabled) { background: var(--c-hover); color: var(--c-ink); }
#csmail .btn:disabled { background: var(--c-chip); color: var(--c-ink-3); border-color: transparent; box-shadow: none; }
/* the quiet ones: words in the lock bar, and a list row's Edit / Remove / arrows */
#csmail .csm-lockbar .btn { background: transparent; border: 0; padding: 0; min-height: 0; color: inherit; text-decoration: underline; box-shadow: none; }
#csmail .csm-rowcard-acts .btn { background: transparent; border: 0; color: var(--c-blue-ink); box-shadow: none; border-radius: 6px; }
#csmail .csm-rowcard-acts .btn:hover:not(:disabled) { background: var(--c-blue-soft); }
#csmail .csm-group-body .csm-form-acts .btn-link { background: transparent; color: var(--c-red); border: 0; box-shadow: none; }

/* the theme toggle */
.csm-theme {
  width: 36px; height: 36px; flex: none; display: grid; place-items: center; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--c-line); background: var(--c-canvas); color: var(--c-ink-2); font: inherit; font-size: 16px;
}
.csm-theme:hover { color: var(--c-ink); background: var(--c-hover); }

/* ── Purchasing → Vendor Directory (2026-10-05) ─────────────────────────────────────────────
   The old page's layout — stats, toolbar, a sortable table, a detail panel from the right —
   in the hub's own tokens, on solid surfaces so a 583-row table stays easy to read. */
body.app-vendors .page-head,
body.app-vendors #form,
body.app-vendors .commit { display: none !important; }
.vd { display: grid; gap: 12px; align-content: start; min-width: 0; }
.vd-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; }
.vd-title { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.vd-title h1 { margin: 0; font-size: 1.35rem; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.vd-role { font-size: 11.5px; font-weight: 700; padding: 3px 10px; border-radius: 999px; background: var(--hover); color: var(--ink-soft); border: 1px solid var(--card-edge); }
.vd-role.is-editor { background: var(--navy); color: var(--navy-ink); border-color: var(--navy); }
.vd-sync { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-soft); }
.vd-sync-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); }
.vd-sync.is-ok .vd-sync-dot { background: #22c55e; }
.vd-sync.is-wait .vd-sync-dot { background: #f59e0b; }
.vd-sync.is-bad { color: var(--charge); }
.vd-sync.is-bad .vd-sync-dot { background: var(--charge); }
.vd-head-acts { display: flex; flex-wrap: wrap; gap: 8px; }

/* buttons of its own, so they read the same in either theme */
.vd-btn {
  display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 14px; border-radius: 8px; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 600; white-space: nowrap;
  border: 1px solid var(--card-edge); background: var(--glass-sunk); color: var(--ink);
}
.vd-btn:hover:not(:disabled) { border-color: var(--navy); }
.vd-btn:disabled { opacity: .55; cursor: default; }
.vd-btn-primary { background: var(--navy); border-color: var(--navy); color: var(--navy-ink); }
.vd-btn-primary:hover:not(:disabled) { filter: brightness(1.08); }
.vd-btn-danger { color: var(--charge); }
.vd-btn-danger:hover:not(:disabled) { border-color: var(--charge); }
.vd-icon {
  width: 30px; height: 30px; display: inline-grid; place-items: center; border-radius: 7px; cursor: pointer; font-size: 13px;
  border: 1px solid var(--card-edge); background: var(--glass-sunk); color: var(--ink-soft);
}
.vd-icon:hover { border-color: var(--navy); color: var(--ink); }
.vd-icon-del:hover { border-color: var(--charge); color: var(--charge); background: rgba(185, 28, 28, .08); }

/* stats — each one filters the table to it */
.vd-stats { display: flex; flex-wrap: wrap; gap: 8px; }
.vd-stat {
  display: grid; gap: 1px; min-width: 6.5rem; padding: 10px 14px; border-radius: 10px; text-align: left; font: inherit;
  background: var(--glass-sunk); border: 1px solid var(--card-edge); box-shadow: var(--card-lift); color: var(--ink);
}
.vd-stat.is-link { cursor: pointer; }
.vd-stat.is-link:hover { border-color: var(--navy); }
.vd-stat b { font-size: 21px; font-weight: 700; line-height: 1.1; }
.vd-stat span { font-size: 11.5px; color: var(--ink-soft); }

/* toolbar */
.vd-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 10px; border-radius: 12px;
  background: var(--glass-sunk); border: 1px solid var(--card-edge); box-shadow: var(--card-lift); }
.vd-search { flex: 1; min-width: 14rem; }
.vd-search input { width: 100%; font-family: inherit; font-size: 14px; }
.vd-filter { width: auto; min-width: 9rem; font-family: inherit; font-size: 13.5px; }
.vd-count { margin: 0; font-size: 12.5px; color: var(--ink-soft); }

/* the table */
.vd-table-wrap { overflow-x: auto; border-radius: 12px; background: var(--glass-sunk); border: 1px solid var(--card-edge); box-shadow: var(--card-lift); }
.vd-table { width: 100%; min-width: 760px; border-collapse: collapse; font-size: 13.5px; }
.vd-table th { position: sticky; top: 0; text-align: left; padding: 0; border-bottom: 1px solid var(--rule-firm); background: var(--glass-sunk); white-space: nowrap; }
.vd-sort {
  width: 100%; padding: 10px 12px; border: 0; background: transparent; font: inherit; font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); text-align: left; cursor: pointer;
}
.vd-sort:hover, th.is-sorted .vd-sort { color: var(--ink); }
.vd-arrow { opacity: .45; font-size: 10px; }
.vd-th-files { width: 48px; }
.vd-th-acts { width: 76px; }
.vd-table td { padding: 10px 12px; border-bottom: 1px solid var(--rule); vertical-align: top; color: var(--ink); }
.vd-table tbody tr { cursor: pointer; }
.vd-table tbody tr:hover td { background: var(--hover); }
.vd-table tbody tr.is-open td { background: var(--navy-tint); }
.vd-table tbody tr:focus-visible { outline: 2px solid var(--navy); outline-offset: -2px; }
.vd-vname { font-weight: 650; }
.vd-vacct { margin-top: 2px; font-size: 11.5px; color: var(--ink-soft); }
.vd-notes-cell { font-size: 12.5px; color: var(--ink-soft); max-width: 22rem; }
.vd-files-cell { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }
.vd-nowrap { white-space: nowrap; }
.vd-dash { color: var(--ink-faint); }
.vd-acts { white-space: nowrap; }
.vd-acts .vd-icon + .vd-icon { margin-left: 4px; }
.vd-empty { margin: 0; padding: 40px 16px; text-align: center; color: var(--ink-soft); font-size: 13.5px; }

/* badges, in the old page's colours */
.vd-badge { display: inline-block; padding: 2px 8px; border-radius: 5px; font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.vd-m-email { background: #eef4ff; color: #1d4ed8; }
.vd-m-online { background: #edfcf2; color: #166534; }
.vd-m-phone { background: #fef9ec; color: #92400e; }
.vd-ds.is-no { background: #fff0f0; color: #b91c1c; }
.vd-ds.is-dtp { background: #fef9ec; color: #92400e; }
.vd-ds.is-always { background: #edfcf2; color: #166534; }
:root[data-theme="dark"] .vd-m-email { background: rgba(96, 150, 255, .16); color: #9cc0ff; }
:root[data-theme="dark"] .vd-m-online, :root[data-theme="dark"] .vd-ds.is-always { background: rgba(80, 200, 120, .14); color: #7fdc9c; }
:root[data-theme="dark"] .vd-m-phone, :root[data-theme="dark"] .vd-ds.is-dtp { background: rgba(255, 180, 80, .14); color: #ffc477; }
:root[data-theme="dark"] .vd-ds.is-no { background: rgba(255, 110, 100, .14); color: #ff9a92; }

/* the detail panel */
.vd-scrim { position: fixed; inset: 0; z-index: 150; background: rgba(0, 0, 0, .28); opacity: 0; transition: opacity .2s ease; }
.vd-scrim.is-open { opacity: 1; }
.vd-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 160; width: 440px; max-width: 95vw; overflow-y: auto;
  padding: 20px 22px 28px; background: var(--paper, #fff); border-left: 1px solid var(--card-edge);
  box-shadow: -16px 0 40px rgba(0, 0, 0, .18); transform: translateX(100%); transition: transform .22s ease; color: var(--ink);
}
:root[data-theme="dark"] .vd-panel { background: #0f191d; }
.vd-panel.is-open { transform: translateX(0); }
.vd-panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.vd-panel-head h2 { margin: 0; font-size: 19px; font-weight: 700; line-height: 1.3; }
.vd-panel-acts { display: flex; gap: 8px; margin-top: 12px; }
.vd-sec { margin-top: 18px; }
.vd-sec h3, .vd-hist summary { margin: 0 0 8px; padding-bottom: 6px; border-bottom: 1px solid var(--rule); font-size: 11.5px;
  font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-soft); }
.vd-hist summary { cursor: pointer; }
.vd-row { display: flex; gap: 10px; margin-bottom: 8px; font-size: 13.5px; }
.vd-label { min-width: 92px; flex: none; font-size: 12.5px; color: var(--ink-soft); padding-top: 1px; }
.vd-val { flex: 1; min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; }
.vd-val a { color: var(--navy); }
:root[data-theme="dark"] .vd-val a { color: #9cc0ff; }
.vd-cred { font-family: var(--mono); font-size: 12.5px; padding: 7px 10px; border-radius: 6px; background: var(--hover); white-space: pre-wrap; overflow-wrap: anywhere; }
.vd-copy { margin-top: 6px; padding: 2px 10px; border-radius: 6px; border: 1px solid var(--card-edge); background: transparent;
  font: inherit; font-size: 12px; font-weight: 600; color: var(--ink-soft); cursor: pointer; }
.vd-copy:hover { color: var(--ink); border-color: var(--navy); }
.vd-updated { margin: 16px 0 0; font-size: 12px; color: var(--ink-faint); }
.vd-files { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.vd-file { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px; border: 1px solid var(--card-edge); background: var(--hover); font-size: 13px; }
.vd-file-ico { font-size: 15px; }
.vd-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--navy); text-decoration: none; }
:root[data-theme="dark"] .vd-file-name { color: #9cc0ff; }
.vd-file-name:hover { text-decoration: underline; }
.vd-file-size { font-size: 11.5px; color: var(--ink-soft); flex: none; }
.vd-file .vd-icon { width: 26px; height: 26px; }
.vd-none { list-style: none; font-size: 13px; color: var(--ink-soft); padding: 4px 0; }
.vd-upload { width: 100%; margin-top: 8px; padding: 8px 12px; border-radius: 8px; border: 1.5px dashed var(--rule-firm);
  background: transparent; font: inherit; font-size: 13px; color: var(--ink-soft); cursor: pointer; }
.vd-upload:hover { border-color: var(--navy); color: var(--ink); }
.vd-history { list-style: none; margin: 0; padding: 0; display: grid; gap: 5px; font-size: 12.5px; color: var(--ink-soft); }
.vd-h-when { display: inline-block; min-width: 11.5rem; color: var(--ink-faint); }

/* dialogs */
.vd-dialog {
  width: min(600px, 94vw); max-height: 90vh; padding: 22px 24px; border-radius: 14px; border: 1px solid var(--card-edge);
  background: var(--paper, #fff); color: var(--ink); box-shadow: 0 24px 60px rgba(0, 0, 0, .3);
}
:root[data-theme="dark"] .vd-dialog { background: #0f191d; }
.vd-dialog::backdrop { background: rgba(0, 0, 0, .45); }
.vd-dialog-narrow { width: min(460px, 94vw); }
.vd-dialog h2 { margin: 0 0 14px; font-size: 17px; font-weight: 700; }
.vd-dialog-hint { margin: -6px 0 14px; font-size: 13px; color: var(--ink-soft); }
.vd-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.vd-full { grid-column: 1 / -1; }
.vd-field { display: grid; gap: 4px; }
.vd-field label { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft); }
.vd-field input, .vd-field select, .vd-field textarea { font-family: inherit; font-size: 14px; }
.vd-form-err { min-height: 1em; margin: 10px 0 0; font-size: 13px; color: var(--charge); }
.vd-dialog-foot { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rule); }
.vd-buyer-list, .vd-deleted-list { list-style: none; margin: 0 0 12px; padding: 0; display: grid; gap: 6px; max-height: 50vh; overflow-y: auto; }
.vd-buyer { display: flex; align-items: center; gap: 8px; }
.vd-buyer input { flex: 1; font-family: inherit; font-size: 14px; }
.vd-buyer-n { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }
.vd-inline { display: flex; gap: 8px; }
.vd-inline input { flex: 1; font-family: inherit; font-size: 14px; }
.vd-del { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 12px; border-radius: 8px; border: 1px solid var(--card-edge); background: var(--hover); }
.vd-del-text { display: grid; gap: 2px; min-width: 0; }
.vd-del-text span { font-size: 12px; color: var(--ink-soft); }

/* the toast */
.vd-toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 300; margin: 0; padding: 10px 18px; border-radius: 9px;
  background: #1b2330; color: #fff; font-size: 13.5px; font-weight: 600; box-shadow: 0 10px 30px rgba(0, 0, 0, .25);
  transform: translate(-50%, 12px); opacity: 0; pointer-events: none; transition: opacity .2s ease, transform .2s ease;
}
.vd-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.vd-toast.is-bad { background: #b3261e; }
@media (max-width: 700px) { .vd-grid { grid-template-columns: 1fr; } }

/* ══════════════════════════════════════════════════════════════════════════
   DARK MODE · PROFILE PICTURE · DEVELOPER STRIP (2026-10-05)
   ══════════════════════════════════════════════════════════════════════════ */

/* ── the dark-mode button: centred at the foot of the rail, above the line ── */
.rail { grid-template-rows: auto minmax(0, 1fr) auto auto; }
.theme-row { display: flex; justify-content: center; padding: calc(var(--step) * 2) 0 calc(var(--step) * 3); }
.theme-toggle {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: calc(var(--step) * 2);
  padding: 6px 14px;
  border: 1px solid var(--rule-firm);
  border-radius: 999px;
  background: var(--field);
  color: var(--ink-soft);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease, border-color .14s ease;
}
.theme-toggle:hover { background: var(--field-lift); color: var(--ink); border-color: var(--navy); }
.theme-toggle svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2;
                    stroke-linecap: round; stroke-linejoin: round; }
.theme-toggle .theme-sun { display: none; }
.theme-toggle.is-dark .theme-moon { display: none; }
.theme-toggle.is-dark .theme-sun { display: block; }
/* The narrow rail: the icon alone, still centred. */
body.rail-mini .theme-toggle { padding: 7px; }
body.rail-mini .theme-toggle .theme-label { display: none; }

/* ── the avatar: a button now, and a picture when there is one ── */
button.avatar { appearance: none; padding: 0; cursor: pointer; overflow: hidden; font-family: inherit; }
button.avatar:hover { border-color: var(--navy); }
.avatar.has-picture { background: #000; border-color: var(--rule-firm); }
.avatar img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 45% 35%;
              transform: scale(1.25); }

/* ── the developer strip: one quiet line under the top bar ── */
.dev-strip {
  grid-column: 1 / -1;
  display: flex;
  /* One line always; on a narrow window it scrolls sideways rather than taking three rows. */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  gap: 6px;
  margin-top: calc(var(--step) * -1);
  font-size: 11px;
  line-height: 1.3;
}
.dev-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 8px;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--field);
  color: var(--ink-soft);
  white-space: nowrap;
  cursor: default;
}
.dev-chip[data-dev] { cursor: pointer; }
.dev-chip[data-dev]:hover { border-color: var(--navy); }
.dev-k { color: var(--ink-faint); text-transform: uppercase; letter-spacing: .06em; font-size: 10px; }
.dev-v { color: var(--ink); font-weight: 600; }
.dev-chip.is-on .dev-v::before { content: "\25CF  "; color: var(--ok); }
.dev-chip.is-bad { border-color: var(--charge); }
.dev-chip.is-bad .dev-v { color: var(--charge); }

/* ══════════════════════════════════════════════════════════════════════════
   HIGH CONTRAST DARK — after VS Code's "Dark High Contrast" theme.
   <html data-theme="dark" data-contrast="high">, stamped by theme.js. The dark tokens above
   apply first (white logo, the dark rules everywhere they exist); this layer then makes it VS
   Code's: pure black, white type, cyan #6FC3DF borders on everything that is a surface or a
   control, orange #F38518 for focus and for the thing that is selected, #21A6FF links. No
   glass, no photograph, no shadows — high contrast means nothing behind the type.
   ══════════════════════════════════════════════════════════════════════════ */
:root[data-contrast="high"] {
  --hc-border:  #6FC3DF;
  --hc-active:  #F38518;
  --hc-link:    #21A6FF;

  --glass:        #000;
  --glass-chrome: #000;
  --glass-soft:   #000;
  --glass-sunk:   #000;
  --paper:        #000;
  --surface:      #000;
  --card-edge:    var(--hc-border);
  --card-lift:    none;
  --edge:         var(--hc-border);
  --spec:         transparent;
  --blur:         none;
  --veil:         #000;
  --void:         #000;
  --hover:        rgba(255, 255, 255, .08);
  --field:        #000;
  --field-lift:   #0b0b0b;
  --disabled:     #000;
  --menu-bg:      #000;
  --scrim:        rgba(0, 0, 0, .8);

  --navy:       var(--hc-border);
  --navy-deep:  #FFFFFF;
  --navy-tint:  rgba(111, 195, 223, .14);
  --navy-ink:   #000;
  --state-ink:  #000;
  --label:      #FFFFFF;

  --ink:        #FFFFFF;
  --ink-soft:   #FFFFFF;
  --ink-faint:  #D4D4D4;
  --rule:       rgba(111, 195, 223, .55);
  --rule-firm:  var(--hc-border);

  --charge:     #F48771;
  --charge-dim: rgba(244, 135, 113, .16);
  --ok:         #89D185;
  --ok-dim:     rgba(137, 209, 133, .16);

  --lift: none;
  --photo-filter: brightness(0);
  --sky-filter: brightness(0);
  color-scheme: dark;
}
:root[data-contrast="high"] body { background: #000; }
:root[data-contrast="high"] .backdrop,
:root[data-contrast="high"] .sky { display: none; }
:root[data-contrast="high"] .glass::before { display: none; }
:root[data-contrast="high"] .glass { border-color: var(--hc-border); box-shadow: none; }

/* Focus and selection: orange, as VS Code draws them. */
:root[data-contrast="high"] :focus-visible { outline: 1px solid var(--hc-active); outline-offset: 1px; }
:root[data-contrast="high"] textarea:focus,
:root[data-contrast="high"] input:focus,
:root[data-contrast="high"] select:focus { border-color: var(--hc-active); box-shadow: none; outline: none; }

/* The rail: the open app outlined in orange rather than filled; hover outlined dashed. */
:root[data-contrast="high"] .tab[aria-selected="true"] {
  background: transparent; color: #FFF; outline: 1px solid var(--hc-active); outline-offset: -1px;
}
:root[data-contrast="high"] .tab:hover:not([aria-selected="true"]):not(:disabled),
:root[data-contrast="high"] .dept-head:hover {
  background: transparent; outline: 1px dashed var(--hc-active); outline-offset: -1px;
}
:root[data-contrast="high"] .dept-icon { color: #FFF; }
:root[data-contrast="high"] .rail-head,
:root[data-contrast="high"] .rail-foot,
:root[data-contrast="high"] .screen-bar { border-color: var(--hc-border); }

/* Controls: black with a cyan edge and white type; primary is the same with a heavier edge.
   VS Code's high-contrast buttons carry no fill, so neither do these. */
:root[data-contrast="high"] .btn,
:root[data-contrast="high"] .btn-ghost,
:root[data-contrast="high"] .btn-link,
:root[data-contrast="high"] .btn-primary {
  background: #000; color: #FFF; border: 1px solid var(--hc-border); box-shadow: none;
}
:root[data-contrast="high"] .btn-primary { border-width: 2px; }
:root[data-contrast="high"] .btn:hover:not(:disabled),
:root[data-contrast="high"] .btn-primary:hover:not(:disabled),
:root[data-contrast="high"] .btn-link:hover:not(:disabled) {
  background: #000; border-color: var(--hc-active); color: #FFF;
}
:root[data-contrast="high"] .btn:disabled { background: #000; color: #8A8A8A; border: 1px solid #555; }
:root[data-contrast="high"] textarea,
:root[data-contrast="high"] input,
:root[data-contrast="high"] select { background: #000; color: #FFF; border-color: var(--hc-border); }
:root[data-contrast="high"] ::placeholder { color: #A6A6A6; }
:root[data-contrast="high"] a { color: var(--hc-link); }

/* Tables inside apps: white on black with cyan lines. */
:root[data-contrast="high"] th,
:root[data-contrast="high"] td { border-color: rgba(111, 195, 223, .45); }

:root[data-contrast="high"] .theme-toggle { background: #000; color: #FFF; border-color: var(--hc-border); }
:root[data-contrast="high"] .theme-toggle:hover { border-color: var(--hc-active); }
:root[data-contrast="high"] .dev-chip { background: #000; border-color: var(--hc-border); color: #FFF; }
:root[data-contrast="high"] .dev-chip.is-bad { border-color: var(--charge); }
:root[data-contrast="high"] .avatar { border-color: var(--hc-border); }

/* CS Email draws from its own palette (--c-*, light by default, its own dark toggle). In high
   contrast the hub's choice wins: the same black, white and cyan as everything else. */
:root[data-contrast="high"] .csm,
:root[data-contrast="high"] .csm.is-dark {
  --c-folders: #000; --c-surface: #000; --c-canvas: #000;
  --c-line: rgba(111, 195, 223, .55); --c-line-2: #6FC3DF;
  --c-ink: #FFF; --c-ink-2: #FFF; --c-ink-3: #D4D4D4;
  --c-blue: #6FC3DF; --c-btn: #000; --c-blue-ink: #FFF;
  --c-blue-soft: rgba(111, 195, 223, .14); --c-sel: rgba(111, 195, 223, .18);
  --c-hover: rgba(255, 255, 255, .08);
  --c-red: #F48771; --c-red-soft: rgba(244, 135, 113, .16);
  --c-green: #89D185; --c-green-soft: rgba(137, 209, 133, .14);
  --c-amber: #FFD370; --c-amber-soft: rgba(255, 211, 112, .14);
  --c-chip: #000;
  --navy-deep: #000;
  color-scheme: dark;
}
:root[data-contrast="high"] #csmail .btn,
:root[data-contrast="high"] #csmail .btn-primary,
:root[data-contrast="high"] #csmail .btn-link { background: #000; color: #FFF; border-color: #6FC3DF; }
:root[data-contrast="high"] #csmail .btn:hover:not(:disabled),
:root[data-contrast="high"] #csmail .btn-primary:hover:not(:disabled),
:root[data-contrast="high"] #csmail .btn-link:hover:not(:disabled) { background: #000; color: #FFF; border-color: #F38518; }

/* The mark in high contrast: PISCES in white rather than navy, which all but vanishes on black. */
:root[data-contrast="high"] .logo { content: url("pisces-central-logo-hc.svg"); }

/* ══════════════════════════════════════════════════════════════════════════
   ALERTS IN THE SEARCH BOX (2026-10-05) — web/alerts.js
   The newest unread alert's text sits where "Search apps" was, the box wears a slow, faint red
   glow until it is acknowledged, and a bell at the right end opens the list. Red is an outcome
   colour here (rule 1) and an alert is one: something needs attention.
   ══════════════════════════════════════════════════════════════════════════ */
:root { --alert: #C8322B; --alert-glow: 200, 50, 43; }
:root[data-theme="dark"] { --alert: #FF6B61; --alert-glow: 255, 107, 97; }
:root[data-contrast="high"] { --alert: #F48771; --alert-glow: 244, 135, 113; }

.search.has-bell input { padding-right: calc(var(--step) * 11); }
.search.has-bell .search-scope { right: calc(var(--step) * 10); }
.search.has-alert input {
  border-color: rgba(var(--alert-glow), .45);
  animation: alert-breathe 3.6s ease-in-out infinite;
}
.search.has-alert.is-fresh input { border-color: rgba(var(--alert-glow), .7); }
.search.has-alert.is-urgent input { border-color: rgba(var(--alert-glow), .85); }
.search.has-alert.is-typing input { animation: none; box-shadow: 0 0 0 1px rgba(var(--alert-glow), .25); }
@keyframes alert-breathe {
  0%, 100% { box-shadow: 0 0 0 1px rgba(var(--alert-glow), .14), 0 0 8px 0 rgba(var(--alert-glow), .10); }
  50%      { box-shadow: 0 0 0 1px rgba(var(--alert-glow), .30), 0 0 16px 2px rgba(var(--alert-glow), .26); }
}
@media (prefers-reduced-motion: reduce) {
  .search.has-alert input { animation: none; box-shadow: 0 0 0 1px rgba(var(--alert-glow), .3), 0 0 12px 1px rgba(var(--alert-glow), .2); }
}

.alert-line {
  position: absolute;
  left: calc(var(--step) * 9);
  right: calc(var(--step) * 11);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font-size: 13px;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
}
.alert-line b { font-weight: 650; overflow: hidden; text-overflow: ellipsis; }
.alert-from { color: var(--ink-faint); flex: 0 0 auto; }
.search.is-typing .alert-line { display: none; }
.alert-dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--alert);
             box-shadow: 0 0 0 3px rgba(var(--alert-glow), .18); }

.alert-bell {
  position: absolute;
  right: calc(var(--step) * 1.5);
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}
.alert-bell:hover { background: var(--hover); color: var(--ink); }
.alert-bell svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.alert-count {
  position: absolute; top: -2px; right: -3px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--alert); color: #fff; font-size: 10px; font-weight: 700; line-height: 16px;
}
.alert-count:empty { display: none; }
.alert-bell.is-empty { opacity: .6; }

.alert-pop {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  min-width: 320px;
  max-height: min(70vh, 520px);
  overflow-y: auto;
  z-index: 60;
  padding: 6px;
  border: 1px solid var(--card-edge);
  border-radius: 16px;
  background: var(--menu-bg);
  color: var(--ink);
  box-shadow: 0 18px 50px -12px rgba(0, 0, 0, .35);
}
.alert-pop-head { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 8px; font-size: 13px; }
.alert-send-btn { border: 0; background: none; color: var(--navy); font-weight: 650; font-size: 12.5px; cursor: pointer; }
.alert-item { padding: 10px 12px; border-radius: 12px; border-left: 3px solid rgba(var(--alert-glow), .55); margin: 4px 2px; background: var(--hover); }
.alert-item.is-urgent { border-left-color: var(--alert); }
.alert-item-title { font-weight: 650; font-size: 13.5px; }
.alert-item-body { margin-top: 3px; font-size: 13px; color: var(--ink-soft); white-space: pre-wrap; }
.alert-item-meta { margin-top: 4px; font-size: 11.5px; color: var(--ink-faint); }
.alert-item-acts { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
.alert-item-acts .btn { padding: 4px 12px; font-size: 12.5px; }
.alert-none { margin: 0; padding: 14px; color: var(--ink-faint); font-size: 13px; text-align: center; }

/* sending */
.alert-modal-back { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: start center; padding: 8vh 16px; overflow-y: auto;
                    background: var(--scrim); }
.alert-modal { width: 100%; max-width: 620px; border-radius: 18px; background: var(--menu-bg); color: var(--ink);
               border: 1px solid var(--card-edge); box-shadow: 0 30px 80px -20px rgba(0, 0, 0, .5); }
.alert-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.alert-tabs { display: flex; gap: 4px; }
.alert-tabs button { border: 0; background: none; padding: 6px 12px; border-radius: 999px; color: var(--ink-soft); font-weight: 600; cursor: pointer; }
.alert-tabs button.is-on { background: var(--navy-tint); color: var(--ink); }
.alert-x { border: 0; background: none; font-size: 22px; line-height: 1; color: var(--ink-faint); cursor: pointer; }
.alert-modal-body { padding: 14px 16px 16px; display: grid; gap: 12px; }
.alert-f { display: grid; gap: 5px; font-size: 13px; }
.alert-f > span { font-size: 11.5px; font-weight: 650; color: var(--label); text-transform: uppercase; letter-spacing: .05em; }
.alert-row { display: grid; grid-template-columns: 1fr 2fr; gap: 12px; }
.alert-group { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.alert-group em { flex: 0 0 100%; font-style: normal; font-size: 12px; color: var(--ink-faint); margin-top: 4px; }
.alert-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--rule-firm); border-radius: 999px; font-size: 12.5px; cursor: pointer; }
.alert-chip.is-all { font-weight: 650; }
.alert-group input#al-people-q { flex: 1 1 100%; }
.alert-people { flex: 1 1 100%; display: grid; }
.alert-people button { text-align: left; border: 0; background: none; padding: 5px 8px; border-radius: 8px; cursor: pointer; }
.alert-people button:hover { background: var(--hover); }
.alert-people span { color: var(--ink-faint); font-size: 12px; margin-left: 6px; }
.alert-picked { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 6px; }
.alert-pick { display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px; border-radius: 999px; background: var(--navy-tint); font-size: 12.5px; }
.alert-pick button { border: 0; background: none; cursor: pointer; color: var(--ink-faint); }
.alert-err { margin: 0; color: var(--charge); font-size: 13px; min-height: 1em; }
.alert-modal-foot { display: flex; justify-content: flex-end; gap: 8px; }
.alert-sent { padding: 10px 2px; border-bottom: 1px solid var(--rule); font-size: 13px; }
.alert-sent.is-withdrawn { opacity: .55; }
.alert-sent-count { display: flex; align-items: center; gap: 8px; margin-top: 4px; color: var(--ink-soft); }
.alert-sent-count .btn { padding: 2px 10px; font-size: 12px; }
.alert-receipts { margin-top: 6px; display: grid; gap: 2px; font-size: 12.5px; }
.alert-receipts div { display: flex; justify-content: space-between; gap: 12px; color: var(--ink-soft); }
.alert-receipts div.is-acked { color: var(--ok); }
.alert-receipts span { color: var(--ink-faint); }
@media (max-width: 40rem) { .alert-row { grid-template-columns: 1fr; } }

/* ══════════════════════════════════════════════════════════════════════════
   THE TEAMS OVERLAY (2026-10-05) — web/teams.js
   ══════════════════════════════════════════════════════════════════════════ */
:root { --teams: #5B5FC7; --teams-deep: #4F52B2; --teams-soft: rgba(91, 95, 199, .12); }
:root[data-theme="dark"] { --teams: #7F85F5; --teams-deep: #6E74E8; --teams-soft: rgba(127, 133, 245, .16); }

.teams-fab {
  position: fixed;
  right: 22px;
  bottom: 22px;
  z-index: 900;
  display: grid;
  place-items: center;
  width: 50px;
  height: 50px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--teams);
  color: #fff;
  box-shadow: 0 8px 24px -6px rgba(40, 42, 110, .55), 0 2px 6px rgba(0, 0, 0, .18);
  cursor: pointer;
  transition: transform .15s ease, background-color .15s ease;
}
.teams-fab:hover { background: var(--teams-deep); transform: translateY(-1px); }
.teams-fab svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; stroke-linecap: round; }
.teams-fab.is-open { transform: scale(.94); }
.teams-badge {
  position: absolute; top: -3px; right: -3px; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 999px; background: var(--alert); color: #fff; border: 2px solid var(--menu-bg);
  font-size: 11px; font-weight: 700; line-height: 16px; text-align: center;
}
.teams-fab.has-unread { animation: alert-ring 3.6s ease-in-out infinite; }
@keyframes alert-ring {
  0%, 100% { box-shadow: 0 8px 24px -6px rgba(40, 42, 110, .55), 0 0 0 0 rgba(var(--alert-glow), .0); }
  50%      { box-shadow: 0 8px 24px -6px rgba(40, 42, 110, .55), 0 0 0 5px rgba(var(--alert-glow), .22); }
}
.teams-fab.is-ping { animation: teams-ping .7s ease; }
@keyframes teams-ping { 0% { transform: scale(1); } 35% { transform: scale(1.14); } 70% { transform: scale(.97); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .teams-fab.has-unread, .teams-fab.is-ping { animation: none; } }

.teams-panel {
  position: fixed;
  right: 22px;
  bottom: 84px;
  z-index: 900;
  display: flex;
  flex-direction: column;
  width: min(370px, calc(100vw - 32px));
  height: min(560px, calc(100vh - 120px));
  overflow: hidden;
  border: 1px solid var(--card-edge);
  border-radius: 18px;
  background: var(--menu-bg);
  color: var(--ink);
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .45), 0 4px 14px rgba(0, 0, 0, .12);
  font-size: 13.5px;
}
.teams-head { display: flex; align-items: center; gap: 6px; padding: 10px 10px 10px 14px; background: var(--teams); color: #fff; }
.teams-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.teams-icon { border: 0; background: rgba(255, 255, 255, .14); color: #fff; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; font-size: 15px; line-height: 1; }
.teams-icon:hover { background: rgba(255, 255, 255, .26); }
.teams-tabs { display: flex; gap: 4px; padding: 8px 10px 4px; border-bottom: 1px solid var(--rule); }
.teams-tabs button { border: 0; background: none; padding: 6px 12px; border-radius: 8px; color: var(--ink-soft); font-weight: 600; cursor: pointer; }
.teams-tabs button[aria-selected="true"] { background: var(--teams-soft); color: var(--ink); }
.teams-list { flex: 1; overflow-y: auto; padding: 4px; }
.teams-pad { padding: 12px 14px; margin: 0; }
.teams-dim { color: var(--ink-faint); font-size: 12.5px; }
.teams-err { color: var(--charge); font-size: 12.5px; margin: 6px 0 0; }
.teams-row { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: 0; border-radius: 10px; background: none; color: inherit; text-align: left; cursor: pointer; }
.teams-row:hover { background: var(--hover); }
.teams-row.is-unread b { font-weight: 750; }
.teams-row-main { flex: 1; min-width: 0; display: grid; gap: 1px; }
.teams-row-top { display: flex; justify-content: space-between; gap: 8px; }
.teams-row-top b, .teams-row-main > b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.teams-row-top span { flex: 0 0 auto; font-size: 11px; color: var(--ink-faint); }
.teams-row-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: var(--ink-faint); }
.teams-row.is-unread .teams-row-sub { color: var(--ink-soft); }
.teams-av { position: relative; flex: 0 0 auto; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: var(--teams-soft); color: var(--teams); font-size: 12px; font-weight: 700; }
.teams-pres { position: absolute; right: -1px; bottom: -1px; width: 11px; height: 11px; border-radius: 50%; border: 2px solid var(--menu-bg); background: #8a8886; }
.teams-pres.is-available, .teams-pres.is-availableidle { background: #6BB700; }
.teams-pres.is-busy, .teams-pres.is-busyidle, .teams-pres.is-donotdisturb { background: #C4314B; }
.teams-pres.is-away, .teams-pres.is-berightback { background: #FFAA44; }
.teams-pill { flex: 0 0 auto; min-width: 18px; padding: 0 6px; border-radius: 999px; background: var(--alert); color: #fff; font-size: 11px; font-weight: 700; line-height: 18px; text-align: center; }
.teams-team { margin: 4px 0 8px; }
.teams-team-name { padding: 6px 10px 2px; font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-faint); }
.teams-chan-row { display: flex; align-items: center; }
.teams-hash { flex: 0 0 auto; width: 20px; color: var(--ink-faint); font-weight: 700; text-align: center; }
.teams-star { border: 0; background: none; color: var(--rule-firm); font-size: 15px; padding: 4px 8px; cursor: pointer; }
.teams-star.is-on { color: #E8A317; }
.teams-thread { flex: 1; overflow-y: auto; padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; background: var(--hover); }
.teams-msg { max-width: 85%; align-self: flex-start; }
.teams-msg.is-mine { align-self: flex-end; text-align: right; }
.teams-msg-from { font-size: 11.5px; font-weight: 650; color: var(--ink-soft); margin: 0 0 2px 2px; }
.teams-bubble { display: inline-block; text-align: left; padding: 7px 11px; border-radius: 12px; background: var(--menu-bg); border: 1px solid var(--rule);
                white-space: normal; overflow-wrap: anywhere; line-height: 1.4; }
.teams-msg.is-mine .teams-bubble { background: var(--teams-soft); border-color: transparent; }
.teams-msg-at { font-size: 10.5px; color: var(--ink-faint); margin: 2px 4px 0; }
.teams-file { margin-top: 4px; font-size: 12px; color: var(--ink-soft); }
.teams-sys { align-self: center; font-size: 11.5px; color: var(--ink-faint); font-style: italic; }
.teams-compose { display: flex; align-items: flex-end; gap: 8px; padding: 10px; border-top: 1px solid var(--rule); }
.teams-compose textarea { flex: 1; resize: none; min-height: 38px; max-height: 120px; border-radius: 12px; font-size: 13.5px; }
.teams-send { flex: 0 0 auto; width: 38px; height: 38px; border: 0; border-radius: 50%; background: var(--teams); color: #fff; cursor: pointer; font-size: 15px; }
.teams-find { width: 100%; }
.teams-connect { padding: 28px 22px; text-align: center; display: grid; gap: 8px; justify-items: center; }
.teams-connect p { margin: 0; }

/* high contrast: black, white type, cyan edges, orange focus — as the rest */
:root[data-contrast="high"] .teams-fab { background: #000; border: 2px solid var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .teams-fab:hover { border-color: var(--hc-active); }
:root[data-contrast="high"] .teams-badge { border-color: #000; color: #000; }
:root[data-contrast="high"] .teams-panel { background: #000; border-color: var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .teams-head { background: #000; border-bottom: 1px solid var(--hc-border); }
:root[data-contrast="high"] .teams-icon { background: #000; border: 1px solid var(--hc-border); }
:root[data-contrast="high"] .teams-bubble { background: #000; border-color: var(--hc-border); }
:root[data-contrast="high"] .teams-msg.is-mine .teams-bubble { border-color: #FFF; }
:root[data-contrast="high"] .teams-thread { background: #000; }
:root[data-contrast="high"] .teams-send { background: #000; border: 1px solid var(--hc-border); }
:root[data-contrast="high"] .teams-av { background: #000; border: 1px solid var(--hc-border); color: #FFF; }
:root[data-contrast="high"] .teams-row:hover { background: transparent; outline: 1px dashed var(--hc-active); outline-offset: -1px; }
:root[data-contrast="high"] .alert-pop,
:root[data-contrast="high"] .alert-modal { background: #000; border-color: var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .alert-item { background: #000; border: 1px solid rgba(111, 195, 223, .5); border-left: 3px solid var(--alert); }
:root[data-contrast="high"] .alert-count,
:root[data-contrast="high"] .teams-pill { color: #000; }

/* The hub's form labels are small monospace capitals; inside the alert dialog only the field names are. */
.alert-modal label, .alert-modal .alert-chip { text-transform: none; letter-spacing: normal; font-family: var(--sans); font-size: 12.5px; font-weight: 500; color: var(--ink); }
.alert-modal .alert-chip.is-all { font-weight: 650; }
.alert-modal input:not([type="checkbox"]), .alert-modal textarea, .alert-modal select { font-family: var(--sans); font-size: 13.5px; letter-spacing: normal; }
.alert-modal .alert-f > span { font-family: var(--sans); }
.alert-chip input { margin: 0; }

/* Teams cards (Workflows, bots) drawn as text — web/teams.js cardHtml(). */
.teams-bubble.is-card { display: block; padding: 0; background: none; border: 0; }
.teams-card { padding: 9px 12px; border: 1px solid var(--rule); border-left: 3px solid var(--teams); border-radius: 10px; background: var(--menu-bg); text-align: left; }
.teams-card + .teams-card { margin-top: 6px; }
.teams-card-title { font-weight: 700; margin-bottom: 3px; }
.teams-card-line { font-size: 12.5px; color: var(--ink-soft); overflow-wrap: anywhere; }
.teams-card-facts { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 6px 0 0; font-size: 12px; }
.teams-card-facts dt { color: var(--ink-faint); font-weight: 600; }
.teams-card-facts dd { margin: 0; overflow-wrap: anywhere; }
.teams-card-links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.teams-card-links a { padding: 3px 10px; border: 1px solid var(--rule-firm); border-radius: 999px; font-size: 12px; text-decoration: none; color: var(--teams); }
:root[data-contrast="high"] .teams-card { background: #000; border-color: var(--hc-border); }
:root[data-contrast="high"] .teams-card-links a { color: var(--hc-link); border-color: var(--hc-border); }

/* CS Email → Info (2026-10-05): the NetSuite order behind a PO, in dropdowns. */
.csm-info .csm-order-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.csm-info-va { margin-left: auto; padding: 3px 12px; font-size: 12px; }
.csm-drop { margin-top: 8px; border: 1px solid var(--c-line); border-radius: 10px; background: var(--c-surface); }
.csm-drop > summary { cursor: pointer; padding: 7px 10px; font-size: 12.5px; font-weight: 650; color: var(--c-ink);
                      list-style: none; display: flex; align-items: center; gap: 6px; }
.csm-drop > summary::-webkit-details-marker { display: none; }
.csm-drop > summary::before { content: "▸"; color: var(--c-ink-3); transition: transform .12s ease; }
.csm-drop[open] > summary::before { transform: rotate(90deg); }
.csm-drop[open] > summary { border-bottom: 1px solid var(--c-line); }
.csm-info-rows { padding: 4px 10px 8px; display: grid; gap: 2px; }
.csm-info-row { display: grid; grid-template-columns: 92px 1fr auto; align-items: center; gap: 8px; min-height: 26px; font-size: 12.5px; }
.csm-info-k { color: var(--c-ink-3); }
.csm-info-v { color: var(--c-ink); overflow-wrap: anywhere; }
.csm-info-addr { display: flex; align-items: flex-start; gap: 8px; padding: 8px 10px; }
.csm-info-pre { flex: 1; margin: 0; font-family: var(--sans); font-size: 12.5px; line-height: 1.45; white-space: pre-wrap; color: var(--c-ink); }
.csm-info-items { padding: 4px 10px 8px; display: grid; gap: 8px; }
.csm-info-item { padding-bottom: 6px; border-bottom: 1px dashed var(--c-line); }
.csm-info-item:last-child { border-bottom: 0; padding-bottom: 0; }
.csm-info-item-top { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; }
.csm-info-sku { font-family: var(--mono); color: var(--c-ink); }
.csm-info-qty { color: var(--c-ink-2); }
.csm-info-amt { margin-left: auto; color: var(--c-ink); font-variant-numeric: tabular-nums; }
.csm-info-name { margin: 2px 0 0; font-size: 12px; color: var(--c-ink-2); }
.csm-info-shipped { margin: 2px 0 0; font-size: 11.5px; color: var(--c-amber); }
.csm-info-shipped.is-done { color: var(--c-green); }

/* ── THE NUMBER RAIN (2026-10-05) — web/matrix.js. One canvas behind everything, dark mode only,
   for the accounts the server names. The home screen's greeting sits straight on it, so it keeps
   a black halo to stay readable over the digits. */
.matrix-rain { position: fixed; inset: 0; z-index: -1; width: 100vw; height: 100vh; pointer-events: none; }
:root.has-matrix body { background: #000; }
:root.has-matrix .home-greet { text-shadow: 0 0 6px #000, 0 0 14px #000, 0 0 28px #000; }
:root.has-matrix .home-search { background: #000; border-radius: 999px; }
.home-avatar {
  width: clamp(88px, 11vw, 132px);
  height: clamp(88px, 11vw, 132px);
  margin-bottom: calc(var(--step) * -2);
  border-radius: 50%;
  object-fit: cover;
  object-position: 45% 35%;
  background: #000;
  border: 2px solid #21A6FF;
  box-shadow: 0 0 0 4px #000, 0 0 24px 2px rgba(33, 166, 255, .55);
}
@media (prefers-reduced-motion: no-preference) {
  .home-avatar { animation: home-avatar-glow 4s ease-in-out infinite; }
}
@keyframes home-avatar-glow {
  0%, 100% { box-shadow: 0 0 0 4px #000, 0 0 18px 1px rgba(33, 166, 255, .45); }
  50%      { box-shadow: 0 0 0 4px #000, 0 0 30px 4px rgba(33, 166, 255, .7); }
}
:root.has-matrix .home-hint { text-shadow: 0 0 4px #000, 0 0 10px #000, 0 0 18px #000; }

/* ══════════════════════════════════════════════════════════════════════════
   STOREFRONTS → DASHBOARD (2026-10-06) — web/dashboard.js
   VA 2.0's modular dashboard in Central's own colours: every surface reads the hub's tokens, so
   light, dark and high contrast all follow without a rule of their own.
   ══════════════════════════════════════════════════════════════════════════ */
body.app-sfdash .page-head,
body.app-sfdash #form,
body.app-sfdash .commit { display: none !important; }

.sfd { display: grid; gap: calc(var(--step) * 3); }
.sfd-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.sfd-title { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.sfd-title h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.sfd-sub { font-size: 12.5px; color: var(--ink-faint); }
.sfd-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sfd-tools .btn { padding: 6px 12px; font-size: 12.5px; }
.sfd-preset { padding: 6px 10px; border-radius: 10px; font-size: 12.5px; max-width: 160px; }
.sfd:not(.is-editing) .sfd-edit-only { display: none !important; }
.sfd-hint { margin: 0; padding: 8px 12px; border-radius: 10px; background: var(--navy-tint); color: var(--ink-soft); font-size: 12.5px; }
.sfd-flash { margin: 0; padding: 8px 12px; border-radius: 10px; border: 1px solid var(--rule-firm); background: var(--glass-sunk); font-size: 12.5px; }
.sfd-empty { display: grid; justify-items: center; gap: 10px; padding: 48px 16px; color: var(--ink-faint); }

.sfd-grid { position: relative; min-height: 120px; }
.sfd-card {
  position: absolute;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--card-edge);
  border-radius: 16px;
  background: var(--glass-sunk);
  box-shadow: var(--card-lift);
  transition: left .18s ease, top .18s ease, width .18s ease, height .18s ease;
}
.sfd-card.is-dragging { transition: none; z-index: 5; box-shadow: 0 18px 40px -12px rgba(0, 0, 0, .35); opacity: .96; }
.sfd.is-editing .sfd-card { outline: 1px dashed var(--rule-firm); outline-offset: -1px; }
.sfd-card-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px 8px; border-bottom: 1px solid var(--rule); min-height: 42px; }
.sfd.is-editing .sfd-card-head { cursor: grab; }
.sfd-card.is-dragging .sfd-card-head { cursor: grabbing; }
.sfd-card-icon { flex: 0 0 auto; width: 16px; height: 16px; fill: none; stroke: var(--navy); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.sfd-card-title { flex: 1 1 auto; min-width: 0; margin: 0; font-size: 13.5px; font-weight: 650; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sfd-card-store { font-size: 11px; color: var(--ink-faint); white-space: nowrap; }
.sfd-card-tools { display: flex; align-items: center; gap: 4px; }
.sfd-range { padding: 2px 6px; border-radius: 8px; font-size: 11.5px; max-width: 130px; }
.sfd-tool { width: 24px; height: 24px; display: grid; place-items: center; border: 0; border-radius: 6px; background: transparent; color: var(--ink-faint); cursor: pointer; font-size: 14px; line-height: 1; }
.sfd-tool:hover { background: var(--hover); color: var(--ink); }
.sfd-card-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 10px 12px; }
.sfd-card.is-loading .sfd-card-body { opacity: .55; }
.sfd-resize { position: absolute; right: 2px; bottom: 2px; width: 16px; height: 16px; cursor: nwse-resize;
  border-right: 2px solid var(--rule-firm); border-bottom: 2px solid var(--rule-firm); border-bottom-right-radius: 10px; }
.sfd-skel { height: 100%; min-height: 40px; border-radius: 10px; background: linear-gradient(90deg, var(--hover), var(--field-lift), var(--hover)); background-size: 200% 100%; animation: sfd-skel 1.4s ease-in-out infinite; }
@keyframes sfd-skel { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }
@media (prefers-reduced-motion: reduce) { .sfd-skel { animation: none; } .sfd-card { transition: none; } }

.sfd-note { margin: 0; color: var(--ink-faint); font-size: 12.5px; }
.sfd-foot { margin: 8px 0 0; color: var(--ink-faint); font-size: 11.5px; }
.sfd-foot.is-bad, .sfd-error { color: var(--charge); }
.sfd-error p { margin: 0 0 8px; font-size: 12.5px; }

.sfd-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }
.sfd-stat { padding: 10px 12px; border-radius: 12px; background: var(--hover); }
.sfd-stat-label { font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .05em; font-weight: 650; }
.sfd-stat-value { font-size: 24px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; line-height: 1.25; margin-top: 2px; }
.sfd-stat-sub { font-size: 12px; color: var(--ink-soft); }
.sfd-delta { margin-top: 4px; font-size: 11.5px; color: var(--ink-faint); }
.sfd-delta.is-up { color: var(--ok); }
.sfd-delta.is-down { color: var(--charge); }

.sfd-table-wrap { overflow: auto; }
.sfd-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.sfd-table th { position: sticky; top: 0; background: var(--glass-sunk); text-align: left; font-size: 11px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint); font-weight: 650; padding: 4px 6px; border-bottom: 1px solid var(--rule); }
.sfd-table td { padding: 6px; border-bottom: 1px solid var(--rule); color: var(--ink); vertical-align: top; }
.sfd-table tr:last-child td { border-bottom: 0; }
.sfd-table .is-right { text-align: right; font-variant-numeric: tabular-nums; }
.sfd-link { color: var(--navy); font-weight: 600; text-decoration: none; }
.sfd-link:hover { text-decoration: underline; }
.sfd-badge { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 650; background: var(--hover); color: var(--ink-soft); }
.sfd-badge.is-ok { background: var(--ok-dim); color: var(--ok); }
.sfd-badge.is-bad { background: var(--charge-dim); color: var(--charge); }
.sfd-badge.is-warn { background: rgba(232, 147, 12, .16); color: #B26A00; }
.sfd-badge.is-info { background: var(--navy-tint); color: var(--navy); }
:root[data-theme="dark"] .sfd-badge.is-warn { color: #FFC266; }

.sfd-buckets { display: grid; gap: 10px; }
.sfd-bucket-head { display: flex; align-items: baseline; gap: 10px; }
.sfd-bucket-list { display: grid; gap: 4px; }
.sfd-bucket { display: flex; justify-content: space-between; padding: 6px 10px; border-radius: 10px; background: var(--hover); font-size: 13px; }
.sfd-bucket.is-hot b { color: var(--charge); }

.sfd-chart { position: relative; height: 100%; min-height: 120px; }
.sfd-chart canvas { position: absolute; inset: 0; }

.sfd-goto { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-content: start; }
.sfd-goto .sfd-note { grid-column: 1 / -1; }
.sfd-notes { display: flex; flex-direction: column; gap: 6px; height: 100%; }
.sfd-note-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
.sfd-note-tab { border: 1px solid var(--rule); background: transparent; border-radius: 8px; padding: 2px 8px; font-size: 11.5px; color: var(--ink-soft); cursor: pointer; }
.sfd-note-tab.is-on { background: var(--navy-tint); color: var(--ink); font-weight: 650; }
.sfd-note-text { flex: 1 1 auto; min-height: 60px; resize: none; font-size: 13px; }
.sfd-links { display: grid; gap: 8px; }
.sfd-link-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.sfd-link-list li { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.sfd-link-add { display: grid; grid-template-columns: 1fr 1.6fr auto; gap: 6px; }
.sfd:not(.is-editing) .sfd-link-add { display: none; }

.sfd-modal-back { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: start center; padding: 6vh 16px; overflow-y: auto; background: var(--scrim); }
.sfd-modal { width: 100%; max-width: 560px; border-radius: 18px; background: var(--menu-bg); color: var(--ink); border: 1px solid var(--card-edge); box-shadow: 0 30px 80px -20px rgba(0, 0, 0, .5); }
.sfd-modal.is-wide { max-width: 860px; }
.sfd-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.sfd-modal-head h3 { margin: 0; font-size: 15px; }
.sfd-modal-body { padding: 14px 16px 16px; display: grid; gap: 12px; }
.sfd-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px 14px; }
.sfd-form .is-wide { grid-column: 1 / -1; }
.sfd-field { display: grid; gap: 4px; font-size: 13px; }
.sfd-field > span { font-size: 11px; font-weight: 650; color: var(--label); text-transform: uppercase; letter-spacing: .05em; }
.sfd-form-foot { grid-column: 1 / -1; display: flex; justify-content: flex-end; gap: 8px; }
.sfd-search { width: 100%; }
.sfd-picker { display: grid; gap: 4px; max-height: 60vh; overflow: auto; }
.sfd-pick-cat { margin: 10px 0 2px; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); }
.sfd-pick { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 10px; }
.sfd-pick:hover { background: var(--hover); }
.sfd-pick-icon { flex: 0 0 auto; width: 18px; height: 18px; fill: none; stroke: var(--navy); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.sfd-pick-text { flex: 1; display: grid; }
.sfd-pick-text b { font-size: 13px; }
.sfd-pick-text span { font-size: 12px; color: var(--ink-faint); }
.sfd-admin-top, .sfd-admin-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.sfd-admin-sec { display: grid; gap: 8px; padding-top: 8px; border-top: 1px solid var(--rule); }
.sfd-admin-sec h4, .sfd-cols h4, .sfd-metric h4 { margin: 0; font-size: 13px; }
.sfd-admin-widgets { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 2px 12px; }
.sfd-check { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
.sfd-dim { color: var(--ink-faint); font-size: 11.5px; }
.sfd-sql { font-family: var(--mono); font-size: 12.5px; }
.sfd-preview .sfd-table-wrap { max-height: 220px; }
.sfd-col-row { display: grid; grid-template-columns: auto minmax(90px, 1fr) minmax(120px, 1.4fr) 120px auto; gap: 8px; align-items: center; margin-top: 4px; font-size: 12.5px; }
.sfd-metric .sfd-col-row { grid-template-columns: 100px 1fr 120px 1fr; }
.sfd-access { border: 1px solid var(--rule); border-radius: 12px; padding: 8px 12px; display: flex; flex-wrap: wrap; gap: 4px 14px; }
.sfd-access legend { font-size: 11.5px; color: var(--ink-faint); padding: 0 4px; }
.sfd-limits { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; }
.sfd-modal label, .sfd-modal .sfd-check { text-transform: none; letter-spacing: normal; font-family: var(--sans); color: var(--ink); }
.sfd-modal input:not([type="checkbox"]), .sfd-modal select, .sfd-modal textarea { font-family: var(--sans); font-size: 13px; letter-spacing: normal; }
.sfd-modal .sfd-sql { font-family: var(--mono); }

/* high contrast: black cards with cyan edges, as the rest of the hub */
:root[data-contrast="high"] .sfd-card { background: #000; border-color: var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .sfd-stat, :root[data-contrast="high"] .sfd-bucket { background: #000; border: 1px solid rgba(111, 195, 223, .45); }
:root[data-contrast="high"] .sfd-table th { background: #000; }
:root[data-contrast="high"] .sfd-modal { background: #000; border-color: var(--hc-border); }
:root[data-contrast="high"] .sfd-hint { background: #000; border: 1px solid var(--hc-border); }
.sfd select, .sfd-range, .sfd-preset { font-family: var(--sans); letter-spacing: normal; }

/* ══════════════════════════════════════════════════════════════════════════
   TOOLS → FILE CONVERTER (2026-10-06) — web/converter.js
   ══════════════════════════════════════════════════════════════════════════ */
body.app-convert .page-head,
body.app-convert #form,
body.app-convert .commit { display: none !important; }
.fc { display: grid; gap: calc(var(--step) * 4); max-width: 980px; }
.fc-head h1 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); }
.fc-sub { margin: 4px 0 0; font-size: 13px; color: var(--ink-soft); max-width: 70ch; }
.fc-warn { margin: 0; padding: 8px 12px; border-radius: 10px; background: var(--charge-dim); color: var(--charge); font-size: 13px; }
.fc-drop { display: grid; justify-items: center; gap: 6px; padding: 36px 20px; text-align: center; cursor: pointer;
  border: 2px dashed var(--rule-firm); border-radius: 18px; background: var(--glass-sunk); transition: border-color .15s, background-color .15s; }
.fc-drop b { font-size: 15px; color: var(--ink); }
.fc-drop span { font-size: 12px; color: var(--ink-faint); max-width: 70ch; }
.fc-drop:hover, .fc-drop.is-over { border-color: var(--navy); background: var(--navy-tint); }
.fc-combine { display: flex; gap: 8px; flex-wrap: wrap; }
.fc-clear { margin-left: auto; }
.fc-list { display: grid; gap: 8px; }
.fc-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 12px; border: 1px solid var(--card-edge);
  border-radius: 14px; background: var(--glass-sunk); box-shadow: var(--card-lift); }
.fc-row.is-done { border-color: var(--ok); }
.fc-row.is-failed { border-color: var(--charge); }
.fc-name { flex: 1 1 220px; min-width: 0; display: grid; }
.fc-name b { font-size: 13.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-name span { font-size: 11.5px; color: var(--ink-faint); }
.fc-where { font-size: 11px; padding: 2px 8px; border-radius: 999px; background: var(--hover); color: var(--ink-soft); white-space: nowrap; }
.fc-where.is-browser { background: var(--ok-dim); color: var(--ok); }
.fc-to { padding: 6px 10px; border-radius: 10px; font-family: var(--sans); font-size: 13px; letter-spacing: normal; }
.fc-row .btn { padding: 6px 14px; font-size: 13px; }
.fc-msg { flex: 1 1 100%; font-size: 12.5px; color: var(--ink-soft); }
.fc-msg.is-bad { color: var(--charge); }
.fc-row.is-done .fc-msg { color: var(--ok); }
.fc-x { border: 0; background: none; color: var(--ink-faint); font-size: 18px; cursor: pointer; padding: 0 4px; }
.fc-x:hover { color: var(--ink); }
:root[data-contrast="high"] .fc-drop, :root[data-contrast="high"] .fc-row { background: #000; border-color: var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .fc-drop:hover, :root[data-contrast="high"] .fc-drop.is-over { border-color: var(--hc-active); }
/* The drop zone is a <label>; the hub styles labels as small capitals. And the format list stays its own width. */
.fc-drop, .fc-drop b, .fc-drop span { text-transform: none; letter-spacing: normal; font-family: var(--sans); }
.fc-drop span { font-weight: 400; }
.fc-row .fc-to { width: auto; flex: 0 0 auto; max-width: 240px; }
.fc-row .btn, .fc-row .fc-where, .fc-row .fc-x { flex: 0 0 auto; }

/* ══════════════════════════════════════════════════════════════════════════
   TOOLS → CONFERENCE ROOM (2026-10-06) — web/rooms.js. The tablets are web/room/.
   ══════════════════════════════════════════════════════════════════════════ */
body.app-rooms .page-head, body.app-rooms #form, body.app-rooms .commit { display: none !important; }
.cr { display: grid; gap: calc(var(--step) * 3); }
.cr-head { display: grid; grid-template-columns: 1fr auto; gap: 10px 16px; align-items: center; }
.cr-title h1 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); }
.cr-sub { font-size: 12.5px; color: var(--ink-faint); }
.cr-rooms { display: flex; gap: 6px; justify-self: end; }
.cr-room { border: 1px solid var(--rule-firm); background: var(--field); color: var(--ink-soft); border-radius: 999px; padding: 6px 16px;
  font-weight: 700; cursor: pointer; font-family: var(--sans); }
.cr-room.is-on { background: var(--navy); color: var(--navy-ink); border-color: var(--navy); }
.cr-nav { display: flex; align-items: center; gap: 6px; }
.cr-nav .btn { padding: 5px 12px; font-size: 13px; }
.cr-arrow { min-width: 34px; }
.cr-range { margin-left: 8px; font-weight: 650; color: var(--ink); font-size: 14px; }
.cr-tools { display: flex; gap: 8px; justify-self: end; }
.cr-tools .btn { padding: 6px 14px; font-size: 13px; }
.cr-flash { margin: 0; padding: 8px 12px; border-radius: 10px; background: var(--ok-dim); color: var(--ok); font-size: 13px; }
.cr-grid { display: grid; grid-template-columns: 52px repeat(7, minmax(110px, 1fr)); overflow-x: auto; border: 1px solid var(--card-edge);
  border-radius: 16px; background: var(--glass-sunk); box-shadow: var(--card-lift); }
.cr-col, .cr-times { border-left: 1px solid var(--rule); min-width: 0; }
.cr-times { border-left: 0; }
.cr-dayhead { height: 46px; display: grid; place-content: center; text-align: center; border-bottom: 1px solid var(--rule); font-size: 12px; color: var(--ink-faint); }
.cr-dayhead b { font-size: 13px; color: var(--ink); }
.cr-col.is-today .cr-dayhead { background: var(--navy-tint); }
.cr-col.is-today .cr-dayhead b { color: var(--navy); }
.cr-col-body { position: relative; }
.cr-time { position: absolute; right: 6px; transform: translateY(-50%); font-size: 10.5px; color: var(--ink-faint); white-space: nowrap; }
.cr-time:first-child { transform: none; }
.cr-slot { position: absolute; left: 0; right: 0; height: 24px; border: 0; border-top: 1px dashed transparent; background: transparent; cursor: pointer; padding: 0; }
.cr-slot.is-hour { border-top: 1px solid var(--rule); }
.cr-slot:hover { background: var(--navy-tint); }
.cr-col.is-past .cr-slot { cursor: default; background: repeating-linear-gradient(45deg, transparent 0 6px, var(--hover) 6px 7px); }
.cr-booking { position: absolute; left: 3px; right: 3px; z-index: 2; display: grid; align-content: start; gap: 0; overflow: hidden; text-align: left;
  padding: 3px 6px; border: 0; border-left: 3px solid var(--ink-faint); border-radius: 6px; background: var(--hover); color: var(--ink);
  font-family: var(--sans); font-size: 11.5px; line-height: 1.25; cursor: pointer; box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
.cr-booking b { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cr-booking span { color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cr-booking.is-mine { border-left-color: var(--navy); background: var(--navy-tint); }
.cr-booking.is-walkup { border-left-color: #E8930C; }
.cr-now { position: absolute; left: 0; right: 0; height: 2px; background: var(--charge); z-index: 3; pointer-events: none; }
.cr-now::before { content: ""; position: absolute; left: -4px; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--charge); }
.cr-modal-back { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: start center; padding: 8vh 16px; overflow-y: auto; background: var(--scrim); }
.cr-modal { width: 100%; max-width: 560px; border-radius: 18px; background: var(--menu-bg); color: var(--ink); border: 1px solid var(--card-edge); box-shadow: 0 30px 80px -20px rgba(0, 0, 0, .5); }
.cr-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.cr-modal-head h3 { margin: 0; font-size: 15px; }
.cr-x { border: 0; background: none; font-size: 22px; color: var(--ink-faint); cursor: pointer; }
.cr-modal-body { padding: 14px 16px 16px; }
.cr-form { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 12px; }
.cr-form .is-wide { grid-column: 1 / -1; }
.cr-field { display: grid; gap: 4px; text-transform: none; letter-spacing: normal; font-family: var(--sans); }
.cr-field > span { font-size: 11px; font-weight: 650; color: var(--label); text-transform: uppercase; letter-spacing: .05em; }
.cr-field input, .cr-field select, .cr-field textarea { font-family: var(--sans); font-size: 13.5px; letter-spacing: normal; }
.cr-check { display: flex; gap: 8px; align-items: center; font-size: 13px; text-transform: none; letter-spacing: normal; font-family: var(--sans); color: var(--ink); }
.cr-err { color: var(--charge); font-size: 13px; }
.cr-clashes { margin: 6px 0 0; padding-left: 18px; }
.cr-foot { display: flex; justify-content: flex-end; gap: 8px; }
.cr-danger { color: var(--charge) !important; margin-right: auto; }
.cr-when { font-weight: 650; }
.cr-notes { white-space: pre-wrap; padding: 8px 10px; border-radius: 10px; background: var(--hover); }
.cr-dim { color: var(--ink-faint); font-size: 12.5px; }
.cr-link { display: grid; grid-template-columns: 150px 1fr auto auto auto; gap: 8px; align-items: center; margin-top: 10px; font-size: 13px; }
.cr-link input { font-family: var(--mono); font-size: 12px; }
.cr-link .btn { padding: 4px 10px; font-size: 12px; }
@media (max-width: 760px) { .cr-head { grid-template-columns: 1fr; } .cr-rooms, .cr-tools { justify-self: start; } .cr-form { grid-template-columns: 1fr 1fr; } }
:root[data-contrast="high"] .cr-grid, :root[data-contrast="high"] .cr-modal { background: #000; border-color: var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .cr-booking { background: #000; outline: 1px solid rgba(111, 195, 223, .6); }
:root[data-contrast="high"] .cr-booking.is-mine { outline-color: var(--hc-active); }
.cr-booking.is-short { display: flex; align-items: center; gap: 6px; padding-top: 1px; padding-bottom: 1px; }
.cr-booking.is-short b { flex: 0 0 auto; max-width: 100%; }
.cr-booking.is-short span { flex: 1 1 0; min-width: 0; font-size: 10.5px; }
.cr-booking.is-short .cr-org { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   CUSTOMER SERVICE → ORDER 360 (2026-10-06) — web/order360.js. One VA order's eleven steps on
   one line: green done, blue in progress, amber stuck, red failed, grey not reached.
   ══════════════════════════════════════════════════════════════════════════ */
body.app-o360 .page-head, body.app-o360 #form, body.app-o360 .commit { display: none !important; }
.o3 { --o3-done: var(--ok); --o3-wait: var(--navy); --o3-stuck: #A85F00; --o3-fail: var(--charge); --o3-todo: var(--rule-firm);
  display: grid; gap: calc(var(--step) * 3); container-type: inline-size; }
:root[data-theme="dark"] .o3 { --o3-stuck: #F0B44C; --o3-wait: #7FB2FF; }
.o3-head { display: grid; grid-template-columns: 1fr minmax(260px, 460px); gap: 10px 20px; align-items: center; }
.o3-title h1 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); }
.o3-sub { font-size: 12.5px; color: var(--ink-faint); }
.o3-search { display: flex; gap: 8px; }
.o3-search input { flex: 1 1 auto; min-width: 0; font-size: 15px; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--card-edge);
  background: var(--glass-sunk); color: var(--ink); font-family: var(--sans); }
.o3-search input:focus { outline: 2px solid var(--navy); outline-offset: 1px; }
.o3-recent { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.o3-recent:empty { display: none; }
.o3-recent-label { font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .08em; margin-right: 4px; }
.o3-chip { border: 1px solid var(--card-edge); background: var(--glass-soft); color: var(--ink-soft); border-radius: 999px; padding: 3px 10px;
  font: 12px/1.4 var(--sans); cursor: pointer; }
.o3-chip:hover { background: var(--hover); color: var(--ink); }
.o3-body { display: grid; gap: calc(var(--step) * 3); }
.o3-empty, .o3-order, .o3-detail { border: 1px solid var(--card-edge); border-radius: 16px; background: var(--glass-sunk); box-shadow: var(--card-lift); padding: 18px 20px; }
.o3-empty { color: var(--ink-soft); font-size: 13.5px; line-height: 1.55; }
.o3-empty p { margin: 0 0 6px; max-width: 760px; }
.o3-empty-lead { font-size: 15px; color: var(--ink); font-weight: 600; }
.o3-loading { display: flex; align-items: center; gap: 10px; color: var(--ink-soft); padding: 18px 4px; }
.o3-spin { width: 16px; height: 16px; border-radius: 50%; border: 2px solid var(--rule); border-top-color: var(--navy); animation: o3spin .8s linear infinite; }
@keyframes o3spin { to { transform: rotate(360deg); } }
.o3-err { margin: 0; padding: 14px 16px; border-radius: 12px; background: var(--charge-dim); color: var(--charge); font-weight: 600; }
.o3-dim { color: var(--ink-faint); font-size: 12.5px; margin: 4px 0; }

/* pick one of several */
.o3-pick-lead { margin: 0; color: var(--ink-soft); }
.o3-pick { display: grid; gap: 6px; }
.o3-pick-row { display: grid; grid-template-columns: 120px 120px 110px 1fr 160px 120px; gap: 10px; align-items: center; text-align: left;
  border: 1px solid var(--card-edge); border-radius: 12px; background: var(--glass-sunk); padding: 10px 14px; cursor: pointer; font: 13px var(--sans); color: var(--ink); }
.o3-pick-row:hover { border-color: var(--navy); }

/* the order card */
.o3-order { display: grid; gap: 12px; }
.o3-order-top { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px; align-items: center; }
.o3-order-name { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.o3-order-name h2 { margin: 0; font-size: 20px; color: var(--ink); }
.o3-po { font-size: 14px; font-weight: 600; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.o3-order-tools { display: flex; flex-wrap: wrap; gap: 6px; }
.o3-order-tools a.btn { text-decoration: none; }
.o3-facts { display: flex; flex-wrap: wrap; gap: 8px 28px; margin: 0; }
.o3-facts div { display: grid; gap: 1px; }
.o3-facts dt { font-size: 11px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); }
.o3-facts dd { margin: 0; font-size: 14px; color: var(--ink); font-weight: 600; }
.o3-facts .is-bot dd { color: var(--navy); }
.o3-summary { margin: 0; padding: 10px 14px; border-radius: 10px; font-size: 14px; color: var(--ink); border-left: 4px solid var(--o3-todo); background: var(--hover); }
.o3-summary b { margin-right: 6px; }
.o3-summary.is-done { border-left-color: var(--o3-done); }
.o3-summary.is-done b { color: var(--o3-done); }
.o3-summary.is-waiting { border-left-color: var(--o3-wait); }
.o3-summary.is-waiting b { color: var(--o3-wait); }
.o3-summary.is-stuck { border-left-color: var(--o3-stuck); }
.o3-summary.is-stuck b { color: var(--o3-stuck); }
.o3-summary.is-failed { border-left-color: var(--o3-fail); }
.o3-summary.is-failed b { color: var(--o3-fail); }

/* the line of steps: across on a wide screen, down on a narrow one */
.o3-track { list-style: none; margin: 0; padding: 4px 0 0; display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); }
.o3-step { position: relative; min-width: 0; }
/* the line INTO each dot, coloured by that step's state */
.o3-step::before { content: ""; position: absolute; top: 17px; left: -50%; width: 100%; height: 3px; background: var(--o3-todo); z-index: 0; }
.o3-step:first-child::before { display: none; }
.o3-step.is-done::before, .o3-step.is-skip::before { background: var(--o3-done); }
.o3-step.is-waiting::before { background: var(--o3-wait); }
.o3-step.is-stuck::before { background: var(--o3-stuck); }
.o3-step.is-failed::before { background: var(--o3-fail); }
.o3-node { position: relative; z-index: 1; display: grid; justify-items: center; align-content: start; gap: 3px; width: 100%; padding: 0 3px 8px; border: 0;
  background: none; cursor: pointer; text-align: center; font-family: var(--sans); color: var(--ink); border-radius: 12px; }
.o3-node:hover .o3-dot { transform: scale(1.08); }
.o3-node:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.o3-dot { width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center; font-size: 17px; font-weight: 800; line-height: 1;
  background: var(--glass-sunk); border: 3px solid var(--o3-todo); color: var(--ink-faint); transition: transform .15s; }
.is-done .o3-dot { background: var(--o3-done); border-color: var(--o3-done); color: #fff; }
.is-skip .o3-dot { border-color: var(--o3-done); color: var(--o3-done); }
.is-waiting .o3-dot { border-color: var(--o3-wait); color: var(--o3-wait); font-size: 26px; animation: o3pulse 1.8s ease-in-out infinite; }
.is-stuck .o3-dot { background: var(--o3-stuck); border-color: var(--o3-stuck); color: #fff; }
.is-failed .o3-dot { background: var(--o3-fail); border-color: var(--o3-fail); color: #fff; }
@keyframes o3pulse { 50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--o3-wait) 18%, transparent); } }
@media (prefers-reduced-motion: reduce) { .is-waiting .o3-dot { animation: none; } }
.o3-step.is-sel .o3-dot { outline: 3px solid color-mix(in srgb, var(--navy) 35%, transparent); outline-offset: 3px; }
.o3-step-title { font-size: 12px; font-weight: 700; line-height: 1.25; margin-top: 4px; }
.o3-step-time { font-size: 11px; color: var(--ink-faint); font-variant-numeric: tabular-nums; min-height: 14px; }
.o3-step-sum { font-size: 11px; color: var(--ink-soft); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
.is-stuck .o3-step-sum { color: var(--o3-stuck); font-weight: 600; }
.is-failed .o3-step-sum { color: var(--o3-fail); font-weight: 600; }
.is-todo .o3-step-title { color: var(--ink-faint); }
@container (max-width: 900px) {
  .o3-track { grid-template-columns: 1fr; gap: 0; }
  .o3-step::before { top: -50%; left: 17px; width: 3px; height: 100%; }
  .o3-node { grid-template-columns: 36px auto 1fr; grid-template-rows: auto auto; justify-items: start; text-align: left; column-gap: 12px; padding: 6px 0; }
  .o3-dot { grid-row: span 2; }
  .o3-step-title { margin: 0; }
  .o3-step-time { grid-column: 3; justify-self: end; }
  .o3-step-sum { grid-column: 2 / 4; }
  .o3-head { grid-template-columns: 1fr; }
  .o3-pick-row { grid-template-columns: 1fr 1fr; }
}

/* one step, opened */
.o3-detail { display: grid; gap: 10px; border-top: 4px solid var(--o3-todo); }
.o3-detail.is-done { border-top-color: var(--o3-done); }
.o3-detail.is-waiting { border-top-color: var(--o3-wait); }
.o3-detail.is-stuck { border-top-color: var(--o3-stuck); }
.o3-detail.is-failed { border-top-color: var(--o3-fail); }
.o3-detail-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.o3-detail-head h3 { margin: 0; font-size: 17px; color: var(--ink); }
.o3-detail-n { font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .08em; }
.o3-detail-sum { margin: 0; font-size: 14px; color: var(--ink); font-weight: 600; white-space: pre-line; }
.o3-pill { display: inline-block; font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px; background: var(--hover); color: var(--ink-soft); }
.o3-pill.is-done { background: color-mix(in srgb, var(--o3-done) 16%, transparent); color: var(--o3-done); }
.o3-pill.is-waiting { background: color-mix(in srgb, var(--o3-wait) 16%, transparent); color: var(--o3-wait); }
.o3-pill.is-stuck { background: color-mix(in srgb, var(--o3-stuck) 18%, transparent); color: var(--o3-stuck); }
.o3-pill.is-failed { background: color-mix(in srgb, var(--o3-fail) 16%, transparent); color: var(--o3-fail); }
.o3-kv { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px 20px; margin: 0; }
.o3-kv div { display: grid; gap: 1px; min-width: 0; }
.o3-kv dt { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); }
.o3-kv dd { margin: 0; font-size: 13.5px; color: var(--ink); overflow-wrap: anywhere; }
.o3-table-wrap { overflow-x: auto; border: 1px solid var(--rule); border-radius: 10px; }
.o3-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.o3-table th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); font-weight: 600;
  padding: 8px 10px; border-bottom: 1px solid var(--rule); background: var(--hover); white-space: nowrap; }
.o3-table td { padding: 7px 10px; border-bottom: 1px solid var(--rule); color: var(--ink); vertical-align: top; overflow-wrap: anywhere; }
.o3-table tr:last-child td { border-bottom: 0; }
.o3-table td.is-warn { color: var(--o3-stuck); font-weight: 600; }
.o3-links { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.o3-link { color: var(--navy); font-size: 13px; font-weight: 600; text-decoration: none; }
.o3-link:hover { text-decoration: underline; }
.o3-note { padding: 8px 12px; border-radius: 10px; background: var(--hover); font-size: 13px; }
.o3-note p { margin: 2px 0 0; }
.o3-more { justify-self: start; }
.o3-more-out:empty { display: none; }
.o3-log { display: grid; gap: 6px; }
.o3-log-row { border: 1px solid var(--rule); border-radius: 10px; padding: 0 12px; }
.o3-log-row.is-bad { border-color: var(--o3-fail); }
.o3-log-row summary { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; padding: 9px 0; cursor: pointer; font-size: 13px; color: var(--ink); }
.o3-log-msg { color: var(--ink-soft); flex: 1 1 260px; min-width: 0; overflow-wrap: anywhere; }
.o3-log-row h4 { margin: 8px 0 4px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); }
.o3-pre { margin: 0 0 10px; max-height: 320px; overflow: auto; padding: 10px; border-radius: 8px; background: var(--hover); font: 12px/1.45 ui-monospace, Consolas, monospace;
  color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.o3-notes { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.o3-notes li { display: grid; grid-template-columns: 130px 1fr; gap: 12px; padding: 6px 10px; border-radius: 8px; font-size: 13px; }
.o3-notes li:nth-child(odd) { background: var(--hover); }
.o3-notes li.is-bad p { color: var(--o3-fail); font-weight: 600; }
.o3-notes time { color: var(--ink-faint); font-size: 12px; font-variant-numeric: tabular-nums; }
.o3-notes p { margin: 0; color: var(--ink); white-space: pre-line; overflow-wrap: anywhere; }
.o3-foot { margin: 0; text-align: right; font-size: 11.5px; color: var(--ink-faint); }
:root[data-contrast="high"] .o3-empty, :root[data-contrast="high"] .o3-order, :root[data-contrast="high"] .o3-detail { background: #000; border-color: var(--hc-border); box-shadow: none; }
:root[data-contrast="high"] .o3 { --o3-todo: #5a5a5a; }

/* ── Logistics → Inventory Transactions (2026-10-15; web/inventory-tx.js) ─────────────────── */
body.app-ivt .page-head, body.app-ivt #form, body.app-ivt .commit { display: none !important; }
.ivt { display: grid; gap: calc(var(--step) * 3); container-type: inline-size; }
.ivt-head { display: grid; gap: 12px; }
.ivt-title h1 { margin: 0; font-size: 22px; font-weight: 700; color: var(--ink); }
.ivt-sub { margin: 4px 0 0; font-size: 12.5px; color: var(--ink-faint); }
.ivt-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.ivt-field { display: grid; gap: 4px; font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .06em; }
.ivt-field input, .ivt-field select { font: 15px var(--sans); padding: 9px 12px; border-radius: 12px; border: 1px solid var(--card-edge);
  background: var(--glass-sunk); color: var(--ink); text-transform: none; letter-spacing: 0; }
.ivt-field input[name="sku"] { width: 220px; }
.ivt-field input:focus, .ivt-field select:focus { outline: 2px solid var(--navy); outline-offset: 1px; }
.ivt-quick, .ivt-kinds { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ivt-quick-label { font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .08em; margin-right: 4px; }
.ivt-chip { border: 1px solid var(--card-edge); background: var(--glass-soft); color: var(--ink-soft); border-radius: 999px; padding: 4px 12px;
  font: 12.5px/1.4 var(--sans); cursor: pointer; }
.ivt-chip:hover { background: var(--hover); color: var(--ink); }
.ivt-chip.is-on { background: var(--navy); border-color: var(--navy); color: #fff; }
.ivt-body { display: grid; gap: 12px; }
.ivt-empty { color: var(--ink-faint); font-size: 14px; }
.ivt-err { color: var(--charge); font-size: 14px; }
.ivt-sum { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px; padding: 16px 20px; border-radius: 16px;
  border: 1px solid var(--card-edge); background: var(--glass-soft); }
.ivt-sku { margin: 0; font-size: 20px; font-weight: 700; color: var(--ink); }
.ivt-name { margin: 2px 0 0; font-size: 13.5px; color: var(--ink-soft); }
.ivt-where { margin: 6px 0 0; font-size: 12.5px; color: var(--ink-faint); }
.ivt-figs { display: flex; flex-wrap: wrap; gap: 22px; }
.ivt-fig { display: grid; gap: 2px; }
.ivt-fig span { font-size: 11.5px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .06em; }
.ivt-fig b { font-size: 22px; color: var(--ink); font-variant-numeric: tabular-nums; }
.ivt-fig.is-up b, .ivt-table .is-up { color: var(--ok); }
.ivt-fig.is-down b, .ivt-table .is-down { color: var(--charge); }
.ivt-check { margin: 0; font-size: 13px; color: var(--ink-faint); }
.ivt-check.is-match { color: var(--ok); }
.ivt-check.is-differs { color: #A85F00; font-weight: 600; }
.ivt-wrap { overflow-x: auto; border-radius: 14px; border: 1px solid var(--card-edge); background: var(--glass-soft); }
.ivt-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.ivt-table th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint); font-weight: 600;
  padding: 10px 12px; border-bottom: 1px solid var(--card-edge); white-space: nowrap; position: sticky; top: 0; background: var(--glass-soft); }
.ivt-table td { padding: 8px 12px; border-bottom: 1px solid var(--rule, var(--card-edge)); vertical-align: top; color: var(--ink); }
.ivt-table tr:last-child td { border-bottom: 0; }
.ivt-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ivt-table .ivt-new { font-weight: 700; }
.ivt-date { white-space: nowrap; }
.ivt-table a { color: var(--navy); font-weight: 600; text-decoration: none; }
.ivt-table a:hover { text-decoration: underline; }
.ivt-kind { display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; margin-right: 6px;
  background: var(--glass-sunk); color: var(--ink-soft); }
.ivt-kind.k-sale { background: rgba(45, 108, 192, .12); color: #2D6CC0; }
.ivt-kind.k-received { background: rgba(22, 163, 74, .12); color: #15803D; }
.ivt-kind.k-adjustment { background: rgba(168, 95, 0, .12); color: #A85F00; }
.ivt-kind.k-transfer { background: rgba(124, 58, 237, .12); color: #7C3AED; }
.ivt-kind.k-return { background: rgba(220, 38, 38, .1); color: #B91C1C; }
.ivt-type, .ivt-by, .ivt-loc { display: block; font-size: 11.5px; color: var(--ink-faint); }
.ivt-memo { max-width: 340px; }
.ivt-none { color: var(--ink-faint); }
:root[data-theme="dark"] .ivt-kind.k-sale { color: #7FB2FF; }
:root[data-theme="dark"] .ivt-kind.k-received { color: #4ADE80; }
:root[data-theme="dark"] .ivt-kind.k-adjustment, :root[data-theme="dark"] .ivt-check.is-differs { color: #F0B44C; }
:root[data-theme="dark"] .ivt-kind.k-transfer { color: #C4A5FF; }
:root[data-theme="dark"] .ivt-kind.k-return { color: #FF8A8A; }
:root[data-theme="dark"] .ivt-table a { color: #7FB2FF; }
