﻿html {
    position: fixed;
    font-size: 16px;
    font-family: 'Roboto', Arial, sans-serif;
    height: 100dvh;
    width: 100dvw;
    overflow-x: hidden;
    overflow-y: hidden;
    margin: 0px;
    padding: 0px;
    /* Turns off double-tap zoom, which a fast second tap onto new text sets off on a phone; pinch zoom stays. */
    touch-action: manipulation;
}

/*
   All three layers of the backdrop live here, on ONE element, so they paint
   in the same frame.

   BOTH LAYERS BELONG ON <body>, TOGETHER. Putting the vignette on
   .radial_background - a div inside Home.razor - while the colour wash stays
   here splits the backdrop across two elements, and with prerender:false that
   div does not exist until the circuit connects: the wash paints from the
   static HTML immediately and the vignette drops on top of it a moment later.
   Two paints, visibly.

   Layer order is first-on-top: vignette over the blue and violet washes,
   over the base colour.
*/
body {
    position: fixed;
    background: radial-gradient(circle at center, rgba(0, 0, 0, 0) 66%, rgba(0, 0, 0, 0.12) 80%, rgba(0, 0, 0, 1)), radial-gradient(circle at 20% 20%, #1e40af 0%, transparent 45%), radial-gradient(circle at 80% 80%, #7c3aed 0%, transparent 45%), #050816;
    height: 100dvh;
    width: 100dvw;
    overflow-x: hidden;
    overflow-y: hidden;
    margin: 0px;
    padding: 0px;
}

/*
   .btn-link is Bootstrap's, and nothing in this project applies it yet. It
   is listed here so a link-styled button matches a real link the day one is
   written, which is the same bet the validation rules at the bottom make.
*/
a, a:link, .btn-link {
    color: white;
    text-decoration: underline;
    text-decoration-color: rgba(255,204,102,0.5);
    text-decoration-thickness: 2px;
    text-shadow: 0px 0px 2px rgba(0,0,0,0.7);
    transition: all 0.2s ease;
}

    a:hover, .btn-link:hover {
        cursor: pointer;
        text-decoration-color: rgba(255,204,102,1);
    }

/*
   =====================================================================
   THE GOLD AURA - the standing signal that a thing can be pressed.

   WHY IT LOOKS THE WAY IT DOES. Every number here was arrived at by putting
   it on screen and looking, not by reasoning about how it would appear -
   which is the only method that works for this, because none of these
   values is wrong in isolation. They are wrong against THIS backdrop, next
   to THIS chrome. See _doc/README_Client_Windows.md.

   THIS FILE IS THE ONLY PLACE THE VALUES LIVE. Five other files consume
   them and none restates one: WindowFrame.razor.css (.window_button,
   .close_button, .window_content), WindowLauncher.razor.css
   (.launch_button), MinimizedWindowGroupList.razor.css
   (.window_drawer_entry), SettingsMenuForm.razor.css (.settings_entry) and
   Toolbar/ToolbarItem.razor, which changed variant when .btn-primary
   changed meaning. Tuning the interface's buttons is one edit here.

   WHAT IT IS. A soft gold glow at rest, brightening on hover, with the
   border going gold while the control is held down. It is what the window
   chrome has always worn - .window_button in WindowFrame.razor.css,
   .launch_button in WindowLauncher.razor.css, .window_drawer_entry in
   MinimizedWindowGroupList.razor.css - and the whole value of it is that
   it is the SAME signal everywhere, so "this responds to a press" is
   learned once rather than per screen.

   EVERY CONSUMER READS THESE TOKENS, AND NOTHING RESTATES THEM. There is
   one aura in this application at one set of numbers:

	.window_button          WindowFrame.razor.css
	.launch_button          WindowLauncher.razor.css
	.window_drawer_entry    MinimizedWindowGroupList.razor.css
	.btn                    here, and therefore every button in the app
	.settings_entry         SettingsMenuForm.razor.css

   THAT IS THE WHOLE ARGUMENT FOR TOKENS, and it is stated as a fact rather
   than a prediction: three components each spelling their glow out had
   already drifted apart by the time anyone compared them. Nobody decides
   that - it is what three copies of one value do. Tuning the aura is a
   single edit here, and no consumer can be left behind.

   THE CHECK THAT PROVES IT IS A LIVE ONE. Every consumer must report the
   IDENTICAL computed string, at every state, in a real browser. That is a
   question a browser answers and a code review does not.

   EVERY CONSUMER ANSWERS A PLAIN :active, never :hover:active - which can
   only match a mouse, so a keyboard Space or Enter would get no
   acknowledgement at all.

   WHAT IS DELIBERATELY *NOT* WEARING IT: Components/UI/CheckBox, which is
   a <button> stripped of border and background on purpose ("a control the
   user reads, not a call to action competing with Submit") - an aura needs
   a rim to sit outside of, and a glowing rectangle around a line of text
   is not that. Nor .launcher_view_button and .launcher_tile, which live
   inside the launcher menu: a grid of tiles each carrying a glow is the
   haze this was just tuned away from, and .launcher_view_active already
   spends a gold border on "this is the view you are in", which would
   collide with the aura's "held down".

   The hue is carried as bare channels so one number governs every alpha.
   =====================================================================
*/
:root {
    --aura-hue: 255, 204, 102;
    /*
       THE DARK RIM THE GLOW SITS OUTSIDE OF, and it is what makes the aura
       read as an aura. A box-shadow is painted immediately outside the
       border box, so with nothing separating the control's own fill from
       the glow the two blend and it looks like a fuzzy edge rather than a
       halo around a defined object. All three chrome controls have carried
       this from the beginning - .window_button, .launch_button and
       .window_drawer_entry are each 1px solid black - and leaving it off
       the application's own buttons is exactly what looked wrong.

       1px is not a token: Bootstrap's --bs-btn-border-width is already 1px
       and so is every chrome control, so there is nothing to reconcile.
  */
    --aura-border: black;
    /*
       THE GEOMETRY, AND HOW IT ARRIVED HERE. The two numbers do different
       jobs and reaching for the wrong one is how "too strong" gets answered
       by making the glow blurrier instead of smaller:

           SPREAD is the solid extent of the glow. It is what makes an aura
                  feel heavy, and it is the first dial to turn.
           BLUR   only decides how softly that edge ends.

       Both came down, in two passes, after the user reported
       the aura was "a bit too strong" once it was on everything. The
       original figures were the chrome's own, chosen for a handful of 26px
       pills spaced along a titlebar and a drawer; a screenful of full-width
       buttons and cards is a different picture, because the glows are no
       longer isolated from one another and what reads as a highlight on one
       control reads as haze over a form.

           was    2px blur / 2px spread, into 5px / 2px on hover
           then   spread down one:      2px/1px into 5px/1px
           now    hover blur down one:  as below

       Visible glow is roughly spread + blur/2, so rest went from about 3px
       to 2px, and hover from about 4.5px to 3px.

       IF IT IS STILL TOO STRONG the next dial is the rest alpha, 0.5 ->
       0.35. Size before colour: the hue is what ties this to the rest of
       the interface and should be the last thing to move.
  */
    /* At rest: present but quiet, so a screenful of controls does not glare. */
    --aura-rest: rgba(var(--aura-hue), 0.5) 0 0 2px 1px;
    /* Hover: full strength and a wider blur. Brightness AND size, because
       a change in only one of them is easy to miss on a small control.
       The RIM STAYS BLACK through hover - the chrome only ever swaps it on
       :active, so brightening it here would be a second signal saying the
       same thing and would leave nothing left to say for the press. */
    --aura-hover: rgba(var(--aura-hue), 1) 0 0 4px 1px;
    /* Held down: the rim itself goes gold - the one part of a press that is
       a hard edge rather than a glow. Named as the pair of --aura-border
       rather than as an "edge", because two tokens a shade apart in meaning
       and a shade apart in name is how the wrong one gets used. */
    --aura-border-active: rgba(var(--aura-hue), 1);
    /* Keyboard focus, matching what CheckBox, SettingsMenu and
       TermsAgreement already draw. A solid ring OUTSIDE the glow rather
       than simply a brighter glow, because focus has to be tellable apart
       from hover and one diffuse shadow is not tellable from another. */
    --aura-focus-ring: 2px solid rgba(var(--aura-hue), 0.9);
    --aura-focus-offset: 2px;
    /*
       HOW FAR THE AURA REACHES PAST A CONTROL'S BORDER BOX, and therefore
       the room a container has to leave INSIDE its clip box for anything it
       holds.

       It is the widest of the three states, which is the focus ring: 2px of
       offset plus 2px of outline. The rest glow reaches 2px (spread 1 plus
       half of a 2px blur) and the hover and active glow 3px (spread 1 plus
       half of a 4px blur), so both fit inside it. It is a literal rather
       than a calc because the ring's width is inside the shorthand above
       and cannot be read back out - if that shorthand's width changes, this
       changes with it, exactly as --aura-focus-inset does.

       IT EXISTS BECAUSE THE SAME DEFECT WAS FOUND TWICE IN ONE DAY, one
       layer apart. The titlebar's button strip had no leading padding, so
       the Dock button's aura was cut down its left side; `.window_content`
       had 3px where 4px was needed, so the first control in every form
       window - the Login button, every text field - was cut down its left
       side too. Both were a container leaving less room than the ink needs,
       and neither is visible until you look for it.

       DO NOT SOLVE THIS WITH `overflow-clip-margin`. WebKit does not
       implement it, so Safari and every browser on iOS and iPadOS ignore it
       entirely. Room inside the box is the only answer that works on every
       engine; a clip margin is a bonus on top.
  */
    --aura-reach: 4px;
    /*
       THE INSET OFFSET, for a control with no room OUTSIDE itself. A ring
       at the positive offset above is drawn 4px beyond the border box
       (2px offset + 2px width), which is fine for a button sitting in a
       strip that clips generously - and wrong for an element that fills its
       container, because whatever clips that container cuts the ring off
       and the focus indicator simply vanishes. .window_content is the case:
       it fills the window body, and .window_container is overflow: hidden.

       Kept beside its positive twin, and the two are a PAIR: this is the
       negative of the ring's own width, so changing that width means
       changing both.
  */
    --aura-focus-inset: -2px;
}

/*
   =====================================================================
   THE BUTTON SURFACE - what a button is MADE of, as opposed to what the
   aura says about it.

   THE SURFACE IS OURS TOO, not just the aura. Bootstrap's palette is a
   different product's, and no amount of gold around the outside fixes a
   grey fill: a window full of `.btn-secondary` is a row of flat #6c757d
   slabs whatever the glow does.

   THE ANSWER IS THE CHROME'S OWN SURFACE, because it already existed and
   already worked: a translucent dark pill on the backdrop, a black rim,
   and the aura outside that. .window_button has been drawn that way from
   the beginning and .window_drawer_entry copied it; these tokens are now
   the single source for all three plus every .btn.

   TRANSLUCENT RATHER THAN A FLAT COLOUR IS THE WHOLE POINT. The backdrop
   is a radial wash of blue and violet over near-black, so a button that
   lets it through sits IN the page, while an opaque grey sits on top of
   it. That is the difference being complained about, and it is why the
   value is an rgba and not a hex.
   =====================================================================
*/
:root {
    /*
       .window_button's own fill, now shared. Dark enough to carry white
       text at any point on the backdrop, transparent enough that the wash
       still reads through it.
  */
    --button-bg: rgba(64, 64, 64, 0.5);
    /*
       THE DEFAULT ACTION - lighter AND less translucent, which is two dials
       moving together on purpose: over a near-black backdrop the base fill
       composites to about rgb(34,34,34) and this to about rgb(68,68,68), so
       it reads as a more solid object rather than merely a paler one.

       WHAT EARNS IT: being the button ENTER presses, and there is at most
       one of those in a section. That is Section's own Submit, which is the
       only `type="submit"` in the application; the Complete windows and
       TermsAgreement carry .btn-primary on the single action their window
       exists to offer, which is the same idea in a section that is not a
       form. Everything else - the Forgot pair, "I Do Not Agree", Resend
       Code, and every DataGrid paginator and filter toggle - is
       .btn-secondary and stays at --button-bg.

       SO .btn-primary NOW MEANS SOMETHING ENFORCEABLE BY READING: one per
       section. A second one in the same section is a bug, not a style
       choice, because two buttons cannot both be what Enter does.

       Tuning: raise the alpha for "more solid", raise the grey for
       "brighter". They are independent and both are legible on this
       backdrop up to about rgba(128,128,128,0.8) before it stops reading as
       the same family of button.
  */
    --button-bg-primary: rgba(96, 96, 96, 0.7);
    /*
       DESTRUCTIVE, and it is .close_button's fill rather than a new
       colour. The chrome already had to answer "how does a button say
       this one is different" and answered it with a red tint at the same
       alpha, which is why Close does not look like a fifth titlebar
       button. .btn-danger gets the same, so "Sign out everywhere else"
       keeps saying what it does.
  */
    --button-bg-danger: rgba(255, 0, 0, 0.5);
    /*
       White rather than --bs-body-color. The fill is translucent dark
       whatever the theme says, so the text on it has to be light - and
       .btn's default is --bs-body-color, which on a light theme would be
       near-black on a dark pill.
  */
    --button-color: #fff;
    /*
       10px, which is .window_button's and .window_drawer_entry's. It also
       overrides .btn-sm's smaller radius, deliberately: a small button is
       still the same kind of object.
  */
    --button-radius: 10px;
}

/*
   Bootstrap's .btn, which is what every button in this application wears:
   Section's Submit, every Return, Login's two Forgot buttons, the consent
   dialog's pair, DataGrid's paging arrows and filter toggles, and User
   Sessions' three sign-out buttons.

   HERE RATHER THAN IN A COMPONENT, for the same reason .btn-link above is
   here - the class is somebody else's and a dozen components wear it, so a
   rule in any one .razor.css would be scoped to that component's own
   markup and silently miss the rest.

   .btn-link IS EXCLUDED THROUGHOUT. It is styled as a link a few rules up
   - white text with a gold underline - and a link wearing a button's glow
   would be claiming to be both at once. Nothing applies it yet; the
   exclusion is written now so that the day something does, it still reads
   as a link.

   ORDERING AND SPECIFICITY ARE LOAD-BEARING, and both were read off the
   vendored Bootstrap 5.3.3 rather than assumed:

   - app.css loads AFTER bootstrap.min.css (App.razor), so a tie on
     specificity is broken in our favour. Do not reorder those two links.
   - The ONLY box-shadow Bootstrap puts on a button is
     `var(--bs-btn-focus-box-shadow)`, and it declares that variable on
     .btn at (0,1,0). Redefining it at (0,2,0) turns every focus path it
     has - including the .btn-check ones nothing here uses yet - gold,
     without a rule of ours per selector.
   - :hover, :active and :focus-visible are written at (0,3,0) so they beat
     Bootstrap's `.btn:first-child:active` (0,3,0, earlier) and
     `.btn:focus-visible` (0,2,0).
   - The disabled rule is LAST on purpose: it ties with :hover on
     specificity, and a tie is broken by source order.

   No transition is declared. Bootstrap's own already names box-shadow
   among the four properties it animates - and nothing here should reach
   for `all`, which is the rule the launcher's tiles record the hard way.
*/
.btn:not(.btn-link) {
    box-shadow: var(--aura-rest);
    --bs-btn-focus-box-shadow: var(--aura-hover);
    /*
       THE RIM IS SET THROUGH BOOTSTRAP'S OWN VARIABLES, NOT AS A
       border-color OF OURS, and that is what keeps this to four lines.

       Bootstrap paints a button's border from --bs-btn-border-color and
       swaps it for the hover, active and disabled ones across half a dozen
       selectors, several of them at (0,3,0) - `.btn:first-child:active`,
       `:not(.btn-check) + .btn:active`, `.btn.active`, `.btn.show`.
       Overriding the VALUES means every one of those paths paints the rim
       we want, with no rule of ours per selector and no specificity to
       lose. It is the same move the focus box-shadow above makes.

       The variants declare these at (0,1,0) - .btn-secondary and friends -
       so this rule at (0,2,0) beats all of them.

       ONE FAMILY THIS WOULD BE WRONG FOR: .btn-outline-*, whose whole
       design is a transparent fill with a COLOURED border. Nothing in this
       application uses one, so the selector is left readable rather than
       carrying an exclusion for a case that does not exist; if an outline
       button is ever added, exclude it here rather than fighting it there.
  */
    --bs-btn-border-color: var(--aura-border);
    --bs-btn-hover-border-color: var(--aura-border);
    --bs-btn-disabled-border-color: var(--aura-border);
    --bs-btn-active-border-color: var(--aura-border-active);
    /*
       THE FILL DOES NOT MOVE - NOT ON HOVER, NOT ON PRESS. A background
       shifting under the cursor reads as the button ITSELF changing rather
       than as the button being pointed at; the aura says everything.

       Bootstrap darkens a button twice as you use it: .btn:hover paints
       --bs-btn-hover-bg and the active selectors paint --bs-btn-active-bg,
       so .btn-primary walks #0d6efd -> #0b5ed7 -> #0a58ca. None of the
       chrome does that. .window_button, .launch_button and
       .window_drawer_entry hold one background for their whole life and
       say everything through the aura: the glow brightens on hover, the
       rim goes gold on press. Two signals for one state is one more than
       the interface needs, and it is the fill - not the glow - that reads
       as the button itself changing.

       Pointing each at its own resting value is what neutralises it, and
       it is deliberately NOT `transparent`: --bs-btn-bg resolves per
       variant, so .btn-primary keeps its blue and .btn-danger its red.
       Bootstrap's :focus-visible also paints --bs-btn-hover-bg, so that
       path is covered by the same line.

       The COLOUR pair goes with them for completeness. It changes nothing
       today - every variant in use has --bs-btn-color and
       --bs-btn-hover-color both #fff - but a variant where they differ
       would reintroduce exactly the flicker this removes.

       Without these six lines Bootstrap's own hover and active backgrounds
       apply, and the fill moves twice as a button is used.
  */
    --bs-btn-hover-bg: var(--bs-btn-bg);
    --bs-btn-active-bg: var(--bs-btn-bg);
    --bs-btn-hover-color: var(--bs-btn-color);
    --bs-btn-active-color: var(--bs-btn-color);
    /*
       AND THE SURFACE ITSELF - see "THE BUTTON SURFACE" above for why.
       This is what makes .btn-primary, .btn-secondary and .btn-sm stop
       being Bootstrap's blue, grey and small-radius and start being this
       product's one button. The markup does not change: every button in
       the application already carries .btn, and the variant class beside
       it now decides nothing except whether it is destructive.

       The variants declare all five at (0,1,0), so this rule at (0,2,0)
       beats every one of them. The hover and active pairs above resolve
       through --bs-btn-bg, so they follow this automatically rather than
       needing their own values.
  */
    --bs-btn-bg: var(--button-bg);
    --bs-btn-color: var(--button-color);
    --bs-btn-disabled-bg: var(--button-bg);
    --bs-btn-disabled-color: var(--button-color);
    --bs-btn-border-radius: var(--button-radius);
}

/*
   THE DEFAULT ACTION. See --button-bg-primary for what earns this and why
   there is at most one per section: it is the button Enter presses.

   Fill only. The rim, the radius, the aura and all three of its states are
   the same as every other button's - being the default action is a matter
   of weight, not of being a different kind of control, and giving it a
   louder aura as well would spend the one signal that means "pressable" on
   something else.

   (0,2,0), the same as .btn:not(.btn-link) above, so it wins by being
   later - and it is mutually exclusive with .btn-danger below in practice,
   so the order of those two relative to each other does not matter.
*/
.btn-primary:not(.btn-link) {
    --bs-btn-bg: var(--button-bg-primary);
    --bs-btn-disabled-bg: var(--button-bg-primary);
}

/*
   THE ONE VARIANT THAT STILL MEANS SOMETHING BEYOND WEIGHT. Primary above
   is louder; this one is a different STATEMENT. Flattening it would be the
   one place this change could do harm - "Sign out everywhere else" is the
   only button in the application that ends something the user cannot get
   back.

   A TINT AT THE SAME ALPHA, not a different kind of button: black rim,
   same aura, same radius, same translucency. Exactly what .close_button
   does in the titlebar, and for the same reason.

   After .btn:not(.btn-link) rather than before it - the two selectors are
   both (0,2,0), so this only wins by being later. --bs-btn-hover-bg and
   --bs-btn-active-bg are declared up there as var(--bs-btn-bg), and custom
   properties resolve per element, so they pick this value up on a danger
   button without being restated.
*/
.btn-danger:not(.btn-link) {
    --bs-btn-bg: var(--button-bg-danger);
    --bs-btn-disabled-bg: var(--button-bg-danger);
}

.btn:not(.btn-link):hover {
    box-shadow: var(--aura-hover);
}

/*
   The rim on :active is already handled by --bs-btn-active-border-color
   above; this restates it as a CATCH-ALL, because Bootstrap's active
   selectors are `.btn:first-child:active` and `:not(.btn-check) + .btn:active`
   and neither reaches a button that is neither the first child nor preceded
   by an element sibling. Cheap insurance against a markup shape nobody
   thought about, and it is the rule that carries the box-shadow anyway.
*/
.btn:not(.btn-link):active {
    border-color: var(--aura-border-active);
    box-shadow: var(--aura-hover);
}

.btn:not(.btn-link):focus-visible {
    box-shadow: var(--aura-hover);
    /* Bootstrap sets `outline: 0` on this selector; this puts one back. */
    outline: var(--aura-focus-ring);
    outline-offset: var(--aura-focus-offset);
}

/*
   NO AURA ON A BUTTON THAT CANNOT BE PRESSED, and that is not tidiness.
   The aura's whole meaning is "this responds to a press", so a disabled
   button wearing it is an invitation the button then refuses - and that is
   the common case rather than a rare one: Section keeps its Submit
   disabled until the form validates, so every half-filled form in the
   application would be glowing at a control that does nothing. Sessions
   disables its row buttons the same way while a revoke is in flight.

   Bootstrap already sets pointer-events: none on a disabled button, so
   :hover cannot fire on one either; this covers the rest state, and being
   last covers the tie with :hover if that ever changes.
*/
.btn:not(.btn-link):disabled,
.btn:not(.btn-link).disabled {
    box-shadow: none;
}

/*
   TEXT INPUTS, so a form does not sit there glowing Bootstrap blue in a
   gold interface. This was the last blue left in the
   application: `.form-control:focus` painted a `#86b7fe` border and a
   `0 0 0 .25rem rgba(13,110,253,.25)` ring, which is Bootstrap's accent and
   not this product's.

   A LIKE-FOR-LIKE SWAP RATHER THAN THE BUTTON TREATMENT. Bootstrap says
   focus with a border colour plus a ring; this says the same two things in
   gold. The buttons' AURA is deliberately not applied - the aura means
   "this responds to a press", and an input is typed into rather than
   pressed, so it would be claiming the wrong thing. What it does share is
   the ring itself, which is the one focus indicator every control in this
   application now draws: CheckBox, TermsAgreement's scroller, the Settings
   cards, every .btn and all three chrome controls.

   `:focus` RATHER THAN `:focus-visible`, matching Bootstrap and correct for
   a text field: a caret placed with the mouse still has to be findable, so
   this is one of the few cases where a mouse focus should show a ring.

   Bootstrap's own literals are hardcoded rather than variables here, so
   unlike .btn this cannot be done by redefining a --bs-* value; it takes a
   real rule. (0,2,0) ties with `.form-control:focus`, and app.css loads
   after bootstrap.min.css, so the tie breaks our way. box-shadow: none is
   what removes the blue ring.

   .form-select is included ahead of a caller for the reason .btn-link above
   is: nothing renders one yet - the tenant and portal dropdowns planned for
   registration would be the first - and having it here means the first one
   written matches everything around it. .form-check-input is deliberately
   NOT included: this project uses Components/UI/CheckBox instead, which
   draws its own ring already.

   NOTE ON .invalid: app.css gives an invalid field a red outline, and that
   outline is suppressed while the field has focus - by Bootstrap's
   `outline: 0` before this change and by the ring after it, identically,
   since (0,2,0) beats (0,1,0) either way. So this is not a regression, and
   the ValidationMessage below the field is what carries the error while
   somebody is typing in it.
*/
.form-control:focus,
.form-select:focus {
    border-color: var(--aura-border-active);
    outline: var(--aura-focus-ring);
    outline-offset: var(--aura-focus-offset);
    box-shadow: none;
}
/*
   =====================================================================
   End of the gold aura section.
   =====================================================================
*/

ul {
    list-style-position: inside;
}

/*
   Drawer menus - the launcher's and the minimized-group ones - sit above
   the drawer scrim, which is itself above the window popup layer (1000).
   Bootstrap's own .dropdown-menu z-index is 1000, which would leave a menu
   blurred underneath the very scrim it raised.

   Global rather than in either component's stylesheet: the class is applied
   by two different components, and CSS isolation would scope a rule in
   either of them to that component's own markup only.
*/
.window_drawer_menu {
    z-index: 1600;
}

/*
   Global for the same reason .window_drawer_menu is: the class is worn by
   both the launcher's dropdown and the minimized-group ones, and a rule in
   either component's stylesheet would be scoped to that component's own
   markup and miss the other.
*/
.menu {
    /*
       The menu's lift, in three layers, held in a variable so a variant can
       ADD to it rather than restate it - .window_group_menu does exactly
       that, and before this it silently replaced the lot.

       1. a large soft drop, which is what puts the menu above the page
       2. a tight ambient ring, so the edge does not dissolve into a dark
          backdrop - the drawer scrim blurs everything behind an open menu
       3. an inset 1px catch light along the top, the glass edge
  */
    --menu-shadow: 0 40px 120px rgba(0,0,0,0.55), 0 0 8px 4px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.12);
    position: absolute;
    border-radius: 10px;
    border: 1px solid rgba(0,0,0,.25);
    background-color: rgba(64,64,64,0.5);
    box-shadow: var(--menu-shadow);
    margin: 0px;
    padding: 0px;
    overflow: hidden;
}

