:root {
    --app-dialog-border-color: rgba(0, 0, 0, 0.3);
}

body {
    -webkit-user-select: none; /* Safari */
    -moz-user-select: none; /* Firefox */
    -ms-user-select: none; /* IE 10+ */
    user-select: none; /* Standard syntax */
}

input, textarea {
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    user-select: text;
}

.text-padding {
    padding: 2px !important;
}

button.mud-button span.mud-button-label {
    text-transform: none;
}

.mud-main-content {
    padding-top: calc(var(--mud-appbar-height) - var(--mud-appbar-height) / 4) !important;
}

/* The menubar header is position: fixed, but the glyph toolbar below it was in normal flow — so
   any page scroll slid the toolbar up underneath the header (a zoomed form makes the document
   taller than the viewport, and the browser's scroll-on-focus then nudged the page the moment the
   form opened — Muppets/Equinox.Web#137). Fix it to the viewport just below the header, like the
   native client's window furniture. Not sticky: the toolbar's wrapper div is exactly its own
   height, and a sticky element cannot pin outside its parent's box. The selector must out-specify
   MudBlazor's .mud-toolbar { position: relative }, under which `top` would shift the bar down
   instead of pinning it. */
div.mud-toolbar.app-toolbar {
    position: fixed;
    top: calc(var(--mud-appbar-height) - var(--mud-appbar-height) / 4);
    left: 0;
    right: 0;
    z-index: 999; /* below the header's 1000, above dialog content */
    background-color: var(--mud-palette-background);
}

/* Restore the layout slot the fixed toolbar vacated, so the dialog area keeps its position. */
.app-toolbar + #appHostContainer {
    margin-top: calc(var(--mud-appbar-height) - var(--mud-appbar-height) / 4);
}

div.mud-card-content.mud-card-content-padding {
    padding: 0;
}

input.app-text-center {
    text-align: center;
}

input.app-text-right {
    text-align: right;
}

/* Live case feedback for a forced-case character field, standing in for the Windows client's
   ES_UPPERCASE / ES_LOWERCASE edit style and its CAPSCASE rewrite (win2.c command_field). Declared
   on the input rather than inherited from the control's own styled root because text-transform is
   reset on input elements, so an ancestor's value never reaches it. `capitalize` approximates
   native caps(), which also lower-cases the rest of each word — CharComponent applies the exact
   rule when the field is left. */
input.app-case-upper {
    text-transform: uppercase;
}

input.app-case-lower {
    text-transform: lowercase;
}

input.app-case-capital {
    text-transform: capitalize;
}

.mud-button-root.mud-icon-button.mud-input-adornment-icon-button {
    padding: 0 5px;
    margin-inline-end: -8px;
}

.mud-icon-button[disabled] img {
    filter: grayscale(100%);
    opacity: 0.5;
}

/* Toolbar separator. Native reserves gui_toolbar.separator_width (6px) and paints a two-pixel
   highlight/shadow rule inset one pixel top and bottom (DrawBlankButton, Library/toolbar.c), which
   is what makes two adjacent separators show as two rules. The element carries the reserved width
   as an inline style; the rule itself is centred in it. Muppets/Equinox.Web#112. */
.app-toolbar-separator {
    flex: 0 0 auto;
    align-self: stretch;
    position: relative;
}

.app-toolbar-separator::before {
    content: "";
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 50%;
    width: 2px;
    margin-left: -1px;
    border-left: 1px solid var(--mud-palette-lines-default, rgba(0, 0, 0, 0.12));
    border-right: 1px solid var(--mud-palette-background, #fff);
}

header.mud-appbar-fixed-top {
    z-index: 1000;
}

.centered-image-container {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: -1000;
}

#appHostContainer .appDialogMudCard .mud-card-header {
    padding: 0 8px;
    border-bottom: solid 1px var(--mud-palette-divider);
}

#appHostContainer .appDialogMudCard .drag-handle {
    cursor: grab;
    height: 38px;
}

#appHostContainer .appDialogMudCard .drag-handle:active {
    cursor: grabbing;
}

#appHostContainer .borderless-caption-offset {
    margin-top: 38px;
}

#appHostContainer .appDialogMudCard .drag-handle .mud-card-header-actions {
    margin-top: -4px;
}

#appHostContainer .appDialogMudCard .size-handle {
    position: absolute;
    bottom: 0;
    right: 0;
    margin: 2px;
    cursor: se-resize;
    fill: var(--app-dialog-border-color);
}

#appHostContainer .appDialogMudCard {
    border: 2px solid var(--app-dialog-border-color);
    height: 100%;
}

#appHostContainer .appDialogMudCard.activeWindow {
    border: 2px solid var(--mud-palette-primary);
}

#appHostContainer .appDialogMudCard.borderless,
#appHostContainer .appDialogMudCard.borderless.activeWindow {
    border: none;
}

#appHostContainer .appDialogMudCard.activeWindow .size-handle {
    fill: var(--mud-palette-primary);
}

#appHostContainer .appDialogMudCard .memo-mud-input textarea {
    height: 100%;
    mask-image: none;
}

/* A memo's two scrollbars are window styles, not overflow behaviour: create_memo2 (win1.c) puts
   WS_VSCROLL in the base style and strips it for `bits & 4`, and adds WS_HSCROLL | ES_AUTOHSCROLL
   when word wrap is off. A Win32 scrollbar is drawn whether or not the text overflows, so `scroll`
   — not `auto` — is what matches, and a memo that native gives no scrollbar must not grow one.
   The `wrap` attribute is what actually stops the text soft-wrapping; white-space is declared with
   it so a MudBlazor reset cannot put the wrapping back. */
