/* GENERATED BY THE BUILD - DO NOT EDIT.
   Every rule below belongs to one file in wwwroot/css. Edit it there:
   an edit here is overwritten by the next build, and is not committed.
   The list and its order are the ComponentStylesheet item group in
   ArrayTrace.Web.csproj. */

/* page-header.css */
/*
    THE PAGE HEADER'S OWN STYLESHEET.

    Everything Components/Shared/PageHeader.razor needs and nothing else, in its own file rather than
    a section of utilities.css: the component, its stylesheet and its behaviour are one thing that
    ten screens wear, and a shared utility file is where a rule goes to be edited by somebody who has
    never opened the component. Its behaviour is js/ui/page-header.js.

    FIVE ROWS IN A FIXED ORDER - the way out, the name and its states, what can be done to it, what
    it measures, the way across - and then the page underneath. Cost of a page hand-rolling its own
    instead, which is what nine project screens used to do: five different title sizes for one
    project name, the actions level with the back link on one screen and level with the subtitle on
    the next, and the primary button moving under the pointer every time somebody walked the strip.

    NO SURFACE OF ITS OWN, and it must not grow one. The well is the page and the raised surface is
    for the cards below; a box around the header made a project the one screen in the product with a
    card inside a card, bounding nothing a reader was asking about. A title is not a container.

    WHAT IT DOES NOT OWN. .sv-back, .sv-btn, .sv-badge and .sv-project-nav are utilities several
    unrelated screens set, so they stay in utilities.css and this file composes them. Copying one in
    here to make the header self-contained would be the dual-presence defect wearing a tidy name.
*/

.sv-pagehead {
  min-width: 0;
}

/* ── The band ────────────────────────────────────────────────────────────────
   THE ROWS ARE STICKY AND THE BODY IS NOT, which is why they are wrapped. A sticky element sticks
   whole: with the body inside it the page would pin to the top of the window and scroll nothing.

   IT SITS UNDER THE SHELL'S UTILITY BAR, at that bar's own height, so the two stack into one
   continuous block of chrome. --sv-topbar-height rather than the number, because the shell owns
   that measurement and this file must not be a second place it is written down.

   IT BREAKS OUT OF THE WELL'S PADDING, so its rule reaches both edges of the main column and the
   band reads as chrome rather than as a card the page happens to start with. It pulls by exactly
   what .sv-content pushed - the well states its gutter as a custom property for this reason, and
   redefines it below the shell's breakpoint, so the pull follows with nothing having to remember.

   TRANSLUCENT WITH A BLUR, matching the bar above it: at full opacity a figure sliding underneath
   vanishes at a hard line, which reads as content being clipped rather than scrolled.
   ────────────────────────────────────────────────────────────────────────── */

.sv-pagehead__bar {
  position: sticky;
  top: var(--sv-topbar-height);

  /* ABOVE THE UTILITY BAR'S OWN 20, because the way back is drawn by this band and painted into
     that one - see .sv-pagehead__back. The two never overlap otherwise: this sticks at exactly the
     height of the bar above it, so nothing but that one child ever enters its band. */
  z-index: 21;

  margin: calc(var(--sv-space-5) * -1) calc(var(--sv-well-gutter) * -1) 0;
  padding: var(--sv-space-3) var(--sv-well-gutter) 0;
  background: color-mix(in srgb, var(--sv-bg) 94%, transparent);
  backdrop-filter: blur(6px);
  border-bottom: 1px solid var(--sv-border);
}

/* NOTHING IS CENTRED AND NOTHING IS CAPPED. The gutter is the only horizontal measurement the app
   well has: the band runs the full width of the main column so its rule is the boundary of the
   chrome, and every row inside it, the page under it and the utility bar above it are all inset by
   that one number. That is what puts them on a single left edge - a maximum width would have to be
   restated by each of the three, and each of them spends the gutter in a different place. */

/* THE WAY BACK, DRAWN BY THE PAGE AND PAINTED INTO THE BAR ABOVE IT. It belongs on the utility row,
   beside who is signed in, and it cannot be RENDERED there: the shell is a layout, layouts render
   statically, and a layout cannot know a page's back destination. A Blazor section will not carry
   it either - the static shell and the page's circuit are two render trees and two scopes.

   So it stays in the page's own markup, where the destination is declared, and is positioned one
   band up. The sticky band is its containing block, so it travels with it: pinned or in flow, it
   lands on the utility row either way. Out of flow, it also stops reserving a row - which is twenty
   pixels of chrome back, and the rule it used to reserve for (the title starting at the same pixel
   whether or not there is a link) now holds because there is nothing in the flow to differ. */
.sv-pagehead__back {
  position: absolute;
  top: calc(var(--sv-topbar-height) * -1);
  left: var(--sv-well-gutter);
  height: var(--sv-topbar-height);
  display: flex;
  align-items: center;
}

.sv-pagehead__back > .sv-back {
  margin-bottom: 0;
}

/* ROW 2 - the name, AND THE ACTIONS ARE PART OF IT. ALIGNED ON THE TITLE'S BASELINE, which is the
   only anchor that does not move: the name block is a title plus whatever the identity strip holds,
   so it is one line tall on a screen whose strip is a run of facts and two on a screen whose strip
   is a sentence. Centred, the primary action floated by half the difference; bottom-aligned it sat
   level with the sentence rather than with the name. A flex container's baseline is its first
   item's, so this is the h1's baseline against the first button's - the rule this component exists
   to hold, written as one declaration. The title takes the slack so a long name wraps rather than
   pushing the buttons off the edge. */
.sv-pagehead__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sv-space-4);
  flex-wrap: wrap;
}

/* THE TITLE AND ITS FACTS SHARE A BASELINE. Centred, a 10px uppercase label beside a 27px heading
   rides high enough to read as a superscript - the same reason .sv-supply__fact is baseline-set. */
.sv-pagehead__name {
  flex: 1 1 auto;
  display: flex;
  align-items: baseline;
  gap: var(--sv-space-3);
  flex-wrap: wrap;
  min-width: 0;
}

/* THE PAGE TITLE IS THE HEADER'S, NOT THE SCALE'S. .sv-h1 is 30px and stays 30px - the marketing
   pages and the printed sheet are set on it. A project name at the top of an instrument sits a step
   down from that: it shares its line with a run of facts and a row of controls, and at 30px it was
   the loudest thing on a screen whose subject is the figures underneath. */
.sv-pagehead__name > .sv-h1 {
  font: 700 27px/1.1 var(--sv-font-sans);
  letter-spacing: -0.02em;
}

/* THE STATE IS CENTRED ON THE TITLE, and align-self has to say so here. .sv-badge sets
   align-self: flex-start for the flex COLUMN case - where it stops a pill stretching the width of
   a card - and that declaration wins over the container's align-items, so a badge on a title line
   hung from the top of a 34px heading with the word beside it sitting six pixels lower.

   THE STATUS MARK IS CENTRED FOR A DIFFERENT REASON AND NEEDS THE SAME LINE. The row is
   baseline-set, and a wordless control has no baseline worth aligning to - the browser falls back
   to the bottom edge of its box, which hangs a dot below the foot of a 27px name. */
.sv-pagehead__name > .sv-badge,
.sv-pagehead__name > .sv-statuspick,
.sv-pagehead__name > .sv-menu--status {
  align-self: center;
}

/* What can be done to this subject, opposite its name. Kept short by the caller: one primary, at
   most one more beside it, and the rest behind the overflow menu below. */
.sv-pagehead__actions {
  display: flex;
  align-items: center;
  gap: var(--sv-space-2);
  flex: none;
}