/*
   These three layers sat on a .menu::before for a while and painted NOTHING,
   which is worth writing down because putting them back there is the obvious
   thing to try. The pseudo-element was `content: ""` and nothing else - a
   zero-size inline box - so its shadow had no box to cast from. Neither of
   the two ways of finishing it works:

   - Give it `position: absolute; inset: 0` and the OUTER layers vanish. An
     element's own box-shadow is not clipped by its own overflow, but a
     DESCENDANT's is, and .menu is overflow: hidden - which it has to be, or
     a hovered first or last item squares off the 10px corner. So layers 1
     and 2 can only ever live on .menu itself.

   - The inset layer would survive that, but it then scrolls. .window_group_menu
     is overflow-y: auto with a max-height, and an absolutely positioned child
     of a scroll container is anchored to the scroll origin, not the border
     box. Measured in the browser: the overlay sat 0.8px below the menu top at
     scrollTop 0 and -79.2px at scrollTop 80, i.e. it slid straight out of the
     frame it was meant to be part of. An inset shadow on .menu does not move,
     because a background does not scroll with content.

   The one thing an overlay would have bought is painting the catch light
   OVER the first child rather than under it, and that is not worth the two
   problems above: in the launcher the first child is a separator band at 10%
   white, so the highlight reads through it almost unchanged, and in the group
   menu the 6px padding means nothing reaches the top edge at all.
*/