#appHostContainer .appDialogMudCard .app-memo-wrap textarea {
    white-space: pre-wrap;
    overflow-x: hidden;
}

#appHostContainer .appDialogMudCard .app-memo-nowrap textarea {
    white-space: pre;
    overflow-x: scroll;
}

#appHostContainer .appDialogMudCard .app-memo-vscroll textarea {
    overflow-y: scroll;
}

#appHostContainer .appDialogMudCard .app-memo-novscroll textarea {
    overflow-y: hidden;
}

/* Radio captions are rendered by MudBlazor as MudText, whose .mud-typography-* rule sets
   font-family/size from the theme — so the control's native font (emitted inline on the positioned
   root) would never reach the caption and it would keep rendering in Roboto. Inherit the four
   properties StyleService.GetFontStyles emits; the theme's line-height is deliberately left alone
   (Muppets/Equinox.Web#128). The toggle used to need this too; it now paints its own caption in a
   plain span, which inherits without help (Muppets/Equinox.Web#104), and so does the list
   (Muppets/Equinox.Web#106). */
#appHostContainer .appDialogMudCard .mud-radio .mud-typography {
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    font-style: inherit;
}

/* An outlined MudInput draws its frame in a child <fieldset class="mud-input-outlined-border">,
   which would sit inside — and on top of — the control's own border. Components add
   app-native-border when the control supplies one, so the native border is the only frame; a
   control with no border of its own keeps MudBlazor's outline (Muppets/Equinox.Web#128). */
#appHostContainer .appDialogMudCard .app-native-border > .mud-input-outlined-border {
    display: none;
}

button.app-button-with-icon span.mud-button-label {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    overflow: hidden;
}

/* Mirror native drawbuttonbitmap (object.c): the glyph is drawn inside the button
   rect, never larger than it. Cap the icon to the button's content box and keep its
   aspect ratio so it scales down to fit instead of rendering at natural size and
   overflowing (Muppets/Equinox.Web#74). */