/* WHY TWO CONTROLS HAVE APPEARED ON A ROW THAT USUALLY HOLDS THREE.

   A form whose Save is pinned to the top of the page needs to say what the Save is for, or the
   button reads as a control that was always there and the reader learns nothing from it arriving.
   The words are the point; the button beside them is what to do about it.

   AMBER, WHICH IS THIS PRODUCT'S ONE COLOUR FOR "SOMETHING IS WAITING ON YOU", and the same tone the
   badge on a tab uses. NOT red: nothing is wrong, and a form with an edit in it is the ordinary
   state of a form somebody is filling in.

   SENTENCE CASE IN THE SANS FACE, not the mono eyebrow. The eyebrow labels a control; this is a
   statement about the page, which is the same distinction the filter row's own count draws. */
.sv-pagehead__dirty {
  flex: none;
  padding: 3px var(--sv-space-3);
  border: 1px solid var(--sv-accent-underline);
  border-radius: var(--sv-radius-pill);
  background: var(--sv-accent-badge);
  color: var(--sv-accent);
  font: 500 11px/1.5 var(--sv-font-sans);
  white-space: nowrap;
}

/* THE IDENTITY STRIP - what this subject measures, in one line, in the mono face because every item
   in it is a measured value or a count. Separated by a middot rather than by a rule, because these
   are facets of one thing and not a list of several.

   ON THE TITLE'S OWN LINE, which is what it is a child of the name block for. A run of short facts
   is a caption on the name; given a row of its own it cost every screen in the product about thirty
   pixels of chrome before a reader had got to anything.

   A STRIP CARRYING PROSE STILL DROPS BELOW THE NAME, and this decides that from what is in it
   rather than from a parameter every caller has to remember - twenty-four of the forty-four screens
   put a summary sentence here, and a sentence set beside a 27px heading reads as a subtitle to it.
   :has() is the whole rule: flex-basis: 100% takes the strip to its own line inside the name block,
   where it wraps against the title rather than against the actions. */
.sv-pagehead__meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sv-space-2);
  color: var(--sv-text-tertiary);
}

.sv-pagehead__meta:has(p) {
  flex-basis: 100%;
  margin-top: var(--sv-space-1);
}

/* THE STRIP PUNCTUATES ITSELF, so a caller lists facts and never types a separator. Twenty-seven
   middots were written by hand across ten screens, each one a chance to leave one out, put two in,
   or forget the aria-hidden that keeps it out of the reading order.

   ONE FACT IS ONE CHILD. A value and the word that qualifies it - "3 MPPT", "2 members" - are one
   fact in two spans, so they go in a wrapper; anything left loose gets punctuated as though it were
   a fact of its own, which is exactly the mistake this replaces.

   The middot is plain content rather than the `content: "\00B7" / ""` form that would hide it from
   a screen reader: that syntax is not old enough to rely on, and an unsupported `content` throws
   the whole declaration away in silence - which here means no punctuation at all, on the browsers
   least likely to be the ones we test. A middot read aloud is the cheaper failure. */
.sv-pagehead__meta > * + *::before {
  content: "\00B7";
  margin-right: var(--sv-space-2);
  color: var(--sv-text-faint);
}