/*
   A window's notice band - something the reader needs to know that is NOT
   about a field, rendered above the content rather than as a
   ValidationMessage. Two windows use it for what their OPENER had to say
   (RegisterConfirm, ResendCodeComplete); Logout uses it for a message it
   writes itself.

   THAT THIRD CASE IS FORCED RATHER THAN CHOSEN, and it is the sharpest reason
   this class exists. Logout is a Section form with no fields, and Section
   derives its Submit button's disabled state from whether the message store
   holds anything - a store refilled only by a validation pass, which is raised
   only by a field change or another submit. With no fields there is no field
   change, so a failure written as a ValidationMessage would disable the only
   button on the window forever. A plain element cannot do that.

   Global for the same reason .menu and .window_drawer_menu are: three separate
   components wear it, and CSS isolation would scope a rule in any one of their
   stylesheets to that component's own markup and miss the others. It WAS a
   copy in two of them - byte-for-byte identical, and named
   .register_confirm_notice in both, so the resend window's stylesheet was named
   after the register window and changing one colour meant editing two files.

   Warning-coloured rather than error-coloured on purpose: nothing has gone
   wrong that the reader did, and whatever the message is about is still
   recoverable by trying again - ask for the code again, press Logout again.
   That is a different message from "that failed, give up".
*/
.window_notice {
    margin-bottom: 12px;
    padding: 10px 12px;
    /* The amber the drawer and the focus rings already use, so this reads as
       part of the same interface rather than as a browser default. */
    border: 1px solid rgba(255, 204, 102, 0.55);
    border-radius: 6px;
    background-color: rgba(255, 204, 102, 0.12);
    color: inherit;
    font-weight: 600;
}