button.app-button-with-icon span.mud-button-label .app-button-icon {
    flex-shrink: 0;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

button.app-button-with-icon span.mud-button-label .app-button-text {
    flex: 1;
    text-align: center;
}

/* MudButton enforces a 64px min-width (Material spec). Equinox positions buttons at an
   exact pixel size, so the inline width must win — otherwise narrow buttons (e.g. a 40px
   icon button) render at 64px (Muppets/Equinox.Web#74). */
button.app-button-left,
button.app-button-center,
button.app-button-right {
    min-width: 0;
    /* MudButton's default 6px 16px padding eats into the caption's content box, which
       (with browser font metrics being a touch wider than native GDI) pushes captions
       like "Export Invoices to Time-Trak" onto a second line. Native buttons draw the
       caption with only a small margin, so tighten the padding to recover that width. */
    padding: 2px 4px;
}

/* Honour the control's justification for button text (default MudButton always centres) */
button.app-button-left span.mud-button-label,
button.app-button-center span.mud-button-label,
button.app-button-right span.mud-button-label {
    width: 100%;
    /* `pre`, not `nowrap`: native never word-wraps a caption on width, but it does honour the
       control characters the caption arrives with — DrawGenericButton95 drops DT_SINGLELINE once
       the text contains a newline, and always passes DT_EXPANDTABS (object.c). The caption reaches
       us with the real CR/LF and tabs already in it, so `nowrap` was collapsing both to spaces
       (Muppets/Equinox.Web#108). `pre` keeps the no-wrap-on-width behaviour `nowrap` was added for.
       tab-size 4ch is native's "16 dialog base units / 4" - four average character widths. */
    white-space: pre;
    tab-size: 4ch;
    overflow: hidden;
}

/* Native centres a single-line caption vertically (DT_VCENTER) but top-aligns a multi-line one
   (DT_TOP, object.c:5457). The component adds this class when the caption contains a newline. */
button.app-button-multiline span.mud-button-label {
    align-items: flex-start;
}

button.app-button-left span.mud-button-label {
    justify-content: flex-start;
    text-align: left;
}

button.app-button-center span.mud-button-label {
    justify-content: center;
    text-align: center;
}

button.app-button-right span.mud-button-label {
    justify-content: flex-end;
    text-align: right;
}

button.app-button-left span.mud-button-label .app-button-text {
    text-align: left;
}

button.app-button-right span.mud-button-label .app-button-text {
    text-align: right;
}

/* Pressed state. DrawGenericButton95's GWW_DOWN branch (object.c:5162) repaints the face in Face
   Down, collapses the whole raised bevel to a flat 1px frame in the Shadow colour, and shifts the
   caption one pixel down and right. StyleService publishes the two colours as custom properties.
   Only for buttons we bevel ourselves — a default-styled one keeps MudBlazor's ripple, which is the
   deliberate divergence recorded in BUTTON-PROPERTY-REVIEW.md. !important because the inline
   background-color and box-shadow would otherwise win (Muppets/Equinox.Web#108). */
button.app-button-bevel:active:not(:disabled) {
    background-color: var(--app-but-face-down) !important;
    box-shadow: inset 0 0 0 1px var(--app-but-pressed-frame) !important;
}

button.app-button-bevel:active:not(:disabled) span.mud-button-label {
    transform: translate(1px, 1px);
}

/* A protected button greys only its caption, to EQX_STDCOL_GREY — the face and bevel are untouched
   (`if (protect) SetTextColor(rgb[EQX_STDCOL_GREY])`, object.c:5443). MudBlazor's disabled rule
   supplies a much lighter --mud-palette-action-disabled instead. */
button.app-button-bevel:disabled {
    color: rgb(128, 128, 128) !important;
}

/* Glyph styles (bfGlyphStyle, drawbuttonbitmap object.c:4205). Styles 0 Normal and 3 Retain Ratio
   place the glyph at the left margin at its size-class size and shift the caption right, which is
   the default flex row above. Styles 1 Full Face and 2 Full Face (Retain Ratio) instead fill the
   whole button inside BUTTON_MARGIN and let the caption draw over the top. */
/* A glyph with no caption is CENTRED, and unconditionally so — drawbuttonbitmap's `if (!len)` branch
   (object.c:4152) computes the centre of the button rect and ignores the control's justification
   entirely. The caption span must give up its `flex: 1` too, or it absorbs the free space and leaves
   the glyph pinned left (Muppets/Equinox.Web#108). */
button.app-button-glyph-only span.mud-button-label {
    justify-content: center;
}

button.app-button-glyph-only span.mud-button-label .app-button-text {
    flex: 0 0 auto;
}

/* BUTTON_MARGIN is 4 device pixels inside the button rect (glyph.h), and is not scaled by the form
   zoom — so a literal 4px is right at any zoom.
   The containing block for an absolutely positioned child is the nearest positioned ancestor's
   PADDING box, and the button's border is 0, so that box is the button rect itself: the offsets are
   measured from the rect, not from inside the button's own padding. Sized with width/height rather
   than the far insets because an absolutely positioned replaced element (an <img>) with `auto`
   sizing takes its intrinsic size and ignores `right`/`bottom`. */
button.app-button-glyph-fullface span.mud-button-label .app-button-icon {
    position: absolute;
    top: 4px;
    left: 4px;
    width: calc(100% - 8px);
    height: calc(100% - 8px);
    max-width: none;
    max-height: none;
}

/* The caption has to sit above a full-face glyph, and take the whole width now that the glyph is
   out of the flex row. */
button.app-button-glyph-fullface span.mud-button-label .app-button-text {
    position: relative;
    z-index: 1;
    flex: 1;
}



/* A field carrying an icon affordance — the date picker's calendar (Muppets/Equinox.Web#100) and
   the password reveal. Neither has a native counterpart, and both sit on a control whose width is
   the native field width, so an adornment left in the input's flex row steals that width from the
   text and clips the value. Take it out of the flow and lay it over the right-hand edge instead. */
.app-adorned-field .mud-input-adornment-end {
    position: absolute;
    right: 2px;
    top: 50%;
    transform: translateY(-50%);
}

/* Sized in absolute units, not a percentage of the adornment: that box is now position:absolute
   with an auto height, so a percentage height resolves against nothing and the button grows to
   whatever the icon and padding make it. The absolute length comes from the component as
   --app-adornment-icon, half the field height, so the button keeps its proportions when the form
   zoom scales the control — a fixed size was cramped unzoomed and lost on a zoomed form. */
/* The margin-right cancels MudBlazor's edge-end rule, which pulls an edge-aligned icon button
   8px past its container to line up with a page margin — here that hung it over the right edge of
   the field and clipped it. */
.app-adorned-field .mud-input-adornment-end .mud-input-adornment-icon-button {
    margin-right: 0;
    padding: 0;
    width: var(--app-adornment-icon, 16px);
    height: var(--app-adornment-icon, 16px);
    min-width: var(--app-adornment-icon, 16px);
    opacity: 0.55;
}

.app-adorned-field:hover .mud-input-adornment-icon-button,
.app-adorned-field:focus-within .mud-input-adornment-icon-button,
.app-adorned-field .mud-input-adornment-icon-button:hover {
    opacity: 1;
}

.app-adorned-field .mud-input-adornment-icon-button .mud-icon-root {
    width: var(--app-adornment-icon, 16px);
    height: var(--app-adornment-icon, 16px);
    font-size: var(--app-adornment-icon, 16px);
}

/* The calendar is positioned from the field's measured rect rather than anchored by the popover
   provider: an Equinox control is absolutely positioned with no positioned ancestor of its own, so
   a popover left to anchor itself lands in the corner of the page. */
.app-date-picker-popup {
    position: fixed;
    z-index: 2001;
    box-shadow: var(--mud-elevation-8);
}

.app-date-picker-backdrop {
    position: fixed;
    inset: 0;
    z-index: 2000;
}

/* Combo field (Muppets/Equinox.Web#103). Native draws the drop / spin / "…" button inside the
   field rectangle and narrows the text area by pa->xdbu * 2 to make room (DrawPField, object.c;
   create_edit_control, win1.c). The button is laid over the field's right-hand edge from the
   control's own rect rather than sitting in the input's flex row, so it cannot steal width from
   the value — the same reason the date field's adornment is taken out of flow. */
.app-combo-field input {
    padding-right: 18px !important;
}

.app-combo-buttons {
    position: absolute;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: stretch;
    padding: 1px 1px 1px 0;
    box-sizing: border-box;
}

.app-combo-button {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-action-disabled-background);
    color: var(--mud-palette-text-primary);
    font-size: 8px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* The spin pair splits the field height, as native does at (yt + yb) >> 1. */
.app-combo-buttons-split .app-combo-button + .app-combo-button {
    border-top: none;
}

.app-combo-button:hover:not([disabled]) {
    background: var(--mud-palette-action-default-hover);
}

.app-combo-button[disabled] {
    cursor: default;
    opacity: 0.5;
}

/* Positioned from the field's measured rect and fixed to the viewport, for the reason the date
   picker is: an Equinox control is absolutely positioned against the dialog with no positioned
   ancestor of its own, so a self-anchoring popover lands in the corner of the page. */
.app-combo-popup {
    position: fixed;
    z-index: 2001;
    overflow-y: auto;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    box-shadow: var(--mud-elevation-8);
}

.app-combo-backdrop {
    position: fixed;
    inset: 0;
    z-index: 2000;
}

/* Row height comes from the control's own font via --app-combo-row, set inline by ComboComponent:
   native measures a row as tm.tmHeight for hFont[o->font] (win3.c WM_MEASUREITEM), so a zoomed form
   gets a proportionally bigger drop-down. A fixed height left the list at its unzoomed size while
   the fields around it grew. */
.app-combo-item {
    padding: 0 4px;
    white-space: nowrap;
    cursor: pointer;
    height: var(--app-combo-row, 20px);
    line-height: var(--app-combo-row, 20px);
}

.app-combo-item:hover {
    background: var(--mud-palette-action-default-hover);
}

/* The selected row keeps its own colours under the cursor. Both selectors are needed: `:hover` on
   its own is a class plus a pseudo-class, so it out-specifies a single class and would swap the
   background for the grey hover fill while leaving the pale selected text behind — light-on-light
   and unreadable. Matching native, which has no hover state at all in an owner-draw listbox: only
   the selection is drawn highlighted. */
.app-combo-item-selected,
.app-combo-item-selected:hover {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

/* ------------------------------------------------------------------------------------------------
   Toggle (Muppets/Equinox.Web#104)

   A native toggle's box is not a Material checkbox. DrawGenericButton95 (object.c:4712) paints a
   square sized from the control's own height, fills it with ColourFaceUp, bevels it with
   ColourTopLeft/ColourBottomRight over a ring hard-coded to EQX_3DSHADOW/EQX_3DHILIGHT, and draws
   the tick in ColourFaceDown. Everything that varies arrives as a custom property from
   StyleService.GetToggleStyles — nothing here may be a fixed pixel size that the form zoom would
   then ignore, which is the bug class the combo drop-down hit.
   ------------------------------------------------------------------------------------------------ */
.app-toggle {
    display: flex;
    align-items: center;
}

.app-toggle-inner {
    display: flex;
    align-items: center;
    gap: var(--app-tog-gap, 4px);
    width: 100%;
    min-width: 0;
    margin: 0;
    cursor: pointer;
}

/* left_align: BS_RIGHTBUTTON on the real control (win1.c:897), and in the painter the box moves to
   the right edge with the caption filling the space to its left (object.c:4837). Reversing the flow
   rather than reordering the DOM keeps the input adjacent to the box, which is what draws the focus
   ring, and keeps the caption's flex-grow pushing the box to the far edge. */
.app-toggle-left .app-toggle-inner {
    flex-direction: row-reverse;
}

.app-toggle-greyed .app-toggle-inner {
    cursor: default;
}

/* Focusable but not visible: the painted box stands in for it, including the focus ring. */
.app-toggle-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    opacity: 0;
    pointer-events: none;
}

.app-toggle-box {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: var(--app-tog-box, 13px);
    height: var(--app-tog-box, 13px);
    background: var(--app-tog-face, #fff);
    border: 1px solid;
    border-color: var(--app-tog-tl) var(--app-tog-br) var(--app-tog-br) var(--app-tog-tl);
    box-shadow: inset 1px 1px 0 0 rgb(160, 160, 160), inset -1px -1px 0 0 rgb(255, 255, 255);
    display: flex;
    align-items: center;
    justify-content: center;
}

.app-toggle-input:focus-visible + .app-toggle-box {
    outline: 1px dotted var(--mud-palette-text-primary);
    outline-offset: 1px;
}

/* Native draws the caption DT_SINGLELINE and clips it at saved_right - 2 (object.c:5518); without
   nowrap a caption too wide for its control wraps to a second line instead of being cut off. The
   flex-grow is what puts the box against the far edge in the left-aligned layout. */
.app-toggle-caption {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
}

.app-toggle-tick {
    display: none;
    box-sizing: border-box;
    width: calc(var(--app-tog-box, 13px) * 0.3);
    height: calc(var(--app-tog-box, 13px) * 0.6);
    border: solid var(--app-tog-tick, #000);
    border-width: 0 var(--app-tog-stroke, 2px) var(--app-tog-stroke, 2px) 0;
    transform: translateY(-8%) rotate(45deg);
}

.app-toggle-checked .app-toggle-tick {
    display: block;
}

/* too_small: at 16px or less native gives up on the check mark and fills the box solid with the
   fore colour (object.c:5254), which GetToggleTickColour has already resolved into --app-tog-tick. */
.app-toggle-small.app-toggle-checked .app-toggle-tick {
    width: 100%;
    height: 100%;
    border: none;
    background: var(--app-tog-tick, #000);
    transform: none;
}

/* Radio group (Muppets/Equinox.Web#105). The circle is painted here rather than delegated to
   MudRadio, whose Material glyph is one colour at a fixed 20px: ColourFaceUp fills the interior,
   ColourFaceDown colours the dot, and ColourTopLeft/ColourBottomRight are the two halves of the
   outer ring over an inner ring hard-coded black/grey (object.c:4962). Every size is a custom
   property from GetRadioStyles because native derives them all from the option height, which is
   the control's own height divided by the option count. */
.app-radio {
    display: flex;
    flex-direction: column;
    /* The group is one entry-order stop, as it is natively: the whole RAD_OBJ is one object, and
       Tab moves between objects (keyboard_subclass -> PM_TAB). The focus indicator is drawn on the
       selected option's caption instead of round the whole group. */
    outline: none;
}

.app-radio-option {
    display: flex;
    align-items: center;
    gap: var(--app-rad-gap, 4px);
    height: var(--app-rad-row, 13px);
    min-width: 0;
    cursor: pointer;
}

.app-radio-greyed .app-radio-option {
    cursor: default;
}

/* Kept for semantics and for the tests that drive the control through it; the painted circle
   stands in for it visually and the group div owns the focus. */
.app-radio-input {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: 0;
    padding: 0;
    opacity: 0;
    pointer-events: none;
}

.app-radio-circle {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: var(--app-rad-circle, 11px);
    height: var(--app-rad-circle, 11px);
    border-radius: 50%;
    background: var(--app-rad-face, #fff);
    border: 1px solid;
    border-color: var(--app-rad-tl) var(--app-rad-br) var(--app-rad-br) var(--app-rad-tl);
    box-shadow: inset 1px 1px 0 0 rgb(0, 0, 0), inset -1px -1px 0 0 rgb(128, 128, 128);
    display: flex;
    align-items: center;
    justify-content: center;
}

.app-radio-dot {
    display: none;
    box-sizing: border-box;
    width: var(--app-rad-dot, 5px);
    height: var(--app-rad-dot, 5px);
    border-radius: 50%;
    background: var(--app-rad-dot-colour, #000);
    /* The Chord is stroked with a hard-coded grey pen (object.c:5219), inset so the dot keeps the
       diameter native gives it. */
    box-shadow: inset 0 0 0 1px rgb(128, 128, 128);
}

.app-radio-option-selected .app-radio-dot {
    display: block;
}

/* Native draws each caption DT_SINGLELINE and clips it at saved_right - 2 (object.c:5517); without
   nowrap a caption too wide for its control wraps and the group overflows its own rect. */
.app-radio-caption {
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
}

/* protect greys the caption only — the dot and the ring keep their colours (object.c:5495), which
   is where the radio differs from the toggle's tick. */
.app-radio-greyed .app-radio-caption {
    color: rgb(128, 128, 128);
}

.app-radio:focus-visible .app-radio-option-selected .app-radio-caption {
    outline: 1px dotted var(--mud-palette-text-primary);
}

/* Listbox (Muppets/Equinox.Web#106). At runtime the Windows client paints a list with an
   owner-draw LISTBOX child window (create_list2 -> win3.c drawitem), not with DrawPList — so these
   rules mirror drawitem/HandleSelectionState/measureitem, and MudList is not involved at all. */
.app-list {
    display: block;
    box-sizing: border-box;
    /* Native clips a too-wide item; the listbox is created with WS_VSCROLL only, so there is never
       a horizontal scrollbar. */
    overflow-x: hidden;
    outline: none;
}

.app-list-item {
    display: flex;
    /* measureitem returns max(tm.tmHeight, pa->ydbu) with FIVELINESPERINCH on (win3.c:9), and for a
       control using the dialog's own font the floor is what wins — that is where a native list gets
       its breathing room. ydbu is one vertical snap unit, (scrresy * 127) / (scrsizey * 25)
       (calibrat.c:73) = a fifth of an inch, ~19px at 96dpi against the 11px design font
       (font.c:207), so the floor is ~1.75x the font size. It is not transmitted, and both it and
       the font track the form zoom, so em is the unit that survives.
       The max() is native's max(), and it matters: the two terms scale differently. On an unzoomed
       dialog the floor wins (an 11px font gives 12.65em-worth of text box against 19px), which is
       where the navigator gets its breathing room; on a form zoomed to 200% the font term wins
       (25.3px), and a fixed 1.75x ratio there would have made the rows 38.5px and dropped a row off
       every picklist. 19px is ydbu unzoomed — the zoom scales ydbu too, but by then the font term
       dominates and the floor is moot.
       It must be line-height and NOT min-height or padding: the selection bar is the caption's own
       background, so growing the row any other way leaves the bar floating inside it with gaps
       above and below. drawitem centres the caption in the surplus the same way
       (yo = (rowHeight - tm.tmHeight) >> 1), which is what align-items does here. */
    align-items: center;
    line-height: max(1.15em, 19px);
    margin: 0;
    padding: 0;
    overflow: hidden;
    cursor: pointer;
}

.app-list-greyed .app-list-item {
    cursor: default;
}

/* lbdrawbitmap blits an 18x12 slice at rcItem.left + 2 and clips it to the row height, then the
   caption starts at LISTBOX_BITMAP_WIDTH + 4 — hence the 2px on each side. */
.app-list-bitmap {
    flex: 0 0 auto;
    width: 18px;
    height: 12px;
    margin: 0 2px;
    max-height: 100%;
    object-fit: none;
    object-position: 0 0;
}

.app-list-text {
    flex: 1 1 auto;
    min-width: 0;
    /* TabbedTextOut with one stop every 16 dialog base units, i.e. four average character widths
       (win3.c) — `ch` is the browser's version of that unit. `pre` also keeps the leading spaces
       the navigator indents its tree with, and stops the caption wrapping as native never does. */
    white-space: pre;
    tab-size: 4ch;
    overflow: hidden;
    /* A small lead-in before the caption, which native does not have: TabbedTextOut starts at
       rcItem.left and HandleSelectionState fills from the same x, so a Windows caption sits flush
       against the left edge of its own selection bar. Deliberate web departure. It belongs on the
       caption rather than the row so the bar covers it — the bar is this element's background, so
       the inset reads as padding inside the highlight rather than a gap before it. em so it tracks
       the form zoom with everything else. */
    padding-left: 0.25em;
}

/* HandleSelectionState fills from rcItem.left + itemwidth, so an item bitmap keeps the window
   background and only the caption area is filled. The text element starts after the bitmap, which
   reproduces that without measuring anything. The colour is the one deliberate departure from the
   Windows client, which hard-codes RGB(0, 0, 128): the web uses the Equinox 8 blue from the theme,
   so it follows dark mode like the rest of the accent (see StyleService). */
.app-list-item-selected .app-list-text {
    background-color: var(--app-lst-sel-bg, var(--mud-palette-primary));
    color: var(--app-lst-sel-fg, rgb(255, 255, 255));
}

/* A negative bitmap number greys the caption, and it stays grey on the selection bar. Declared
   after the selected rule so it wins on a selected row, as in HandleSelectionState. */
.app-list-item-grey .app-list-text,
.app-list-greyed .app-list-item .app-list-text {
    color: var(--app-lst-grey, rgb(128, 128, 128));
}

/* DrawFocusRect, also inset past the bitmap. */
.app-list:focus-visible .app-list-item-selected .app-list-text {
    outline: 1px dotted var(--app-lst-sel-fg, rgb(255, 255, 255));
    outline-offset: -1px;
}

/* Spreadsheet-bar row markers (Muppets/Equinox.Web#110). check_row_indicators (form2x.c) gives
   every row a raised 3-D box and puts the arrow on the current one; the box comes through as a
   normal border, so all that is left here is the pointer affordance. RepeatLine 64 is native's
   app_flags 63 — "cannot move to this line" — so only the other rows get a pointer. */
.app-repeat-line {
    cursor: default;
}

.app-repeat-line-selectable {
    cursor: pointer;
}

/* Tabbed control (Muppets/Equinox.Web#107). The element is the control's whole object rect, as
   native's is: createpropsheet (win1.c:7119) fills the rect below the label strip with
   tabcolours[4] - the Tab Surface colour - and only then draws the strip over it, so the panel
   needs somewhere to live. The strip itself keeps MudBlazor's styling by product decision; see
   TabComponent's note. */
.app-tab {
    overflow: hidden;
}

/* The panel surface. Transparent unless the form author chose a Tab Surface colour, because the
   shipped default is ColourBtnFace - the dialog's own face - and native's surface is invisible
   against it too (StyleService.IsTabThemeColour). */
.app-tab::before {
    content: "";
    position: absolute;
    inset: var(--app-tab-strip, 28px) 0 0 0;
    background-color: var(--app-tab-surface, transparent);
    pointer-events: none;
}

.app-tab-vertical::before {
    inset: 0 0 0 var(--app-tab-strip, 28px);
}

/* The strip is the leading edge of the rect, not the whole of it. Scrolls rather than squashing:
   the labels carry the width LabelSize asks for, and a strip that cannot fit them all is native's
   even-split fallback (resolved server-side in EqBridge.GetTabLabelWidth), so anything still
   overflowing here is a vertical strip taller than its control - which native simply truncates. */
.app-tab .app-tab-strip {
    position: absolute;
    top: 0;
    left: 0;
    max-width: 100%;
    max-height: 100%;
    overflow: auto;
}

.app-tab:not(.app-tab-vertical) .app-tab-strip {
    height: var(--app-tab-strip, 28px);
}

.app-tab-vertical .app-tab-strip {
    width: var(--app-tab-strip, 28px);
}

/* DrawTab95 draws every caption DT_SINGLELINE and clips it; without this a slightly-too-wide
   caption wraps and pushes the label out of the strip. Same rule the buttons already carry. */
.app-tab .app-tab-label {
    flex-shrink: 0;
    white-space: nowrap;
    overflow: hidden;
}

/* Scrollbar (Muppets/Equinox.Web#114). A BAR_OBJ is painted, never a real control until it takes
   focus, so this reproduces DrawPScrollV / DrawPScrollH (object.c): a COLOR_SCROLLBAR outline round
   the whole object rect, an OEM arrow bitmap at each end, a checkerboard track of COLOR_SCROLLBAR
   and white between them, and a fixed-size COLOR_BTNFACE thumb that travels the gap. Every length
   comes from --app-bar-arrow, which is the control's own cross-axis size: recalibrate (calibrat.c)
   forces that to GetScrollBarWidth() / GetScrollBarHeight(), which is the arrow bitmap's size, and
   the OEM arrows are square. */
.app-scrollbar {
    position: absolute;
    box-sizing: border-box;
    /* The Polyline runs round the object rect (x-1, y-1) to (bx, by) before anything else is
       blitted, and the arrows then cover the ends of it. */
    border: 1px solid var(--app-bar-track);
    /* PatBlt with a 2x2 pattern brush: COLOR_SCROLLBAR on one diagonal, white on the other. */
    background-color: #fff;
    background-image:
        linear-gradient(45deg, var(--app-bar-track) 25%, transparent 25%, transparent 75%, var(--app-bar-track) 75%),
        linear-gradient(45deg, var(--app-bar-track) 25%, transparent 25%, transparent 75%, var(--app-bar-track) 75%);
    background-size: 2px 2px;
    background-position: 0 0, 1px 1px;
    overflow: hidden;
    /* The thumb and arrows are placed from the rect's own edges, not the border box. */
    padding: 0;
    cursor: default;
    user-select: none;
}

/* The arrows and the thumb are blitted at x - 1 / y - 1, i.e. over the outline, so they start at the
   object rect's own edge and are the full cross-axis size. */
.app-scrollbar-arrow,
.app-scrollbar-thumb {
    position: absolute;
    box-sizing: border-box;
    background: var(--app-bar-face);
    /* InitPScroll strokes a black Polyline round the thumb bitmap and then gives it the standard
       raised button face; the OEM arrows carry the same bevel. */
    border: 1px solid rgb(0, 0, 0);
    box-shadow: inset 1px 1px 0 0 rgb(255, 255, 255), inset -1px -1px 0 0 rgb(128, 128, 128);
}

.app-scrollbar-v .app-scrollbar-arrow,
.app-scrollbar-v .app-scrollbar-thumb {
    left: -1px;
    width: var(--app-bar-arrow);
    height: var(--app-bar-arrow);
}

.app-scrollbar-h .app-scrollbar-arrow,
.app-scrollbar-h .app-scrollbar-thumb {
    top: -1px;
    width: var(--app-bar-arrow);
    height: var(--app-bar-arrow);
}

.app-scrollbar-v .app-scrollbar-arrow-start { top: -1px; }
.app-scrollbar-v .app-scrollbar-arrow-end { bottom: -1px; }
.app-scrollbar-h .app-scrollbar-arrow-start { left: -1px; }
.app-scrollbar-h .app-scrollbar-arrow-end { right: -1px; }

.app-scrollbar-v .app-scrollbar-thumb { top: var(--app-bar-thumb); }
.app-scrollbar-h .app-scrollbar-thumb { left: var(--app-bar-thumb); }

/* OBM_UPARROW and friends: a solid black triangle centred on the button face. Sized from the arrow
   so it tracks the bar's own width rather than a fixed pixel count. */
.app-scrollbar-arrow::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    border: calc(var(--app-bar-arrow) / 5) solid transparent;
}

.app-scrollbar-v .app-scrollbar-arrow-start::after {
    transform: translate(-50%, -65%);
    border-top-width: 0;
    border-bottom-color: rgb(0, 0, 0);
}

.app-scrollbar-v .app-scrollbar-arrow-end::after {
    transform: translate(-50%, -35%);
    border-bottom-width: 0;
    border-top-color: rgb(0, 0, 0);
}

.app-scrollbar-h .app-scrollbar-arrow-start::after {
    transform: translate(-65%, -50%);
    border-left-width: 0;
    border-right-color: rgb(0, 0, 0);
}

.app-scrollbar-h .app-scrollbar-arrow-end::after {
    transform: translate(-35%, -50%);
    border-right-width: 0;
    border-left-color: rgb(0, 0, 0);
}

/* ---------------------------------------------------------------------------------------------
   Dialog-level state (Muppets/Equinox.Web#115)
   --------------------------------------------------------------------------------------------- */

/* dlgactive(dh, 0) -> EnableWindow(hWnd, FALSE). A disabled window in Win32 stays on screen and
   simply stops accepting mouse and keyboard input; its painted children grey themselves. The web
   used not to render such a dialog at all, which left a hole where a form's disabled group or page
   should be. pointer-events is the input half; the wash is the greying. */
#appHostContainer .dialog-disabled {
    pointer-events: none;
}

#appHostContainer .dialog-disabled .appDialogMudCard {
    /* Enough to read as inert without hiding the content, which native keeps fully painted. */
    opacity: 0.55;
    filter: grayscale(55%);
}

/* A dialog's background picture, behind every control. paint_pic_windows (win3.c:1136) blits it
   into the client rect before the objects are drawn, switching on the picture's draw mode. */
#appHostContainer .app-dialog-bg {
    position: absolute;
    inset: 0;
    background-repeat: no-repeat;
    background-position: center center;
    pointer-events: none;
    /* Behind every control: the lowest control z-index is 1 (ZINDEX_* in EquinoxControl). */
    z-index: 0;
}

/* iDrawMode 0 - StretchBlt over the whole client rect, aspect ratio ignored. */
#appHostContainer .app-dialog-bg-stretch {
    background-size: 100% 100%;
}

/* iDrawMode 1 - iTileBlt. */
#appHostContainer .app-dialog-bg-tile {
    background-repeat: repeat;
    background-position: 0 0;
}