/* A fact in more than one span, so the strip counts it once. */
.sv-pagehead__fact {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* A page whose summary is a sentence rather than a strip of measured facts sets it as prose. The
   face is set here rather than by every caller repeating two utility classes on its own paragraph:
   twenty-one screens were doing exactly that, and one of them getting it wrong would look like a
   decision. */
.sv-pagehead__meta > p {
  margin: 0;
  font: 400 13px/1.45 var(--sv-font-sans);
  color: var(--sv-text-secondary);
}

.sv-pagehead__meta > .sv-mono,
.sv-pagehead__fact > .sv-mono {
  font: 400 12px/1.4 var(--sv-font-mono);
  color: var(--sv-text-secondary);
}

/* ROW 3 - the way across, and it is the LAST thing in the band, so the band's own rule is the
   strip's rule. The strip carries one of its own everywhere else it is drawn; inside here that
   would be a second hairline one pixel under the first. */
/* THE LAST ROW IN THE BAND OWNS THE GAP UNDER IT, and on a page with no section strip that row is
   the title's.

   The band states no bottom padding, because on every screen inside a project the strip is the last
   row and its own tabs are the footing. A SIDEBAR DESTINATION HAS NO STRIP - the projects list, the
   library, the schedules, every staff console - so the title row ends the band; and the head is
   baseline-set, so the primary action beside the title hangs below the title's own box and lands on
   the rule with a pixel to spare. Thirty-five screens draw a header with no strip, so this is one
   rule rather than a bottom padding each of them remembers.

   `:last-child` rather than a parameter the caller passes: whether there is a strip is a fact about
   the markup, and asking a page to declare it twice is the second declaration that goes stale. */
.sv-pagehead__head:last-child {
  padding-bottom: var(--sv-space-3);
}

.sv-pagehead__nav {
  margin-top: var(--sv-space-3);
}

.sv-pagehead__nav .sv-project-nav {
  margin-top: 0;
  border-bottom: 0;
}

/* UNLESS THE READ-ONLY BANNER IS RIDING WITH IT. The banner comes after the strip, so with the
   strip's own rule gone the tabs would be underlined by nothing and the band's rule would be
   forty pixels below them, under a notice. Where there is a banner the strip keeps its footing. */
.sv-pagehead__nav:has(.sv-alert) .sv-project-nav {
  border-bottom: 1px solid var(--sv-border);
}

.sv-pagehead__nav .sv-alert {
  margin-top: var(--sv-space-4);
  margin-bottom: var(--sv-space-3);
}

/* THE PAGE ITSELF, outside the band because the band is sticky and a page is not. The header
   carries it so the strip has something to be the footing OF. */
.sv-pagehead__body:not(:empty) {
  margin-top: var(--sv-space-5);
}

/* AND THE STEP FROM ONE BLOCK OF THE PAGE TO THE NEXT, which is the same step. The header is the
   first block and what the page draws under it is the rest, so the body states that gap once -
   rather than nineteen cards across nine screens each stating it, and a block that renders only
   sometimes takes its gap with it, which a sibling holding a margin cannot.

   A MARGIN RATHER THAN A FLEX GAP, deliberately. Adjacent margins collapse, so a block already
   stating one of its own keeps the larger of the two; a gap would land on top of it and stand that
   one block twice as far down the page as its neighbours. */
.sv-pagehead__body > * + * {
  margin-top: var(--sv-space-5);
}

/* THE PAGE STARTS AT THE SAME PIXEL WHATEVER IT PUTS FIRST, and this is what makes that true rather
   than usual. The step above the first block is the body's own margin, and a first block carrying a
   margin of its own was landing in one of two places depending on something no reader can see: a
   block sitting directly in here has its margin COLLAPSE into the body's and starts flush, and the
   same block inside a wrapper that establishes a formatting context - a grid, a flex row, anything
   with padding - cannot collapse and starts a whole step lower.

   `TabStrip` is where it showed. The strip states `margin: var(--sv-space-5) 0`, so the library that
   dropped it straight into the body drew it at the top of the page and the library that wrapped its
   blocks in a grid drew it 22px down, from one class, one stylesheet and no difference a diff would
   show. Zeroed here, the body's own margin is the only thing above the first block on every page. */
.sv-pagehead__body > :first-child {
  margin-top: 0;
}

/* ── The header's overflow menu ────────────────────────────────────────────
   WHERE THE REST OF THE ACTIONS GO. Six equal buttons opposite a title is a row with no primary
   action in it, so anything past the second one drops in here.

   THE SKIN ONLY. The open state is a class js/ui/page-header.js toggles on the container, which
   names that class in its own data-sv-menu attribute - which is what lets the account block in the
   top bar wear a completely different skin on the same mechanism. Never MudMenu: opening a menu
   changes nothing the server owns, so it does not go on the circuit.
   ───────────────────────────────────────────────────────────────────────── */

.sv-menu {
  position: relative;
  display: inline-flex;
  flex: none;
}

/* ONE ICON BOX, sized like every other icon control in the product - which means SQUARE, at the
   height .sv-btn states. That box is .sv-btn--icon in utilities.css, worn by the trigger in the
   markup; it is not restated here, because a second copy of a shape is how two controls in one row
   come to be different sizes. */

.sv-menu__panel {
  display: none;
  position: absolute;
  top: calc(100% + var(--sv-space-2));
  right: 0;
  z-index: 40;
  flex-direction: column;
  min-width: 168px;
  padding: var(--sv-space-1);
  background: var(--sv-bg-raised);
  border: 1px solid var(--sv-border);
  border-radius: var(--sv-radius-card);
  box-shadow: var(--sv-shadow-panel);
}

.sv-menu--open .sv-menu__panel {
  display: flex;
}

/* HOW MANY OF THE THINGS BEHIND THIS TRIGGER ARE SET, said quietly.

   NOT `.sv-tab-badge`, which is the one place a data screen shouts: filled amber, answering "is
   anybody waiting on me" from across a room. This answers "how many of these did I leave on" to
   somebody whose eyes are already on the button, and wearing the alert it read as a queue on a
   screen that has none. Mono because it is a count, tertiary because the word beside it is the
   thing being pressed. */
.sv-menu__count {
  margin-left: 6px;
  font: 500 11px/1 var(--sv-font-mono);
  color: var(--sv-text-tertiary);
}

/* AN ITEM IS A BUTTON OR A LINK and reads identically either way. An export is a navigation the
   browser does better than we can, so it is a real anchor with a download attribute - and an
   anchor arrives underlined and in the link colour unless it is told otherwise. */
.sv-menu__item {
  display: block;
  width: 100%;
  padding: 7px var(--sv-space-2);
  border: 0;
  border-radius: var(--sv-radius-control);
  background: none;
  color: var(--sv-text);
  font: 400 13px/1.4 var(--sv-font-sans);
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.sv-menu__item:hover {
  background: var(--sv-bg-overlay);
  color: var(--sv-text);
  text-decoration: none;
}

.sv-menu__item--danger {
  color: var(--sv-danger-ink);
}

.sv-menu__item--danger:hover {
  color: var(--sv-danger-ink);
}

/* ── The status chip's menu ────────────────────────────────────────────────
   THE SECOND BORROWER OF THE MECHANISM, and it declares itself one. The account block in the top
   bar was the first; this is a badge on the title line that moves the project on. Only the skin is
   here - where the panel hangs, and that its items are pills rather than words.

   IT DROPS FROM THE LEFT, because it does. The overflow menu is the last control in a row at the
   right of the header and hangs back from its own right edge; this one is the first thing after
   the title at the far left, and a panel right-aligned to it opens backwards underneath the name.
   ───────────────────────────────────────────────────────────────────────── */

.sv-menu--status .sv-menu__panel {
  right: auto;
  left: 0;

  /* THE PANEL IS THE MEASURE, NOT ITS LONGEST SENTENCE. The note underneath is prose and would set
     its own width given the chance; capped here, it wraps inside a panel whose width is settled by
     the states above it, which is the part a reader is choosing from. */
  min-width: 214px;
  max-width: 268px;
}

/* AN OPEN CONTROL STAYS LIT, so it and the panel below read as one object rather than as a menu
   that happens to be near some words. The properties are .sv-statuspick's in utilities.css and are
   only restated under the open class, which belongs to this family: the module toggles it, and
   utilities.css declaring it would be the shared stylesheet owning half of a mechanism it cannot
   see. */
.sv-menu--status.sv-menu--open .sv-statuspick {
  border-color: var(--sv-border);
  background: var(--sv-bg-overlay);
}

.sv-menu--status.sv-menu--open .sv-statuspick__caret {
  opacity: 1;
}

/* AN ITEM THAT IS A DOT AND A WORD, exactly as the control it fills is. A menu is the one place a
   whole lifecycle is seen at once, which makes it the one place the palette can be learnt - so the
   colour is carried, but in the same register as the trigger rather than in a second one. Drawn as
   pills the eight rows were eight different widths, and a list of ragged capsules is harder to
   read down than a column of dots. */
.sv-menu__item--status {
  display: flex;
  align-items: center;

  /* A BUTTON MADE A FLEX CONTAINER CENTRES ITS OWN CONTENTS, and the browser's own sheet is where
     that comes from - text-align on the item does not reach a flex line. Left unsaid, eight rows of
     dot-and-word sat centred on a panel sized by the longest of them, so the dots ran down a curve
     instead of a column, which is the one thing a list of states has to do. */
  justify-content: flex-start;

  gap: 9px;
}

/* WHERE THE PROJECT IS NOW, marked rather than dropped. Removed from the list the menu would be a
   different height and a different shape on every project, and the state somebody is moving away
   from is the one they are checking themselves against. */
.sv-menu__item--current {
  background: var(--sv-bg-overlay);
}

/* WHAT THE THING BEING PICKED DRIVES, under the rule that separates it from the picking. It is the
   FieldHelp sentence that used to sit behind the mark on a form field, and it is set at the size
   help is set at everywhere else - a definition read once, not a label read every time. The measure
   is capped because a panel sized by its longest sentence is a panel the width of the screen. */
.sv-menu__note {
  margin: var(--sv-space-1) 0 0;
  padding: var(--sv-space-2);
  border-top: 1px solid var(--sv-border-subtle);
  color: var(--sv-text-tertiary);
  font: 400 11px/1.5 var(--sv-font-sans);
  white-space: normal;
}

/* ── Below the shell's breakpoint ─────────────────────────────────────────────
   THE HEADER STACKS RATHER THAN WRAPPING ITS BUTTONS OFF THE EDGE, and the primary action takes the
   width it has - a form long enough to scroll puts its own submit button on a screen the person
   filling it in cannot see, which is the whole reason the action is up here. 1000px is the shell's
   own breakpoint, stated in utilities.css where the sidebar becomes a drawer; the two move together.
   ────────────────────────────────────────────────────────────────────────────── */

@media (max-width: 1000px) {
  /* THE BAND PULLS BY WHAT THE WELL PUSHED, and the well's vertical padding steps down at this
     width while its gutter does so through the custom property the band already reads. Only the
     top pull has to be restated.

     AND IT STOPS STICKING. The head stacks at this width - the title, then the actions at full
     width, then the strip - so a band that stays put would hold a third of a phone's screen for
     chrome the reader has already read. Sticking is what a wide screen can afford. */
  .sv-pagehead__bar {
    position: static;
    margin-top: calc(var(--sv-space-4) * -1);
  }

  /* THE WAY BACK COMES BACK INTO THE FLOW. The utility row's left end is the drawer's opener at
     this width, and the band is no longer positioned for anything to be painted out of. */
  .sv-pagehead__back {
    position: static;
    height: auto;
    min-height: 12px;
    margin-bottom: var(--sv-space-2);
  }

  .sv-pagehead__head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sv-space-3);
  }

  .sv-pagehead__actions {
    flex-wrap: wrap;
    width: 100%;
  }
}

/* ── On paper ─────────────────────────────────────────────────────────────────
   A CONTROL IS NOT FOR READING. The grid-application pack and the production report both print from
   a page that wears this header, and a way back, a row of buttons and a strip of tabs printed onto
   a document somebody files with a distributor are three things that mean nothing there. The name
   and what the subject measures do print - they are what the sheet is about.
   ────────────────────────────────────────────────────────────────────────────── */