/*
   AN ICON NAME AND THE GLYPH IT PRODUCES, side by side, with IconPicker's
   button after them.

   A typed name is checked by nothing but its preview: the icon set is a
   stylesheet from a CDN, and a wrong name paints nothing and reports nothing.
   Seeing the glyph appear as it is typed is what closes that. The picker
   offers only names the loaded set has, so a chosen one always draws.

   THE ROW WRAPS so the picker's panel can take a line of its own beneath the
   field. The text box's basis is small, so it grows into what the
   preview and the button leave rather than pushing them onto a line of their
   own: Bootstrap gives it width: 100%, which a wrapping row reads as a basis.

   THE PREVIEW BOX KEEPS ITS SIZE WHEN IT IS EMPTY, which is why the width and
   height are stated rather than left to the glyph. A box that collapses when
   the name is wrong moves the input beside it, so the field jumps while
   somebody is typing in it - and an empty box that stays put is itself the
   signal that the name is not one the font has.
*/
.icon_field {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.icon_field .form-control {
    flex: 1 1 8rem;
    min-width: 0;
}

.icon_field_preview {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 6px;
    background-color: rgba(255, 255, 255, 0.04);
    font-size: 1.25rem;
    line-height: 1;
}

/*
   WHAT CHOOSING SOMETHING MEANS, under the control that chooses it.

   The sentence comes off the selected definition's own string row, so this
   is one appearance for every screen that explains a choice rather than a rule
   each of them writes for itself - the same reason .window_notice is here.

   QUIETER THAN THE LABEL AND QUIETER THAN THE VALUE, deliberately. It is read
   once, while deciding, and then never again; at full weight it competes with
   the control it is describing on every later visit to the form.

   No margin-bottom. It belongs to the field above it and the form-group's own
   spacing is what separates the next field - giving it one of its own would
   detach it from the control it explains and make it read as an independent
   line of prose.
*/
.field_description {
    margin-top: 4px;
    margin-bottom: 0;
    font-size: 0.8rem;
    line-height: 1.35;
    opacity: 0.7;
}

/*
   The four string-editor dialogs, sized so they are one family.

   A DIALOG OPENS `Normal`, WHICH MEANS CONTENT-SIZED, and each of these four
   has different content: a definition list, a five-field form, a two-field
   form, a confirmation. Left alone each opened at whatever its widest line
   happened to be - View String came up around 200px and the frame truncated its
   own title to "Vie...", and Update String read "Update S...". They also sat at
   four visibly different widths while being four screens onto one row.

   Global rather than four copies in four stylesheets, for the reason
   .window_notice and .menu are: CSS isolation scopes a rule to the component
   whose markup it is in, so one of these four would have to own the others'
   appearance. It is the same class on all four, which is what makes them one
   size.

   min-width rather than width, so a long description still widens the box, and
   a max-width so a paragraph does not stretch it across a desktop. In rem, so
   both follow the reader's own text size.

   THE `min()` IS WHAT KEEPS THIS WORKABLE ON A PHONE, and it is not decoration:
   24rem is 384px at the default text size, which is wider than a 375px
   viewport - so a bare min-width would put a horizontal scrollbar on the whole
   page at exactly the size this application is designed for first. Bounded by
   the viewport it simply stops growing instead. Viewport units rather than
   `100%` deliberately: the containing block here is a window that is sized BY
   its content, so a percentage would be resolving against something this rule
   is helping to decide.
*/
.string_editor {
    min-width: min(24rem, 92vw);
    max-width: 40rem;
}

/*
   MAXIMIZING A WINDOW MAXIMIZES WHAT IS IN IT.

   The cap above answers "how wide should a window be that is sized BY its
   content", which is the question a dialog asks when it opens. A maximized
   window has already been given a width and is not asking it - so the cap stops
   being a sensible measure and becomes a 40rem column pinned to the left of a
   full-screen window, with the rest of the window empty beside it.

   IT SHOWS UP ON THE BANDS FIRST. Text left-aligned in a narrow column is
   ordinary; the system-entry notice and the server's answer are bordered bands
   with a fill, so a band ending two thirds of the way across a maximized window
   reads as something that failed to resize. That is how it was reported.

   `.window_maximized` is worn by `.window_lock`, `.window_shadow` and `.window`
   alike - see WindowFrame.razor - so any of the three is an ancestor of every
   window's content, dialogs included. Self-limiting the same way `.section_fill`
   is: one rule, both behaviours, nothing toggled in C# when a window is
   maximized.

   The min-width stays. It is a floor, so lifting the ceiling does not make it
   wrong, and it goes on doing its job the moment the window is restored.
*/
.window_maximized .string_editor {
    max-width: none;
}

/*
   A DIALOG'S LIST OPENS AS WIDE AS ITS GRID, worn by every window with a grid
   that another opens as a dialog - a panel in its list mode. Under the 40rem
   cap the grid opened scrolling, and with no cap at all it asks for every
   pixel there is and runs the dialog past the screen's edge. So the grid
   states its columns' width (DataGrid.SizesToColumns), the cap is lifted,
   and the sentences are contained, so they add nothing to the width and wrap
   to what the grid sets. A narrow screen still caps the window, and the grid
   scrolls inside it.
*/
.string_editor_grid {
    max-width: none;
}

.string_editor_grid > p {
    contain: inline-size;
}

/*
   A View window's list of facts about one row: the value under its label
   rather than beside it, and the label quiet, since the value is what earns
   emphasis and Bootstrap's own dt and dd give the opposite. Worn by every View
   window, so it lives here rather than in any one window's stylesheet.
*/
.string_read dt {
    font-weight: 600;
    opacity: 0.7;
}

.string_read dd {
    margin-bottom: 0.75rem;
}

/*
   A heading band inside a menu.

   GLOBAL BECAUSE THREE COMPONENTS DRAW ONE - the launcher, the profile
   switcher and the update indicator. It was scoped to the launcher's own
   stylesheet, which meant the other two wrote a class name that styled
   nothing: no padding, no band, and their heading sat flush against the
   menu's rounded corner with the text touching the edge. That is how it was
   reported, and "the class exists but not here" is the failure mode CSS
   isolation produces every time a class gains a second consumer.

   `color: transparent` is inert as things stand and is kept as the fallback it
   reads as: every call site pairs this with Bootstrap's `.text-light`, which
   sets the colour with `!important` and wins. A heading that ever appeared
   without it would be invisible rather than wrongly coloured, which is the
   safer way round for a band whose whole job is to be a separator.

   The corners need no radius of their own: `.menu` is `overflow: hidden`, so a
   full-width band at the top is clipped to the menu's own curve.
*/
.seperator_header {
    display: block;
    width: 100%;
    clear: both;
    padding: 3px;
    margin: 0px;
    color: transparent;
    text-align: inherit;
    text-decoration: none;
    white-space: nowrap;
    background-color: rgba(255,255,255,0.10);
    overflow: hidden;
}

/*
   The same band used as a menu's TITLE rather than as a separator within it.

   The launcher's headings divide a list into categories and read as left
   aligned, the way a list's own items do. The drawer's two menus have exactly
   one heading, at the top, naming the menu - and a title centred over its
   contents is what that is. Enough padding to sit away from the curve as well,
   since this one is always the first thing in the menu.
*/
.seperator_header_title {
    padding: 6px 10px;
    text-align: center;
}

/*
   THE HEADER A PROFILE SCREEN OPENS WITH: the picture, with the name beside it.

   Global rather than a copy in each profile stylesheet, for the reason
   .string_editor is: View Profile and Update Profile have to look like one
   screen with two jobs, and a scoped rule would make one of them the owner of
   the other's appearance.

   THE PICTURE IS SMALLER HERE THAN THE ONE A PROFILE SHOWS. The stored
   rendition is a 256-square and this draws it at 96 - downscaling, which is
   clean, where growing it would not be. 256 beside a name leaves about 60px
   for the name on a 375px phone, which is where the whole idea falls over;
   96 leaves about 220 and reads properly. The full-size one is a tap away on
   the picture list, which is what that screen is for.

   break-word AND NOT anywhere on the name. `anywhere` counts toward the
   element's intrinsic min-content width, so a flex child carrying it can be
   squeezed to nothing rather than making its neighbours wrap - which is how a
   person's name came to render one letter per line in the profile list. See
   _doc/README_AI_Testing.md.
*/
.profile_header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 0.75rem;
}