/* iDrawMode 2 / 3 - centred and unscaled; 3 centre-crops a bitmap larger than the window, which
   is what clipping to the container achieves. */
#appHostContainer .app-dialog-bg-centre {
    background-size: auto;
}

/* A minimised dialog. ShowWindow(SW_SHOWMINIMIZED) on a WS_CHILD page collapses it to its caption;
   the client cannot reproduce Win32's icon-title window, so the caption bar alone stands in. The
   card keeps its own height:100%, so the height is clamped on the container. */
/* A minimised dialog is parked at the bottom-left of the client area and packed left to right,
   which is what the Win32 window manager does with an iconic WS_CHILD window. Native never learns
   the minimised rect — win_normalsize reads WINDOWPLACEMENT.rcNormalPosition because dp->x/y/cw/ch
   keep the *normal* values while iconic — so the placement is the client's to make, and the
   transmitted Top/Left are ignored here. --app-iconic-slot is this dialog's rank among the
   minimised ones (AppDialogComponent.IconicSlot).

   position: fixed rather than absolute because #appHostContainer is a zero-height positioning
   context (every dialog in it is absolutely positioned from the top), so `bottom` has nothing to
   resolve against. The offset clears the status bar, a MudBlazor dense bottom appbar.

   Width is SM_CXMINIMIZED, the width Windows gives a minimised window (160 at 96 DPI); the caption
   is truncated to fit, as Windows truncates it. */