@media print {
  .sv-pagehead__back,
  .sv-pagehead__actions,
  .sv-pagehead__nav {
    display: none;
  }

  /* A SHEET HAS NO SCROLL FOR ANYTHING TO STICK TO, and a translucent band with a blur behind it
     is a renderer's guess at what is underneath. The band goes back to being an ordinary block,
     and its rule goes with the controls it was ruling off. */
  .sv-pagehead__bar {
    position: static;
    margin: 0;
    padding: 0;
    background: none;
    backdrop-filter: none;
    border-bottom: 0;
  }

  .sv-pagehead__body:not(:empty) {
    margin-top: var(--sv-space-4);
  }
}

/* ai-mark.css */
/*
    THE AI MARK'S OWN STYLESHEET.

    Everything Components/Shared/AiMark.razor needs and nothing else, in its own file rather than a
    section of utilities.css: a shared utility file is where a rule goes to be edited by somebody
    who has never opened the component.

    AMBER, because it is the one accent and this is the one thing on a data screen that costs money
    per press. The glyph is a SHAPE, so it takes --sv-accent-fill; the letters are TEXT, so they
    take --sv-accent, which is darkened on paper where the fill fails AA. That way round or the
    light theme carries unreadable amber lettering.

    WHAT IT DOES NOT OWN. .sv-btn is a utility seventy-odd screens set and stays in utilities.css;
    the last rule here only says what the mark does when it is inside one.
*/

.sv-ai-mark {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  vertical-align: middle;
  flex: 0 0 auto;
}

.sv-ai-mark__glyph {
  width: 14px;
  height: 14px;
  fill: var(--sv-accent-fill);
  flex: 0 0 auto;
}

.sv-ai-mark__text {
  font: 500 10px/1 var(--sv-font-mono);
  letter-spacing: 0.1em;
  color: var(--sv-accent);
}

/* Inside a button the words are already there, so the glyph carries it alone and needs the optical
   nudge a leading icon always needs. */
.sv-ai-mark--compact {
  margin-right: 2px;
}

/* ON A FILLED ACCENT SURFACE THE MARK TAKES THE SURFACE'S OWN INK. The glyph is --sv-accent-fill and
   an .sv-btn--primary is filled with the same token, so a mark hosted on one is amber on amber: it
   is drawn, it is announced, and nobody can see it. currentColor rather than --sv-on-accent, so the
   mark follows whatever the host sets its text to and cannot drift from the words beside it. */
.sv-btn--primary .sv-ai-mark__glyph {
  fill: currentColor;
}

.sv-btn--primary .sv-ai-mark__text {
  color: currentColor;
}

/* site-map.css */
/*
    THE SITE MAP'S OWN STYLESHEET.

    Everything Components/Shared/SiteMap.razor needs and nothing else: the Leaflet host box, its
    fallback, and the overrides that drag Leaflet's own chrome onto this product's surfaces.

    LEAFLET SHIPS ITS OWN LIGHT CHROME. Without the overrides at the foot of this file a bright
    white zoom control and a white attribution strip sit on top of satellite imagery in both themes.
    They are written as descendants of .sv-map rather than on the bare Leaflet classes, so a map
    this component does not draw is left alone.

    NOT CSS ISOLATION. Leaflet creates these elements itself, so the scoping attribute never reaches
    them, which is the same reason the canvas modules are styled from a stylesheet rather than a
    .razor.css.
*/

.sv-map {
  position: relative;

  /* LEAFLET'S STACK IS CONFINED TO THIS BOX. Leaflet numbers its own panes from 400 and its
     controls from 800, against a sticky page head at 21 and a top bar at 20 - so a map scrolling
     under the chrome painted straight over it, tiles and zoom control included. `position: relative`
     alone does not stop that: without a z-index it establishes no stacking context, so those
     numbers are still measured against the page. `isolation` forces one with no z-index of its own,
     which leaves the map exactly where it sits in the document order and contains every number
     Leaflet invents inside this element.

     NOT A z-index ON THE CHROME. Raising the header above 1000 would win here and lose to the next
     library that picks a bigger number; the defect is unbounded numbers escaping a box, and this is
     the box. The designer is deliberately unaffected - its own overlay sits at 1001 over a canvas
     that is the whole screen, and it draws no .sv-map. */
  isolation: isolate;

  border: 1px solid var(--sv-border);
  border-radius: var(--sv-radius-card);
  overflow: hidden;

  /* The handoff has a token for exactly this surface - the ground behind the
     tiles, visible while they load and beyond the imagery's coverage. */
  background: var(--sv-map-canvas);
}

.sv-map__canvas {
  width: 100%;

  /* An explicit height, because Leaflet measures its container on creation and
     a percentage height inside a flex or grid parent resolves to zero. A map
     that renders as a grey box is nearly always this. */
  height: var(--sv-map-height, 380px);
}

.sv-map--tall .sv-map__canvas {
  height: var(--sv-map-height-tall, 560px);
}

.sv-map__fallback {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sv-space-2);
  padding: var(--sv-space-4);
  text-align: center;
  background: var(--sv-bg-raised);
}

.sv-map .leaflet-container {
  background: var(--sv-map-canvas);
  font-family: var(--sv-font-sans);
}

.sv-map .leaflet-control-attribution {
  background: color-mix(in srgb, var(--sv-bg) 82%, transparent);
  color: var(--sv-text-tertiary);
  font: 400 10px/1.4 var(--sv-font-sans);
}

.sv-map .leaflet-control-attribution a {
  color: var(--sv-text-secondary);
}

.sv-map .leaflet-bar a {
  background: var(--sv-bg-raised);
  color: var(--sv-text);
  border-bottom-color: var(--sv-border);
}

.sv-map .leaflet-bar a:hover {
  background: var(--sv-bg-overlay);
  color: var(--sv-accent);
}

/* loss-donut.css */
/*
    THE LOSS DONUT'S OWN STYLESHEET.

    Everything Components/Shared/LossDonutChart.razor needs and nothing else.

    SIX TONES, AND THE RING DRAWS NO MORE SLICES THAN IT HAS. Six blues against thirteen loss
    categories give two adjacent wedges one colour and two legend swatches nobody can tell apart, so
    the tail is gathered once into "Everything else (n)" and the ring and the list are drawn from
    that one list. The slice and swatch modifiers are numbered to match, which is why they stop
    at five.

    THE LEGEND IS A BLOCK BESIDE THE RING, never a row justified to the widget's edge: on a
    full-width proposal box that puts a percentage half a page from the name it belongs to.

    WHAT IT DOES NOT OWN. .sv-chart and .sv-measured are utilities and stay in utilities.css.
*/

.sv-donut {
  display: flex;
  align-items: center;
  gap: var(--sv-space-5);
  margin: 0;
  flex-wrap: wrap;
}

.sv-donut__svg {
  width: 200px;
  height: 200px;
  flex: 0 0 auto;
}

/* Six data tones in one family, not six brand colours. The blues are DATA - the
   same rule the canvas follows - and keeping them in one family stops a slice
   reading as "the amber one", which would imply an emphasis nobody chose. */
.sv-donut__slice--0, .sv-donut__swatch--0 { fill: var(--sv-loss-1); background: var(--sv-loss-1); }
.sv-donut__slice--1, .sv-donut__swatch--1 { fill: var(--sv-loss-2); background: var(--sv-loss-2); }
.sv-donut__slice--2, .sv-donut__swatch--2 { fill: var(--sv-loss-3); background: var(--sv-loss-3); }
.sv-donut__slice--3, .sv-donut__swatch--3 { fill: var(--sv-loss-4); background: var(--sv-loss-4); }
.sv-donut__slice--4, .sv-donut__swatch--4 { fill: var(--sv-loss-5); background: var(--sv-loss-5); }
.sv-donut__slice--5, .sv-donut__swatch--5 { fill: var(--sv-loss-6); background: var(--sv-loss-6); }

.sv-donut__total {
  font: 600 22px/1 var(--sv-font-mono);
  fill: var(--sv-text);
}

.sv-donut__caption {
  font: 400 9px/1 var(--sv-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  fill: var(--sv-text-tertiary);
}

.sv-donut__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 200px;
  min-width: 200px;
}