.profile_header_picture {
    flex: 0 0 auto;
    width: 96px;
    height: 96px;
    object-fit: cover;
    border-radius: 8px;
}

.profile_header_name {
    min-width: 0;
    overflow-wrap: break-word;
    font-weight: 600;
    font-size: 1.15rem;
}

/*
   The line under the name - "50 year old Male".

   Quieter and lighter than the name, because it is context rather than
   identity: it should be readable at a glance and never compete with what the
   person is called.
*/
.profile_header_summary {
    font-weight: 400;
    font-size: 0.9rem;
    opacity: 0.75;
    margin-top: 2px;
}

/*
   The picture when it opens the full-size one.

   A BUTTON RATHER THAN A CLICK HANDLER ON THE IMAGE, so it is reachable by
   keyboard and announced as something that can be pressed. Stripped back to
   the image it wraps - no fill, no border, no padding - because the affordance
   is the picture itself and a chrome button around a face reads as a control
   that does something to the picture rather than opens it.

   `display: block` on the wrapper and `flex: 0 0 auto` in its place, so the
   header measures the same whether the picture is pressable or not.
*/
.profile_header_picture_button {
    flex: 0 0 auto;
    display: block;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    border-radius: 8px;
    line-height: 0;
}

@media (min-width: 30rem) {
    .profile_header_picture {
        width: 128px;
        height: 128px;
    }
}

/*
   The row of buttons that reaches a profile's sub-screens - pictures,
   connections, tags.

   IT SITS DIRECTLY UNDER THE HEADER ON BOTH SCREENS, and that placement is the
   whole point rather than a preference: these are where somebody goes next, and
   at the bottom of a long edit form they are behind a scroll.

   FULL WIDTH RATHER THAN INSIDE THE NAME COLUMN. Three buttons want more than
   the ~220px left beside a 96px picture on a phone, so nested they would stack
   into a tall ragged column. Across the window they wrap at a sensible point.

   gap RATHER THAN A MARGIN ON EACH BUTTON: the buttons were inline-blocks
   separated by whatever whitespace the markup happened to have, so they ran
   into each other. A flex gap applies between wrapped ROWS as well, which a
   right-margin does not.
*/
.profile_actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 1rem;
}