#appHostContainer .dialog-iconic {
    position: fixed !important;
    top: auto !important;
    bottom: 31px;
    left: calc(var(--app-iconic-slot, 0) * 160px) !important;
    width: 160px !important;
    height: 38px !important;
    overflow: hidden;
}

#appHostContainer .dialog-iconic .mud-card-content {
    display: none;
}

#appHostContainer .dialog-iconic .size-handle {
    display: none;
}

/* SM_CXMINIMIZED is narrow (160 at 96 DPI) and Windows truncates the caption to fit, so let the
   text ellipsis rather than push the caption buttons out of the box. */
#appHostContainer .dialog-iconic .mud-card-header-content {
    overflow: hidden;
    min-width: 0;
}

#appHostContainer .dialog-iconic .mud-card-header-content > * {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Host-api pickers (Muppets/Equinox.Web#68) — the stand-ins for the modal Win32 dialogs the method
   language calls. Positioned in the page like the Print and Report windows rather than through
   MudDialog: MudDialog renders into an overlay portal that does not reliably tear down when the
   component is removed from the render tree, which is exactly how these are dismissed (see the note
   on the Connection Lost dialog in AppHostComponent).

   Above the combo and date-picker popups at 2000/2001, because a picker is opened by a method
   running for a field that may itself have one of those open. */