.sv-donut__legend-item {
  display: flex;
  align-items: center;
  gap: var(--sv-space-2);
  padding: 3px 0;
  font: 400 12px/1.4 var(--sv-font-sans);
}

.sv-donut__swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: 0 0 auto;
}

.sv-donut__legend-name {
  flex: 1 1 auto;
  color: var(--sv-text-secondary);
}

.sv-donut__legend-value {
  font-family: var(--sv-font-mono);
  color: var(--sv-text);
}

/* shade-heat-map.css */
/*
    THE PER-MODULE HEAT MAP'S OWN STYLESHEET.

    Everything Components/Shared/ShadeHeatMap.razor needs and nothing else.

    THE RAMP IS ANCHORED WHERE THE DATA LIVES, not at zero, or every array renders one flat colour.
    The stops below are the legend for that ramp, so they are drawn from the same ends the figure
    is: a scale whose labels and whose fill disagree is worse than no scale.

    WHAT IT DOES NOT OWN. The shade report's own page furniture stays with that page, and
    .sv-measured is a utility.
*/

.sv-heatmap {
  display: flex;
  flex-direction: column;
  gap: var(--sv-space-4);
}

.sv-heatmap__segment {
  margin: 0;
}

.sv-heatmap__svg {
  max-width: 100%;
  height: auto;
  margin-top: var(--sv-space-2);
}

.sv-heatmap__empty {
  font: 400 12px/1.4 var(--sv-font-sans);
  color: var(--sv-text-tertiary);
}

.sv-heatmap__scale {
  display: flex;
  align-items: center;
  gap: var(--sv-space-2);
  flex-wrap: wrap;
}

.sv-heatmap__ramp {
  display: flex;
  border-radius: 2px;
  overflow: hidden;
}

.sv-heatmap__stop {
  width: 26px;
  height: 10px;
}

.sv-heatmap__scale-ends {
  font-size: 10px;
  color: var(--sv-text-tertiary);
}

/* monthly-bars.css */
/*
    THE MONTHLY BAR CHART'S OWN STYLESHEET.

    Everything Components/Shared/MonthlyBars.razor needs and nothing else. Two screens draw it - the
    condition set's irradiance and soiling months, and the storage screen's dispatch - so it is the
    document's stylesheet rather than either page's.

    THE VALUE ABOVE EACH BAR IS MONO, because it is a measured quantity. A chart given fewer values
    than its axis has refuses and says so; it never pads with zero, because a zero-height bar labelled
    "0" claims a month produced nothing, which is a different statement from a month nobody modelled.
*/

.sv-monthly__bar {
  fill: var(--sv-module-edge);
}

/* The value above each bar. Mono, because it is a measured quantity, and a step
   up in weight from the axis ticks - it is the reading, not the scaffolding. */
.sv-monthly__values text {
  font: 500 10px/1 var(--sv-font-mono);
  fill: var(--sv-text-secondary);
}

/* brand-assets.css */
/*
    THE BRAND VOCABULARY'S OWN STYLESHEET.

    A team's own logo where the product's mark would stand, and the two previews of the file it was
    uploaded from. Components/Shared/BrandMark.razor draws the first on report sheets and proposals,
    and /app/team/branding draws the other two, so this is the document's stylesheet rather than
    either one's.

    THE MARK ITSELF CARRIES NO PLATE. No border, no background: a logo drawn for white paper sits on
    the sheet exactly as its owner drew it, and its height comes from the component so one number
    governs the logo and the fallback mark. The preview and the thumbnail are the opposite case -
    they are pictures OF a file, so they get a plate to be seen against.
*/

.sv-brand-preview {
  display: block;
  margin-top: var(--sv-space-3);
  max-width: 220px;
  max-height: 90px;
  object-fit: contain;
  background: var(--sv-bg-sunken);
  border: 1px solid var(--sv-border-subtle);
  border-radius: var(--sv-radius-card);
  padding: var(--sv-space-2);
}

/* THE BANNER'S PREVIEW SHOWS THE CROP, NOT THE FILE. The logo preview is `contain`, because what a
   firm is checking there is the artwork whole; a banner is drawn edge to edge at a fixed height and
   what they are checking is what SURVIVES that - so this one is `cover` at the same aspect the
   shared page uses. Shown `contain`, a panorama would look complete here and arrive on the page
   with its ends cut off, which is a surprise found by the client rather than by the firm. */
.sv-brand-preview--banner {
  max-width: 520px;
  width: 100%;
  height: 96px;
  max-height: none;
  object-fit: cover;
  padding: 0;
}

.sv-brand-thumb {
  width: 44px;
  height: 28px;
  object-fit: contain;
  background: var(--sv-bg-sunken);
  border: 1px solid var(--sv-border-subtle);
  border-radius: 2px;
}

/* A team's own logo standing where the product's mark stands, in a document header. No border,
   no plate and no background: this is the mark itself, not a preview of an uploaded file, and a
   logo drawn for white paper must sit on the sheet exactly as its owner drew it. The height comes
   from the component so one number governs the logo and the fallback mark. */
.sv-brand-mark {
  display: block;
  width: auto;
  object-fit: contain;
  /* A very wide mark shrinks rather than pushing the report title off the sheet. */
  flex-shrink: 1;
}

/* THE BRAND LOCKUP, and all three shells wear it: the application sidebar, the public nav and the
   panel beside a sign-in form. Named for what it is rather than for the shell it appears in, so no
   one of the three reads as its owner. */
.sv-brand-link {
  display: flex;
  align-items: center;
  gap: var(--sv-space-2);
  padding: var(--sv-space-1) var(--sv-space-2);
  color: var(--sv-text);
  font: 400 16px/1 var(--sv-font-sans);
  letter-spacing: -0.035em;
  min-width: 0;
}

.sv-brand-link:hover {
  text-decoration: none;
  color: var(--sv-text);
}

/* THE NAME IS SET IN TWO WEIGHTS, AND THAT IS THE BRAND rather than a flourish - the lockup on the
   pack is Array at 400 and Trace at 600, tracked -0.035em, with no space between the words. The
   tracking is on the link above so the mark and the word keep one measure.

   TWO ELEMENTS RATHER THAN A FONT TRICK. There is no way to weight half a text node, and a
   `font-synthesis` fake bold would be a fifth weight IBM Plex Sans already ships properly.

   The break is where the name's own second capital is, not a character count - see BrandWordmark,
   which is the one place that decides it. A one-word name simply draws at 400 and is not forced
   into a split it does not have. */
/* Nowrap and nothing else. The rail hides the word with `.sv-app--rail .sv-brand-link span`, which
   still reaches this element and the two inside it; giving it a display of its own would only be
   something that rule has to out-specify. */
.sv-wordmark {
  white-space: nowrap;
}

.sv-wordmark__lead {
  font-weight: 400;
}

.sv-wordmark__tail {
  font-weight: 600;
}

/* feedback.css */
/*
    THE FEEDBACK OVERLAY'S OWN STYLESHEET.

    The button that says "this page is wrong", the surface somebody drags an area on, and the dialog
    they fill in. Its consumers are Components/Shared/FeedbackLauncher.razor, FeedbackDialog.razor and
    js/feedback/screen-capture.js, and the launcher is mounted by MainLayout on every shell, so this
    is the document's stylesheet.

    THE SELECTION SURFACE IS BUILT BY JAVASCRIPT and styled from here, because there is nowhere else
    a colour is allowed to be. It sits above everything the product draws - Leaflet's panes and
    MudBlazor's dialogs included - since the whole point is to select over the top of them.

    THE LAUNCHER IS A BARE 34px SQUARE in the bottom-right corner, and that is a decision rather than
    a default: a wider fixture there covers a page's primary action and takes the click aimed at it.
    Everything else that wants that corner clears its height instead - the toast dock stacks above it
    and the well below adds 44px of bottom padding.

    CHROME THAT FLOATS OVER THE APPLICATION MUST NEVER REACH PAPER, which is the print block.

    NO GRADIENTS, one accent, depth by border. Same rules as everywhere.
*/