/*
   An invitation code, sized to be read off one device and typed into another.

   MONOSPACE IS DOING REAL WORK HERE rather than setting a tone. The alphabet
   already drops every look-alike pair, and a fixed pitch is the other half of
   the same job: it keeps the four groups the same width so somebody copying
   the code can keep their place, and it stops a proportional font closing up
   runs like `NPRT` into something that has to be counted rather than read.

   user-select: all so one click takes the whole code and not a group. The
   viewer's sandbox blocks a download and a clipboard write, so selecting it is
   how it leaves the screen.
*/
.invitation_code {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 1.35rem;
    letter-spacing: 0.08em;
    user-select: all;
    margin: 0.75rem 0;
    word-break: break-all;
}

/*
   The class names below are BLAZOR'S, not ours - no markup in this project
   writes any of them, and none should. `modified`, `valid` and
   `invalid` are applied to an input by the EditContext's field CSS class
   provider, and `validation-message` is what the ValidationMessage and
   ValidationSummary components render. So they are styled here for the same
   reason the default Blazor template styles them: they turn up the moment a
   form does.

   Forms exist now - Login, Register, RegisterConfirm and ResendCode all
   render one - so these DO match, which they did not when the note here said
   otherwise. Still do not write any of them by hand: the EditContext and the
   ValidationMessage component are the only things that should apply them.
*/
.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid indianred;
}

.validation-message {
    color: indianred;
}

/*
   THE DATE-RANGE ROW ABOVE A LOG LIST.

   GLOBAL BECAUSE THREE WINDOWS DRAW ONE - Task History, Exceptions and
   Activity. It began in TaskHistory.razor.css with a note saying every element
   it selected was that window's own markup, which was true on the day it was
   written and stopped being true the moment a second list wanted the same
   control. CSS isolation scopes a rule to the component whose file it is in,
   so the other two would have got the class NAME and none of the rules - and
   the markup looks perfect either way. The tell is that it renders correctly
   in exactly one place.

   THREE THINGS ON ONE LINE THAT HAVE TO BECOME THREE LINES. Two date inputs
   and a button need roughly 500px side by side; these windows are MAXIMIZED
   from the launcher, so there is room on a desktop and 375px on a phone.
   flex-wrap is what makes that one rule rather than a breakpoint.

   NOTHING IN THIS ROW GROWS, and that is the whole of it. `flex: 0 1 14rem`
   gives a field a real width to wrap at while refusing it the slack: with
   `1 1` the two inputs split a maximized window between them and a date box
   ends up 460px wide, which reads as the main content of the screen rather
   than as a filter above it. MEASURED - that is what the first version did.

   THE BASIS IS A WRAPPING THRESHOLD RATHER THAN A WIDTH. A date input clips
   its own mm/dd/yyyy placeholder somewhere below 12rem, so 14rem is where the
   row should break rather than where the box should sit.

   MEASURED AT 375px: the fields land on separate lines and the button sits
   beside the last one, all of it inside the window and none of it scrolling
   the page sideways. Two fields cannot share a line there whatever basis is
   picked - two date inputs plus a button want about 376px of a viewport that
   is 375 - so the ragged edge is the shape of the problem rather than a value
   to tune. Activity adds a THIRD field and simply takes another line.

   `flex: 0 0 auto` ON THE BUTTON for the same reason, so it stays the width of
   its word.

   ALIGNED TO THE END rather than centred, because each field is a label above
   an input and the button has no label: aligning the boxes' baselines is what
   `flex-end` does here, and `center` would float the button half a label
   higher than the inputs beside it.
*/
.history_range {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin-top: 12px;
    margin-bottom: 4px;
}

.history_range_field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 0 1 14rem;
    min-width: 0;
}

.history_range_show {
    flex: 0 0 auto;
}

/* In a dialog sized to its content - Task History - the range asks for its whole width, so the dates and Search stand on
   one line however narrow the hidden grid leaves the window; capped, so a phone still wraps them. */
.string_editor_grid .history_range {
    width: max-content;
    max-width: 100%;
}

/*
   A STACK TRACE, AND THE AUDIT PAYLOAD BESIDE IT.

   PRE AND NOT A PARAGRAPH. A stack is lines, and reflowing it makes the frame
   boundaries disappear - which is the only structure it has. The same is true
   of the JSON an audit row carries in `data`.

   IT SCROLLS INSIDE ITS OWN BOX. A trace has lines far longer than any window
   here, so without `overflow-x` the widest frame decides the width of the
   whole window and then of the page - which is the one thing the layout rules
   in _doc/README_Client_Windows.md say must never happen. Two windows draw one, so it is global
   for the reason the row above is.

   A HEIGHT CAP, because a trace can be eighty frames and the Close button
   would otherwise be an eighty-frame scroll away inside a dialog that is sized
   by its content.

   Colours from the theme rather than literals: this renders on a dark ground
   and a hardcoded light block would be the one bright rectangle on the screen.
*/
.exception_stack {
    overflow-x: auto;
    overflow-y: auto;
    max-height: 24rem;
    margin: 0;
    padding: 8px;
    border-radius: 6px;
    background-color: rgba(0, 0, 0, 0.25);
    color: inherit;
    font-size: 0.85rem;
    line-height: 1.35;
    white-space: pre;
}

/* THE DASHBOARD BANNERS - what an administrator posts and everybody standing at
   that level reads. Drawn by MessageBanner, which three windows render: the
   signed-in dashboard, the public one, and the administrator's View screen.

   HERE RATHER THAN IN A SCOPED STYLESHEET, and that is the whole reason this
   block exists in app.css. CSS isolation scopes a rule to the component whose
   file it is in, so a shared class written in one of the three would give the
   other two the NAME and none of the rules - which renders correctly in exactly
   one place. That is what happened to .seperator_header, and it is the same
   reason .window_notice, .menu and .string_editor are here.

   THE COLOUR IS A TINT AND A BORDER, never a fill. Every pressable thing in
   this product is a translucent pill with an aura and nothing is a solid
   Bootstrap block, so a banner that shipped as .alert-danger would be the one
   loud rectangle on the page. The five styles differ only in hue.

   THE FIVE CLASSES ARE WHAT MAKES message_type A CLOSED LIST. A tenant
   cannot ship CSS, so a sixth style is a code change here and a constant in
   MessageType - which is why that table is not tenant-extensible and why
   MessageStyles falls back to neutral rather than drawing nothing. */
.message_banners {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 12px;
}

.message_banner {
    padding: 10px 12px;
    border: 1px solid;
    border-radius: 6px;
    color: inherit;
}

.message_banner_head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-weight: 600;
}

/* THE ANNOUNCEMENT WRAPS AND THE GLYPHS DO NOT, which is what keeps a long
   notice from squeezing the icon column to nothing. `break-word` rather than
   `anywhere`: anywhere counts toward min-content, so a flex child carrying it
   can be squeezed to one character per line - which is exactly what happened to
   the profile list's names at 375px. */
.message_banner_announcement {
    flex: 1 1 auto;
    overflow-wrap: break-word;
}

.message_banner_icon,
.message_banner_pin {
    flex: 0 0 auto;
    font-size: 1.1rem;
    line-height: 1;
}

/* Quieter than the announcement, and read once. It is the paragraph under the
   headline rather than a second headline. */
.message_banner_detail {
    margin: 6px 0 0 0;
    font-size: 0.9rem;
    line-height: 1.4;
    opacity: 0.85;
}

.message_banner_actions {
    margin-top: 8px;
}

/* The file's own name beside the word, so somebody can tell what they are about
   to download without pressing it. */
.message_banner_filename {
    margin-left: 6px;
    opacity: 0.75;
    font-size: 0.85rem;
}

.message_banner_information {
    border-color: rgba(102, 170, 255, 0.55);
    background-color: rgba(102, 170, 255, 0.12);
}

.message_banner_success {
    border-color: rgba(102, 204, 136, 0.55);
    background-color: rgba(102, 204, 136, 0.12);
}

/* The amber the drawer, the focus rings and .window_notice already use, so a
   warning reads as part of the same interface rather than as a browser
   default. */
.message_banner_warning {
    border-color: rgba(255, 204, 102, 0.55);
    background-color: rgba(255, 204, 102, 0.12);
}

.message_banner_critical {
    border-color: rgba(255, 119, 119, 0.65);
    background-color: rgba(255, 119, 119, 0.14);
}