.app-host-api-backdrop {
    position: fixed;
    inset: 0;
    z-index: 2100;
    background: var(--mud-palette-overlay-dark);
}

.app-host-api-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 2101;
    width: min(92vw, 30rem);
    max-height: 85vh;
    background: var(--mud-palette-surface);
    box-shadow: var(--mud-elevation-8);
}

/* The card wears an Equinox dialog's chrome, so it inherits appDialogMudCard - including the
   height: 100% a real dialog needs to fill its sized container, which here would stretch the
   panel to 85vh whatever is in it. The list region is the only part that scrolls; a scrollbar on
   the panel as well would nest two. */
#appHostContainer .app-host-api-panel .appDialogMudCard {
    display: flex;
    flex-direction: column;
    height: auto;
    max-height: 85vh;
    overflow: hidden;
}

#appHostContainer .app-host-api-caption {
    flex: none;
    height: 38px;
    background: var(--mud-palette-background);
}

/* MudBlazor pulls card-header actions into the top-right corner (align-self: flex-start with a
   negative margin-top), which on a fixed-height title bar leaves the close box riding above the
   caption text. Centre it on the bar; the horizontal inset is left as MudBlazor has it so the box
   lines up with the one on a real Equinox dialog. */
#appHostContainer .app-host-api-caption .mud-card-header-actions {
    align-self: center;
    margin-top: 0;
}
#appHostContainer .app-host-api-panel .app-host-api-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 0;
    padding: 0.75rem;
    overflow: hidden;
}