.sv-fb-launcher {
  position: fixed;
  right: var(--sv-space-2);
  bottom: var(--sv-space-2);
  z-index: 1150;                 /* under #blazor-error-ui (1200), over the app */
}

.sv-fb-launcher__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  background: var(--sv-bg-raised);
  border: 1px solid var(--sv-border);
  border-radius: var(--sv-radius-control);
  box-shadow: var(--sv-shadow-panel);
  color: var(--sv-text-secondary);
  cursor: pointer;

  /* Fades back until it is wanted. It is a permanent fixture on every page and
     an always-full-contrast square in the corner reads as an unread badge. */
  opacity: 0.65;
  transition: opacity 120ms ease, border-color 120ms ease, color 120ms ease;
}

.sv-fb-launcher__button:hover,
.sv-fb-launcher__button:focus-visible {
  opacity: 1;
  border-color: var(--sv-accent);
  color: var(--sv-accent);
}

/* The word is still in the DOM for a screen reader; only the sighted layout
   drops it. Removing it outright would leave an icon-only control with no
   accessible name. */
.sv-fb-launcher__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The launcher is mounted by the layout, so it reaches every branch of it -
   including the bare one the A4 report sheet renders on. Chrome must never reach
   paper, and a print rule is the right instrument for that: a route test in the
   layout would have to name every printable page, and the next one added would
   not be on the list. */
@media print {
  .sv-fb-launcher {
    display: none;
  }
}

/* Hidden for the duration of a capture: the dialog asking for the picture sits
   on top of the thing being photographed. Visibility rather than display, so
   nothing reflows and the page is captured exactly as the reporter had it. */
.sv-fb-hidden {
  visibility: hidden !important;
}

.sv-fb-capture {
  position: fixed;
  inset: 0;
  z-index: 3000;                 /* over Leaflet, over MudBlazor, over everything */
  cursor: crosshair;
  /* THE DIM DOES NOT FOLLOW THE THEME. It is laid over whatever the reporter is
     photographing - a map, a document, a light page - so it is the unthemed ink,
     the same token the hero scrims take. */
  background: color-mix(in srgb, var(--sv-unthemed-ink) 28%, transparent);
  touch-action: none;
  user-select: none;
}

.sv-fb-capture__hint {
  position: absolute;
  top: var(--sv-space-5);
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 14px;
  background: var(--sv-bg-raised);
  border: 1px solid var(--sv-accent);
  border-radius: var(--sv-radius-control);
  color: var(--sv-text);
  font: 400 13px/1.4 var(--sv-font-sans);
  white-space: nowrap;
}

/* The rubber band. Amber as a thin stroke, and the area inside it is left clear
   so the thing being selected stays visible while it is selected. */
.sv-fb-capture__band {
  position: absolute;
  border: 1px solid var(--sv-accent-fill);
  background: color-mix(in srgb, var(--sv-accent-fill) 10%, transparent);
  box-shadow: 0 0 0 9999px color-mix(in srgb, var(--sv-unthemed-ink) 10%, transparent);
  pointer-events: none;
}

.sv-fb-capture__size {
  position: absolute;
  padding: 2px 6px;
  background: var(--sv-accent-fill);
  border-radius: 3px;
  /* Fixed, because the fill is amber in both modes and the ink on it must be
     too - --sv-on-accent moves with the theme and this surface does not. */
  color: var(--sv-unthemed-ink);
  font: 500 11px/1.4 var(--sv-font-mono);
  pointer-events: none;
}

/* ── the dialog ───────────────────────────────────────────────────────────── */

.sv-fb-context {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.sv-fb-context .sv-mono {
  overflow-wrap: anywhere;       /* a project URL is long and must not widen the dialog */
}

.sv-fb-prior {
  display: flex;
  align-items: center;
  gap: var(--sv-space-2);
  padding: 6px 0;
  border-top: 1px solid var(--sv-border-subtle);
  min-width: 0;
}

.sv-fb-prior:first-of-type {
  border-top: 0;
}

/* The capture button and its advice sit on one row. Without this the button is a flex item that
   shrinks to its min-content and "Capture an area" wraps onto two lines. */
.sv-fb-actions {
  align-items: flex-start;
}

.sv-fb-actions > .sv-btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

.sv-fb-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sv-space-3);
  margin-top: var(--sv-space-3);
}

.sv-fb-thumb {
  position: relative;
  width: 132px;
  padding: 4px;
  background: var(--sv-bg-overlay);
  border: 1px solid var(--sv-border-subtle);
  border-radius: var(--sv-radius-control);
}

.sv-fb-thumb img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 3px;
}

.sv-fb-thumb__remove {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 22px;
  height: 22px;
  background: var(--sv-bg-raised);
  border: 1px solid var(--sv-border-strong);
  border-radius: 50%;
  color: var(--sv-text-secondary);
  font: 500 12px/1 var(--sv-font-sans);
  cursor: pointer;
}

.sv-fb-thumb__remove:hover {
  border-color: var(--sv-accent);
  color: var(--sv-accent);
}

.sv-fb-thumb__meta {
  display: block;
  padding-top: 4px;
  color: var(--sv-text-tertiary);
  font-size: 10px;
  letter-spacing: 0.04em;
}

/* loader.css */
/*
  THE LOADER: what a wait says once it has stopped being an ordinary wait.

  The product's answer to "something is happening" is already written and stays: .sv-spinner in the
  control that was pressed, and a bar over a surface that is reading. What neither of them can say
  is that this particular wait has gone on longer than the same wait normally does - a bar moves at
  the same speed for eight seconds as it does for eight hundred milliseconds - and not saying it is
  what turns a slow page into a reloaded one.

  EVERY DELAY HERE IS AN ANIMATION DELAY, NOT A TIMER. The element is in the document from the
  moment the wait starts and is simply transparent until --sv-loader-delay has passed, so nothing
  holds a timer, nothing has to dispose one, and a wait that ends inside the delay removes an
  element nobody ever saw. An element going from display:none to shown restarts its animations,
  which is what makes the navigation indicator's delay start when the navigation does.

  THE MARK IS THE ANIMATION. The one shaded cell in the logo is the shading loss the product
  computes; the loader is that shadow walking the array on the diagonal. The geometry belongs to
  ArrayTraceMark and is composed, never copied - what this file adds is which diagonal each cell
  sits on and how long the shadow takes to reach it.
*/

/* ── The surface loader ───────────────────────────────────────────────────── */

.sv-loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sv-space-2);
  padding-block: var(--sv-space-6);
  text-align: center;

  /* Transparent until the wait has earned it. `forwards` holds the end state, and the element's
     own opacity: 0 is what covers the delay - so the two together are the whole mechanism. */
  opacity: 0;
  animation: sv-loader-arrive 260ms ease-out var(--sv-loader-delay) forwards;
}

.sv-loader__note {
  margin: 0;
  color: var(--sv-text-tertiary);
}

/*
  THE SHADOW CROSSES ON THE DIAGONAL, which is the order a row of modules is actually shaded by
  something to the east of it. Each cell dips and recovers; the stagger between diagonals is what
  makes a band rather than a flicker.
*/
.sv-loader__mark .sv-mark__cell {
  animation: sv-loader-shade 1700ms ease-in-out infinite;
  animation-delay: 0ms;
}

.sv-loader__mark .sv-mark__cell--d0 {
  animation-delay: 0ms;
}

.sv-loader__mark .sv-mark__cell--d1 {
  animation-delay: 140ms;
}