/* No hue at all, and what an unrecognised style falls back to - see
   MessageStyles.CssClass. It is a real choice as well as the fallback: a
   masthead that is not an alarm. */
.message_banner_neutral {
    border-color: rgba(160, 160, 160, 0.45);
    background-color: rgba(160, 160, 160, 0.10);
}

/*
    THE VERSION HISTORY TABLE - every version of one chain-tracked entity, drawn
    under the controls that decide it.

    HERE RATHER THAN IN A SCOPED STYLESHEET BECAUSE TWO COMPONENTS DRAW IT:
    DocumentLibraryTypeColumns (15800) and PortalSettings (29900). CSS isolation
    scopes a rule to the component whose file it is in, so a second consumer
    would get the class name and none of the rules and it would render correctly
    in exactly one place. It lived in DocumentLibraryTypeColumns.razor.css until
    the second screen arrived, which is what that file's own comment said to do.
*/

/*
    THE SCROLL CONTAINER IS THE STANDING RULE FOR ANY WIDE CONTENT. A long field
    name or a long context name scrolls inside its own box rather than putting a
    horizontal scrollbar on the window - which on a phone is the difference
    between a table somebody can read and a screen that slides sideways.
*/
.version_history_scroll {
    overflow-x: auto;
}

.version_history {
    width: 100%;
    border-collapse: collapse;
}

/*
    THE HEADINGS SIT ON A LINE RATHER THAN A BAND, because this table is one
    block inside a form rather than a screen of its own - a filled header would
    read as a second grid competing with the controls above it.
*/
.version_history th {
    text-align: left;
    font-weight: 600;
    opacity: 0.75;
    white-space: nowrap;
    padding: 3px 12px 6px 0;
    border-bottom: 1px solid rgba(128, 128, 128, 0.35);
}

.version_history td {
    padding: 4px 12px 4px 0;
    vertical-align: top;
}

/*
    NO `overflow-wrap: anywhere` HERE, DELIBERATELY. That rule counts toward an
    element's min-content width, so a cell carrying it can be squeezed to one
    character per line - which is what it did to a person's name on the profile
    list. `Sessions` uses it because an IPv6 address is one unbreakable token;
    everything in this table is words, a date or a number, and the scroll
    container above handles the rest.
*/
.version_history td:first-child {
    white-space: nowrap;
}

/*
    A NOTE ON A WORK ORDER KEEPS THE LINES IT WAS TYPED WITH, worn by View Work
    Order (34700). pre-wrap rather than pre-line, since a note laid out with
    spaces - a list of parts, a reading - is read as it was typed. Each note
    sits on a line like the history's headings rather than in a card, for the
    same reason: it is one block inside a form. overflow-wrap is safe here,
    unlike in the history's cells, because a note is a block of prose rather
    than a column somebody's name is squeezed into, and a pasted part number
    would otherwise push the dialog past its width.
*/
.work_order_note {
    border-top: 1px solid rgba(128, 128, 128, 0.35);
    padding: 6px 0;
}

.work_order_note_text {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/*
    A PICTURE ON A WORK ORDER IS DRAWN AT THE WIDTH IT IS GIVEN, worn by View
    Work Order (34700), and never wider than the copy the browser cut for it.
    The copy is up to 1280 pixels on its longer side, so a phone draws it at
    the window's width and a maximized window at its own; the height follows,
    which keeps a tall photograph tall rather than letterboxed.
*/
.work_order_picture {
    border-top: 1px solid rgba(128, 128, 128, 0.35);
    padding: 6px 0;
    margin: 0;
}

.work_order_picture img {
    display: block;
    max-width: 100%;
    height: auto;
}

/*
    THE CROPPER NEEDS A BOUNDED HEIGHT or it grows to the picture's own size,
    which on a phone photo is several thousand pixels and pushes everything
    below it off the window. Worn by Components/UI/Cropper, as drawn by Add
    Picture (100800) and View Work Order (34700). A viewport unit rather than
    a fixed number, because both are opened on anything from a phone to a
    desktop, and the crop box has to stay big enough to aim with.
*/
.picture_cropper {
    max-height: 50vh;
    overflow: hidden;
    margin-top: 0.5rem;
}

.picture_cropper img {
    max-width: 100%;
}

/*
   The press-and-hold label over a drawer entry, which names an icon-only
   entry for a finger as `title` names it for a mouse. Here rather than in the
   drawer's own stylesheet because WindowDrawer.razor.js puts it on <body> -
   the drawer clips its overflow - where no scoped rule reaches. Fixed and
   transparent to the pointer, so it never takes the next touch.
*/
.window_drawer_hold_label {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 2000;
    max-width: calc(100vw - 16px);
    padding: 6px 10px;
    border: 1px solid var(--aura-border);
    border-radius: var(--button-radius);
    background-color: rgba(32, 32, 32, 0.95);
    box-shadow: var(--aura-rest);
    color: #fff;
    font-size: 0.95rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    opacity: 0;
    transition: opacity 120ms ease;
}

.window_drawer_hold_label_shown {
    opacity: 1;
}

/*
   A chat room's sections and its people, as the two windows that run a room
   draw them: Chat Room Settings, for its owner and moderators, and Update
   Chat Room, for an administrator. Here rather than in either window's own
   stylesheet because both wear these classes.

   A PERSON'S BUTTONS WRAP UNDER THEIR NAME on a phone rather than squeezing it.
   A row is a name and a line about where they stand, then up to four buttons,
   and at 375px the buttons alone are wider than the window - so the row wraps,
   and the name keeps the whole width of its line.
*/
.settings_heading {
    margin: 12px 0 4px;
    font-size: 1rem;
}

.settings_actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.settings_danger {
    margin-top: 16px;
}

.settings_confirm {
    margin: 8px 0;
    padding: 8px;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}

.settings_people {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.settings_person {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--bs-border-color);
}

.settings_person_detail {
    display: flex;
    flex: 1 1 12rem;
    flex-direction: column;
    min-width: 0;
}

.settings_person_name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.settings_person_actions {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/*
   A BODY'S TYPOGRAPHY. .rich_text is worn by RichTextView's box and by the
   editor's text area, which RichTextEditor.razor.js gives the class as it
   starts, so a body reads the same written as drawn. Here rather than in a
   .razor.css because two components wear it, and Quill builds the editor's
   elements in JavaScript, where no scope attribute reaches.

   A paragraph has no margin, because a blank line is a paragraph of its own,
   as it is in the editor. A heading is a step down from the page's, since a
   body sits in a window under the window's own title: the view draws a
   heading as h2 or h3 and the editor as h1 or h2, so each pair is sized
   together. The lists undo the page's list-style-position: inside, which
   would put a nested item's marker on the line above its words.
*/
.rich_text {
    line-height: 1.5;
    overflow-wrap: anywhere;
}

.rich_text p,
.rich_text ol,
.rich_text ul,
.rich_text pre {
    margin: 0;
}

.rich_text:not(.ql-editor) h2,
.ql-editor.rich_text h1 {
    font-size: 1.4rem;
    margin: 0.5rem 0 0.25rem;
}

.rich_text:not(.ql-editor) h3,
.ql-editor.rich_text h2 {
    font-size: 1.15rem;
    margin: 0.5rem 0 0.25rem;
}

.rich_text:not(.ql-editor) ul,
.rich_text:not(.ql-editor) ol {
    padding-left: 1.5em;
    list-style-position: outside;
}

.rich_text blockquote {
    border-left: 4px solid var(--bs-border-color);
    margin: 0.25rem 0;
    padding-left: 1rem;
}

.rich_text code {
    background-color: var(--bs-tertiary-bg);
    border-radius: 3px;
    font-size: 85%;
    padding: 2px 4px;
}

.rich_text pre,
.rich_text_editor .ql-snow .ql-editor .ql-code-block-container {
    background-color: var(--bs-tertiary-bg);
    color: inherit;
    border-radius: 3px;
    margin: 0.25rem 0;
    padding: 0.5rem 0.75rem;
    white-space: pre-wrap;
}

.rich_text pre code {
    background: none;
    font-size: inherit;
    padding: 0;
}

.rich_text a,
.rich_text_editor .ql-snow a {
    color: var(--bs-link-color);
}

/*
   A PICTURE IN A BODY is one of the writing profile's own, drawn in its line
   as Quill draws it in the editor, never wider than the body. A blog entry is
   the one body that holds them.
*/
.rich_text img.rich_text_picture {
    max-width: 100%;
    height: auto;
    border-radius: 6px;
    vertical-align: middle;
}

/*
   A PICTURE THAT OPENS WHOLE: a button around it, carrying nothing of a
   button's look, and a picture its profile does not show to everybody
   blurred until it is pressed. The overflow keeps the blur's soft edge inside
   the square.
*/
.rich_text .rich_text_picture_press {
    display: inline-block;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: none;
    overflow: hidden;
    cursor: zoom-in;
    vertical-align: middle;
    max-width: 100%;
}

.rich_text .rich_text_picture_press:focus-visible {
    outline: var(--aura-focus-ring);
    outline-offset: 2px;
}

.rich_text img.rich_text_picture_veiled {
    filter: blur(14px);
}

/*
   THE EDITOR'S CHROME: Quill's snow theme, which is drawn for a white page,
   over to this one. Quill's own rules are (0,2,0) to (0,4,1), so each rule
   here leads with .rich_text_editor to win, and the hover and pressed states
   - gold, as the aura's active rim is - are written one step more specific
   than snow's #06c. A touch screen keeps a hover after the tap, so there the
   hover is put back to the resting colour, as snow does for its own.

   The text area grows with its text from a few lines tall, and the editor
   wears the focus ring an input wears, gold and outside its border.

   The link box's words are custom properties the script sets on the editor's
   box, since snow writes "Visit URL:", "Edit", "Remove" and "Save" into its
   stylesheet; a property the script has not set draws nothing.
*/
.rich_text_editor .ql-toolbar.ql-snow {
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius) var(--bs-border-radius) 0 0;
    font-family: inherit;
}

.rich_text_editor .ql-container.ql-snow {
    border: 1px solid var(--bs-border-color);
    border-top: 0;
    border-radius: 0 0 var(--bs-border-radius) var(--bs-border-radius);
    background-color: var(--bs-body-bg);
    color: var(--bs-body-color);
    font-family: inherit;
    font-size: 1rem;
}

.rich_text_editor .ql-container.ql-snow:focus-within {
    border-color: var(--aura-border-active);
    outline: var(--aura-focus-ring);
    outline-offset: var(--aura-focus-offset);
}

.rich_text_editor .ql-editor {
    min-height: 8rem;
}

.rich_text_editor .ql-editor.ql-blank::before {
    color: var(--bs-secondary-color);
}

.rich_text_editor .ql-snow .ql-stroke,
.rich_text_editor .ql-snow .ql-stroke-miter {
    stroke: var(--bs-body-color);
}

.rich_text_editor .ql-snow .ql-fill,
.rich_text_editor .ql-snow .ql-stroke.ql-fill {
    fill: var(--bs-body-color);
}

.rich_text_editor .ql-snow.ql-toolbar button:hover .ql-stroke,
.rich_text_editor .ql-snow.ql-toolbar button:focus-visible .ql-stroke,
.rich_text_editor .ql-snow.ql-toolbar button.ql-active .ql-stroke,
.rich_text_editor .ql-snow.ql-toolbar button:hover .ql-stroke-miter,
.rich_text_editor .ql-snow.ql-toolbar button:focus-visible .ql-stroke-miter,
.rich_text_editor .ql-snow.ql-toolbar button.ql-active .ql-stroke-miter {
    stroke: var(--aura-border-active);
}

.rich_text_editor .ql-snow.ql-toolbar button:hover .ql-fill,
.rich_text_editor .ql-snow.ql-toolbar button:focus-visible .ql-fill,
.rich_text_editor .ql-snow.ql-toolbar button.ql-active .ql-fill {
    fill: var(--aura-border-active);
}

.rich_text_editor .ql-snow.ql-toolbar button:focus-visible {
    outline: var(--aura-focus-ring);
    outline-offset: var(--aura-focus-inset);
}

@media (pointer: coarse) {
    .rich_text_editor .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
    .rich_text_editor .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
        stroke: var(--bs-body-color);
    }

    .rich_text_editor .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill {
        fill: var(--bs-body-color);
    }
}