/* A bordered, scrolling well, so the tree reads as a list of somewhere rather than as loose text
   on the card. min-height holds the panel steady between the loading spinner and the roots. */
.app-host-api-tree {
    flex: 1 1 auto;
    min-height: 10rem;
    max-height: 45vh;
    overflow: auto;
    padding: 0.25rem 0;
    border: 1px solid var(--mud-palette-divider);
    background: var(--mud-palette-surface);
}

.app-host-api-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 10rem;
}

/* What the caller will actually be given. Long server paths ellipsis rather than widen the panel;
   the full value is on the title attribute. */
.app-host-api-selection {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: none;
    min-height: 1.75rem;
    color: var(--mud-palette-text-secondary);
    font-size: 0.8125rem;
}

.app-host-api-selection-path {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-host-api-note {
    color: var(--mud-palette-text-secondary);
}

/* The upload control on its own dashed panel: an unadorned <input type=file> on a card gives the
   eye nothing to aim at. */
.app-host-api-drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    padding: 1.25rem 0.75rem;
    border: 1px dashed var(--mud-palette-divider);
    background: var(--mud-palette-surface);
}

/* Kept in the layout while idle so starting a transfer does not shift the buttons. */
.app-host-api-progress {
    flex: none;
}

.app-host-api-progress-idle {
    visibility: hidden;
}

#appHostContainer .app-host-api-actions {
    flex: none;
    gap: 0.5rem;
    justify-content: flex-end;
    padding: 0.5rem 0.75rem;
    border-top: 1px solid var(--mud-palette-divider);
}