.sv-loader__mark .sv-mark__cell--d2 {
  animation-delay: 280ms;
}

/* The cell that is ALREADY the shadow takes none of it. Animating the mark's shaded cell would
   dim the one part of the logo whose whole job is to be the dim one, and at 30px it disappears. */
.sv-loader__mark .sv-mark__cell--d3 {
  animation: none;
}

@keyframes sv-loader-arrive {
  from {
    opacity: 0;
    transform: translateY(4px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes sv-loader-fade {
  to {
    opacity: 1;
  }
}

@keyframes sv-loader-shade {
  0%,
  45%,
  100% {
    opacity: 1;
  }

  20% {
    opacity: 0.22;
  }
}

/* ── The navigation indicator ─────────────────────────────────────────────── */

/*
  Drawn by NavigationLoader and switched on by js/ui/nav-loader.js for exactly as long as an
  enhanced navigation is in flight. Two stages: the band says the click landed, the panel says the
  wait has become unusual, and each waits for its own token before appearing.
*/
/*
  IT REPORTS AND IT NEVER INTERCEPTS. Both halves are fixed to the window at the toast tier, so
  without this the panel would swallow every click landing under it for the whole of a slow
  navigation - and the top-centre of the window is the page header on an ordinary screen and the
  designer's own bar on a workspace. The one moment somebody most wants to press something else is
  the moment they have decided the navigation is not coming, so an indicator that takes the click
  is worse than no indicator. Inherited by both children.
*/
.sv-loader-nav {
  display: none;
  pointer-events: none;
}

.sv-loader-nav--running {
  display: block;
}

/*
  1150 is the toast and feedback-launcher tier: over the shell's own chrome, because this reports
  on the whole window rather than on anything inside it, and under #blazor-error-ui at 1200,
  because a framework error outranks a wait.
*/
.sv-loader-nav__bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1150;
  height: 2px;
  overflow: hidden;
  opacity: 0;

  /* A LONG FADE, AND THAT IS THE POINT. No delay can avoid being cut halfway by a navigation
     that finishes just past it, so what is drawn in that case is made proportional to how late
     the wait ran: a 500ms navigation shows a quarter of a 2px band and reads as nothing, and a
     three-second one has had the band at full strength for most of it. */
  animation: sv-loader-fade 400ms linear var(--sv-nav-loader-delay) forwards;
}

/* The band itself is the pseudo-element, so the travel and the appearance are two animations on
   two elements rather than two transforms fighting over one. */
.sv-loader-nav__bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--sv-accent-fill);
  transform-origin: left center;
  animation: sv-loader-crawl 1400ms ease-in-out infinite;
}

.sv-loader-nav__panel {
  position: fixed;
  top: var(--sv-space-5);
  left: 50%;
  z-index: 1150;

  /* A fixed box measures a percentage against the viewport, which is what makes this a gutter
     rather than a guess: on a phone the panel stops short of both edges instead of running off
     one of them. */
  max-width: calc(100% - var(--sv-space-6));
  transform: translateX(-50%);
  padding: var(--sv-space-3) var(--sv-space-5);
  border: 1px solid var(--sv-border);
  border-radius: var(--sv-radius-panel);
  background: var(--sv-bg-raised);
  box-shadow: var(--sv-shadow-panel);
  opacity: 0;
  animation: sv-loader-fade 220ms ease-out var(--sv-loader-delay) forwards;
}

/* The panel is what waits, so the loader inside it must not wait a second time - two delays in
   series would put the mark most of two seconds behind the click. */
.sv-loader--nav {
  flex-direction: row;
  align-items: center;
  gap: var(--sv-space-3);
  padding-block: 0;
  text-align: left;
  opacity: 1;
  animation: none;
}

@keyframes sv-loader-crawl {
  0% {
    transform: translateX(-100%) scaleX(0.35);
  }

  55% {
    transform: translateX(15%) scaleX(0.62);
  }

  100% {
    transform: translateX(100%) scaleX(0.35);
  }
}

/*
  MOTION COMES OFF AND THE INDICATOR STAYS ALIVE, which is the same trade .sv-spinner makes: a
  loader that stops moving altogether reads as a page that has stalled, and it is travel across the
  screen rather than a change of brightness that makes people ill. The band holds its full width
  and breathes, the array breathes as one instead of shading cell by cell, and the arrival keeps
  its fade and loses its 4px rise.
*/
@media (prefers-reduced-motion: reduce) {
  /* The navigation panel is excluded rather than left to chance: it is the panel that carries the
     delay there and the loader inside it stands its own down, and a bare .sv-loader here would sit
     later in the file at equal specificity and hand that animation back. */
  .sv-loader:not(.sv-loader--nav) {
    animation-name: sv-loader-fade;
  }

  .sv-loader__mark .sv-mark__cell,
  .sv-loader__mark .sv-mark__cell--d0,
  .sv-loader__mark .sv-mark__cell--d1,
  .sv-loader__mark .sv-mark__cell--d2 {
    animation: none;
  }

  .sv-loader__mark {
    animation: sv-loader-breathe 2400ms ease-in-out infinite;
  }

  .sv-loader-nav__bar::after {
    animation: sv-loader-breathe 2400ms ease-in-out infinite;
  }
}

@keyframes sv-loader-breathe {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.4;
  }
}

/* Paper has no waits. The indicator is fixed to the window, so left in it would print on page one. */
@media print {
  .sv-loader,
  .sv-loader-nav,
  .sv-loader-nav--running {
    display: none;
  }
}

/* auth-shell.css */
/*
    THE SIGN-IN SHELL'S OWN STYLESHEET.

    One thing one component needs and nothing else does: the split layout Components/Pages/Account/
    Shared/AuthShell.razor draws a sign-in, register, reset or confirmation screen on. Product case
    on the left, the form column on the right.

    IT USED TO CARRY A SECOND VOCABULARY. The scaffolded Identity screens were written in Bootstrap
    class names - .form-control, .btn, .row, .nav-link, .alert - and about two hundred lines here
    dragged those onto this product's surfaces. That is why the file was asked for by route rather
    than linked with the other component stylesheets: names that generic, in front of every page in
    the product, are a collision waiting for the next component that wants to call something a row.
    The screens are written in the product's own vocabulary now, so the reconciliation is gone, the
    route test with it, and this is an ordinary component stylesheet linked once in App.razor.
    AccountVocabularyGuardTests is what keeps the second vocabulary from growing back.

    THE SPLIT IS TWO EQUAL COLUMNS, stacks below 900px and loses the case below 700px, where a
    screenful of selling stands between somebody who has already decided and the form. Recovery
    codes, an authenticator secret and a QR do not fit a 380px column, which is what --wide is for.

    WHAT IT DOES NOT OWN. .sv-points is the arrow-led list the landing page uses too, .sv-account and
    .sv-subnav are the signed-in account nav and belong to the application shell, and the toast in
    the corner is status-message.css.
*/

.sv-auth {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  min-height: 100vh;
}

/* THE WARM HALF IS THE HALF THAT SELLS, and the white one is the half somebody works in. Drawn the
   other way round - the case on raised white, the form on the page's own grey - the brighter, more
   finished surface was the one with nothing to do on it, and the alerts a registration stacks in
   the form column sat beige-on-grey with no surface under them to be raised from. */
.sv-auth__aside {
  background: var(--sv-bg-overlay);
  border-right: 1px solid var(--sv-border-subtle);
  padding: var(--sv-space-7) var(--sv-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--sv-space-5);
  justify-content: center;
}