.rich_text_editor .ql-snow .ql-tooltip {
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
    color: var(--bs-body-color);
    z-index: 2;
}

.rich_text_editor .ql-snow .ql-tooltip input[type=text] {
    background-color: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    color: var(--bs-body-color);
}

.rich_text_editor .ql-snow .ql-tooltip::before {
    content: var(--rich-text-link-shown);
}

.rich_text_editor .ql-snow .ql-tooltip[data-mode=link]::before {
    content: var(--rich-text-link-enter);
}

.rich_text_editor .ql-snow .ql-tooltip a.ql-action::after {
    border-right-color: var(--bs-border-color);
    content: var(--rich-text-link-change);
}

.rich_text_editor .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
    content: var(--rich-text-link-save);
}

.rich_text_editor .ql-snow .ql-tooltip a.ql-remove::before {
    content: var(--rich-text-link-remove);
}

/*
   A FRAME. A faint rounded border round a part of a window, so where one part
   ends and the next begins is drawn rather than guessed from the spacing.
   Every Section wears it unless told not to, and System Health's grids and
   graphs wear it inside theirs. Tokens rather than figures, so the two cannot
   drift apart.
*/
:root {
    --frame-border: 1px solid rgba(255, 255, 255, 0.12);
    --frame-radius: 8px;
    --frame-fill: rgba(255, 255, 255, 0.03);
    --frame-padding: 0.5rem;

    /* Between two framed sections one above the other. */
    --frame-gap: 0.75rem;
}

/*
   A PART OF ONE SECTION, FRAMED ON ITS OWN. A list screen's search criteria
   and its results are one section - one read, one grant, one audit identity -
   but are drawn as two frames, so the question and the answer read as two
   things. The section turns its own frame off (`Framed="false"`) and wraps
   each half in this, which wears the frame a section does.
*/
.section_part {
    min-width: 0;
    padding: var(--frame-padding);
    border: var(--frame-border);
    border-radius: var(--frame-radius);
    background-color: var(--frame-fill);
}

    .section_part + .section_part {
        margin-top: var(--frame-gap);
    }

/*
   AN OVERLAY SCROLLBAR TAKES NO ROOM. It is drawn over a window's content and
   widens when the pointer reaches it, so it lies over a frame's right edge. A
   classic bar takes its own width and the content moves over by itself.
   window-scroll.js adds this class to a window's scroll area only while its
   content scrolls under an overlay bar, so a window that does not scroll keeps
   its padding even. Here rather than in WindowFrame's stylesheet because the
   class is added by script; the extra class outranks that stylesheet's rule.
*/
:root {
    --overlay-scrollbar-room: 12px;
}

.window_content_padding > .window_content.window_content_overlay_scroll {
    padding-right: calc(var(--aura-reach) + var(--overlay-scrollbar-room));
}

/*
   SYSTEM HEALTH'S TABS. What every one of System Health's (48800) sections
   wears, which is why it is here rather than in one of their stylesheets: a
   grid of the newest sample, then its graphs.

   The graphs are a grid that puts two side by side once each can have 22rem,
   and one under another below that, so a wide window shows a tab's graphs at
   a glance and a phone gets them full width.

   The grid and each graph sit in a frame of their own, the one a chat room's
   transcript wears, so where one ends and the next begins is drawn rather
   than guessed from the spacing.
*/
.system_health_charts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
    gap: 0.75rem;
    margin-top: 0.75rem;
}

.system_health_grid,
.system_health_chart {
    min-width: 0;
    padding: var(--frame-padding);
    border: var(--frame-border);
    border-radius: var(--frame-radius);
    background-color: var(--frame-fill);
}

.system_health_heading {
    font-size: 0.95rem;
    margin: 0 0 0.25rem;
    color: var(--bs-secondary-color);
}