/* THE CASE IS A COLUMN OF PROSE, NOT A HALF OF A SCREEN. Unbounded it took the width it was given:
   on a 1920 display that is a 960px aside holding a 552px sentence, three short points and a line
   of small print, ranged against the outer edge with 340px of nothing beside them - which reads as
   a screen that failed to finish loading rather than as one half of a split.

   BOUNDED, AND RANGED TOWARD THE SEAM, so that the two halves are near enough to be read as one
   spread. Centred in its own half instead, the case ended 500px short of the form and each side sat
   talking to itself; against the seam the three gaps on the line - outer, middle, outer - come out
   within about 90px of each other at 1920 and the eye crosses the split in one move. The form stays
   centred in its own half, because it is the thing being walked up to and a control column hard
   against a rule reads as cramped however much room is behind it. */
.sv-auth__aside > * {
  width: 100%;
  max-width: 34rem;
  margin-inline-start: auto;
}

.sv-auth__panel {
  background: var(--sv-bg-raised);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sv-space-7) var(--sv-space-5);
}

/* THE FORM COLUMN IS THE SCREEN'S RHYTHM, and it states it once. Everything a sign-in, register,
   reset or confirmation page puts here is a block in a column - a status alert, a form, a row of
   alternatives, the way back - so no page states a gap of its own, and one that renders only
   sometimes takes its gap with it rather than leaving the block above it with a margin against
   nothing. */
.sv-auth__form {
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  gap: var(--sv-space-5);
}

/* The line under the heading belongs to the heading, not to the body below it, which is why the two
   are one block rather than the column's first two children. With no line the column's own gap is
   what the screen keeps - the same spacing the shell used to buy with an empty div of a stated
   height, so that a page with a subtitle and a page without began at the same pixel. */
.sv-auth__head {
  display: flex;
  flex-direction: column;
  gap: var(--sv-space-2);
}

/* Both columns state their gaps, so a paragraph in either carries none of its own. */
.sv-auth__form > p,
.sv-auth__head > p {
  margin: 0;
}

/* Recovery codes, an authenticator secret and a QR do not fit a 380 px column, and the answer is a
   wider column rather than a page that opts out of the shell. */
.sv-auth__form--wide {
  max-width: 560px;
}

/* THE SPLIT STACKS BELOW 900px, and the rule lives here rather than in a shared responsive block:
   a breakpoint about this layout that this layout's own stylesheet does not carry is a rule nobody
   reading it will find. */
@media (max-width: 900px) {
  .sv-auth {
    grid-template-columns: minmax(0, 1fr);
  }

  .sv-auth__aside {
    border-right: 0;
    border-bottom: 1px solid var(--sv-border-subtle);
  }

  /* STACKED, THERE IS NO SEAM TO RANGE AGAINST. The case is a band across the top of the screen
     rather than the far side of a split, so its measure starts where every other line on the page
     starts; held against the inline end it would hang off the right of a band with the mark above
     it ranged left. */
  .sv-auth__aside > * {
    margin-inline-start: 0;
  }
}

/* ── The form is the screen on a phone ────────────────────────────────────────
   THE CASE FOR THE PRODUCT IS THE OTHER HALF OF A SPLIT, and stacked it is a screenful of selling
   in front of somebody who has already decided: a heading, three points and the residency line
   filled the whole of a 375px screen and the form began below the fold. Somebody arriving here is
   signing in, and half of them are here because they could not get in.

   WHAT STAYS IS WHAT IS NOT AN ARGUMENT. The mark says which product this is and is the only way
   back to the site from a bare shell; the residency line is the operator's own statement and this
   shell has no footer to carry it. The heading and the points are the argument, and they are the
   part that goes.

   THE ROWS ARE STATED because the grid is `min-height: 100vh`: two auto rows share that height
   between them, so the band keeps a third of the screen for a mark and one sentence.
   ------------------------------------------------------------------------ */
@media (max-width: 700px) {
  .sv-auth {
    grid-template-rows: auto 1fr;
  }

  .sv-auth__aside {
    padding: var(--sv-space-4);
    gap: var(--sv-space-2);
    justify-content: flex-start;
  }

  /* THE PITCH IS MATCHED BY ITS FACE, NOT BY ITS TAG. It stopped being an h1 when the shell moved
     the heading into the form column so that navigation focus announced the screen rather than the
     sales argument, and this selector went on naming the element it used to be - matching nothing,
     refusing nothing, and leaving a phone with the screenful of selling the rule above exists to
     remove. A tag selector over an element whose tag is chosen for what announces it is a rule
     that goes stale in silence the first time somebody changes the one without the other. */
  .sv-auth__aside > .sv-h1,
  .sv-auth__aside > .sv-points {
    display: none;
  }

  /* The form starts at the top of what is left rather than centred in it: centred, a short form
     floats and a long one is cut off at both ends by the same rule. */
  .sv-auth__panel {
    padding: var(--sv-space-5) var(--sv-space-4) var(--sv-space-6);
    align-items: flex-start;
  }
}

/* THE OUTCOME COLUMN'S SEQUENCE. A screen reporting a state the visitor cannot act on has no form
   to carry it, and written as paragraphs it reads as an apology: three sentences of one weight, the
   loudest of which is what the reader CANNOT do. The same facts drawn as a position - what is
   already done, where they are standing, what happens without them - is what turns "there is
   nothing to do" from a dead end into a place in a queue, and it is the shape any held, pending or
   expired state on this shell wants.

   MARKS, RULES AND INK ONLY. The type comes off the scale on the markup, as every screen's file
   does. */
.sv-auth__steps {
  display: flex;
  flex-direction: column;
  list-style: none;
  margin: 0;
  padding: 0;
}

.sv-auth__step {
  position: relative;
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: 0 var(--sv-space-3);
  padding-bottom: var(--sv-space-4);
}

/* Nothing follows the last step, so it keeps no gap under it and grows no connector. */
.sv-auth__step:last-child {
  padding-bottom: 0;
}

/* THE CONNECTOR IS WHAT MAKES IT A SEQUENCE rather than three bulleted lines, and it is drawn from
   the step rather than from the mark: a mark's own pseudo-element would have to reach a height only
   the step knows, which is a calc against a note whose length is the operator's. */
.sv-auth__step:not(:last-child)::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 21px;
  bottom: 2px;
  width: 1px;
  background: var(--sv-border);
}

/* THREE STATES AS THREE SHAPES, not three greys: filled for what is done, ringed for where the
   reader stands, hollow for what has not happened. A reader who cannot separate the tones still
   reads the sequence. */
.sv-auth__step-mark {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border: 1px solid var(--sv-border);
  border-radius: 50%;
  background: var(--sv-bg-raised);
}

/* The tick is two borders on a rotated box rather than a glyph, because a glyph would need a size
   and this file states no type. --sv-on-accent is the ink the fill is paired with everywhere. */
.sv-auth__step--done .sv-auth__step-mark {
  border-color: var(--sv-accent-fill);
  background: var(--sv-accent-fill);
}

.sv-auth__step--done .sv-auth__step-mark::before {
  content: '';
  width: 4px;
  height: 7px;
  margin-top: -2px;
  border: solid var(--sv-on-accent);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}

.sv-auth__step--now .sv-auth__step-mark {
  border-color: var(--sv-accent-fill);
}

.sv-auth__step--now .sv-auth__step-mark::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--sv-accent-fill);
}

.sv-auth__step-name {
  display: block;
  color: var(--sv-text);
}

/* What has not happened is set a step back, so the eye lands on where the reader is. */
.sv-auth__step--next .sv-auth__step-name {
  color: var(--sv-text-secondary);
}

.sv-auth__step-note {
  margin: var(--sv-space-1) 0 0;
  color: var(--sv-text-secondary);
}

/* THE WAY ON IS A CONTROL, not a link in the small print. A screen whose only affordance is the
   shell's own back link reads as a page that ran out; two buttons sharing the row read as a choice.
   They share it evenly rather than sizing to their labels, which at 380px is the difference between
   a pair of buttons and one button with something after it. */
.sv-auth__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sv-space-2);
}

.sv-auth__actions > .sv-btn {
  flex: 1 1 0;
}

