/* @rspond-css-engine
   This stylesheet realises the layout primitives Java emits -- the box classes forceBox() asks for,
   the spacing levels a Tray writes, the hug and stretch classes a tray resolves per axis. Its layout
   declarations are the mechanism the rule exists to protect, so it is held to everything else and not
   to this. There is exactly one of these. */
/* The layer order is restated in every SDK stylesheet, because the first declaration the browser sees is
   the one that counts and stylesheet load order varies by page. Consumer CSS stays unlayered and so
   always wins. */
@layer rspond.reset, rspond.vendor, rspond.document, rspond.component, rspond.theme, rspond.look;

@layer rspond.component {

    :root {

        --rspond-debug-label-color: var(--rspond-brand-primary);
        --rspond-debug-background-color: var(--rspond-status-warning-background);
        --rspond-debug-background-color-top: var(--rspond-status-info-background);
        --rspond-debug-background-color-bottom: var(--rspond-status-info-background);
        --rspond-debug-background-color-left: var(--rspond-status-success-background);
        --rspond-debug-background-color-right: var(--rspond-status-success-background);
        --rspond-debug-border-color: var(--rspond-brand-primary);
        --rspond-debug-font-size: 0.75em;
        --rspond-debug-padding: 2px;
        --rspond-debug-opacity: 0.95;

        /* The type scale: every text size the SDK states names one of these steps rather than a number, so a theme
           resizes all of its text by restating the steps at :root -- an accessibility theme grows them all, the
           smallest most. Declared here, at the component layer, so a theme restates only the steps it changes and
           a stylesheet can name a step without repeating its fallback. Java states the same steps through
           WebFontSizeApi, whose defaults must agree with these; a test holds them equal. */
        --rspond-font-size-tiny: 9.5px;
        --rspond-font-size-x-small: 11px;
        --rspond-font-size-small: 12px;
        --rspond-font-size-default: 13px;
        --rspond-font-size-medium: 14px;
        --rspond-font-size-large: 15px;
        --rspond-font-size-x-large: 16px;
        --rspond-font-size-heading-small: 14pt;
        --rspond-font-size-heading-medium: 16pt;
        --rspond-font-size-heading-large: 18pt;

        /* The hairline every edge is drawn with. A theme for readers with low contrast sensitivity thickens every
           edge at once by restating it; Java states the same token through WebBorderApi.BorderWidth. */
        --rspond-border-width: 1px;

        /* One height for every control an operator can put a value into, so a text field, a dropdown and a toggle bar
           standing side by side line up. Each control's intrinsic height is different -- a native select reserves more
           room than a text input for the same font -- so leaving them to their content makes a ragged row. Java states
           the same token through FormField.ControlHeight, so a theme redefining it here rescales fields stated in
           Java and pills stated in CSS together; the two 38s must agree. */
        --rspond-form-control-height: 38px;

        /* One width for the controls of a form, so they line up in a column: a text box stands exactly at it, since
           its native width is a count of characters every browser draws differently, and a drop-down or a choice
           stands at least at it, growing to fit a longer choice. Java states the same token through
           FormFieldApi.FormFieldControlWidth; the two 180s must agree. */
        --rspond-form-control-width: 180px;

        /* The height a button stands when nothing places it -- the same figure as a form control, so a button
           beside a field reads as one row. A button's face states no vertical padding (a size baked into paint is
           what kept buttons out of toolbar strips), so this floor is where a free-standing button's height comes
           from; a placing tray that resolves the button to a stretch overrides it with the layout's own
           min-height, which is how the same button stands toolbar-height in a strip. */
        --rspond-button-height: 38px;

        --rspond-layout-grid-columns: 2;
        --rspond-layout-flow-gap: 10px;

        /* The space between the buttons of a button group, ButtonGroupApi.ButtonGroupGap -- a level of the spacing
           scale like the others, declared here rather than by every theme because it has one sensible value for most:
           the flow gap a row of buttons has always had. A theme that wants its buttons further apart restates it. */
        --rspond-space-button-group: 10px;
        --rspond-layout-gap: 25px;
        --rspond-layout-grid-gap: 1rem;

        --rspond-form-padding: 20px;
    }

    /* What a component gets for answering forceBox(). A component's tag is chosen by whoever placed it, and an
       element inherits its box-or-text behaviour from that tag -- so a component whose own markup wraps block
       content says which box it needs and this resolves it. A class rather than an inline style, so consumer CSS,
       which is unlayered, can still overrule it. */

    .rspond-box-block {
        display: block;
    }

    .rspond-box-inlineblock {
        display: inline-block;
    }


    .rspond-disabled {
        color: var(--rspond-components-text-disabled);
    }

    /* A disabled link is not a hover target. Without this it still lit up under the pointer: the page's bare
       `a:hover` rule carries an element and a pseudo-class, which outranks the single class above it, so the
       hover colour won and a control that had already refused the click invited it anyway. Wicket strips the
       href from a disabled link, so nothing would have happened -- the paint was simply lying. */
    a.rspond-disabled {
        pointer-events: none;
    }

    .rspond-hidden {
        display: none;
    }

    .rspond-invisible {
        visibility: hidden;
    }

    /* Inherit, never visible: visibility is a property a descendant can override, so a shown component inside a
       hidden ancestor would resurrect itself -- a nested flip book's shown page punched through its hidden parent
       page exactly this way. Shown means "as my ancestors are": a client script revealing a component by flipping
       these classes still reveals it, because its ancestors are visible whenever revealing it makes sense. */
    .rspond-visible {
        visibility: inherit;
    }

    /* A Reserve-mode box may state a rail -- a height, in --rspond-reserve, that its reservation keeps to.
       Reserve alone keeps a hidden box at its natural size, and a box whose content varies would reserve
       whatever its content happens to measure: a form field's three-line note dragged its whole grid row
       taller than its neighbours while showing nothing. With a rail the reservation is a constant: hidden,
       the box clamps to the rail; revealed, the clamp lifts and the content takes its full height. The
       floor holds in both states so revealing an empty box never shrinks it. The size itself is stated
       inline, in Java, by the component that owns it -- these rules only translate the stated value into
       each visibility state, the same way .rspond-invisible above translates the visibility answer. */
    .rspond-reserve {
        min-height: var(--rspond-reserve);
    }

    .rspond-invisible.rspond-reserve {
        max-height: var(--rspond-reserve);
        overflow: hidden;
    }

    .rspond-muted {
        color: var(--rspond-components-text-muted);
    }

    .rspond-horizontal-flow-left-right {
        display: flex;
        flex-direction: row;
        gap: 0.2rem;
    }

    .rspond-horizontal-flow-left-right > div {
        flex: 1;
        white-space: nowrap;
    }

    .rspond-layout-flow {
        display: flex;
        flex-wrap: wrap;
        gap: var(--rspond-layout-flow-gap, 10px);
    }

    .rspond-layout-flow-right {
        display: flex;
        flex-wrap: wrap;
        gap: var(--rspond-layout-flow-gap, 10px);
        justify-content: flex-end;
    }

    /* Vertical and horizontal are the single-axis twins that own the rhythm between their stacked children, so no
       component ever carries a margin of its own. Rather than a container gap (a floor that a per-item override could
       only ever add to), each item declares its own leading space through a rspond-space-<level> class that sets
       --item-space; the two rules below turn that into a top or a left margin along the axis. An item with no class
       gets none (the first item is flush), so a note (or any component) composes in a stack while the tray, not the
       component, decides the spacing. */
    .rspond-layout-vertical {
        display: flex;
        flex-direction: column;
    }

    .rspond-layout-vertical > .rspond-layout-grid-item {
        margin-block-start: var(--item-space, 0);
    }

    /* No flex sizing here. A layout container declares how it arranges its items, never how big it is inside whatever
       holds it -- that is the item's WebSizing, resolved by the placing tray. This rule used to carry `flex: 1`, which is
       `flex-basis: 0` and so discards the container's content size entirely. It was inert wherever a tray's panel tag was
       an ordinary block (the common case) and fired wherever a stylesheet made that panel tag a flex container, so the
       same Java produced two different sizes depending on a stylesheet the tray could not see, and no WebSizing could
       override it -- sizing classes land on the item wrapper in the parent tray, never on this element. */
    .rspond-layout-horizontal {
        display: flex;
        flex-direction: row;
    }

    .rspond-layout-horizontal > .rspond-layout-grid-item {
        margin-inline-start: var(--item-space, 0);
    }

    /* minmax(0, 1fr), not a bare 1fr: bare 1fr is minmax(auto, 1fr), whose auto minimum lets a cell grow to fit its
       widest content and steal width from its siblings -- so equal columns stop being equal and changing one cell's
       content resizes its neighbours. minmax(0, ...) pins every column to an even share; over-wide content wraps or
       scrolls within its own cell. */
    .rspond-layout-grid-flow {
        display: grid;
        grid-template-columns: repeat(var(--rspond-layout-grid-columns, 1), minmax(0, 1fr));
        gap: var(--rspond-layout-gap, 10px);
    }

    /* In development mode Wicket leaves its bookkeeping tags in the DOM, and a tag that is not a box would
       break every flex and grid arrangement around it. Making them contents-transparent is the engine's
       business: the debris is the framework's own, and every application would otherwise rediscover this rule.
       The SDK's own markup emits only wicket:panel, but an application's markup legitimately produces the
       rest of the family -- an enclosure, an i18n message, a border with its body, inherited markup -- and a
       tag left uncovered breaks layout only in development, presenting as an engine defect that vanishes in
       production. CSS has no wildcard for a tag-name family, so the members are enumerated. Deployment
       strips the tags and the rule matches nothing. */
    wicket\:panel,
    wicket\:border,
    wicket\:body,
    wicket\:container,
    wicket\:extend,
    wicket\:child,
    wicket\:fragment,
    wicket\:enclosure,
    wicket\:message,
    wicket\:link {
        display: contents;
    }

    /* The track maximum defaults to 1fr -- columns as equal shares of the container. A GridLayout that states a
       column width writes it over the fallback, and the columns stand at that width with leftover container
       space falling after the last one; minmax's zero minimum is what lets them share the shortfall evenly
       when the container is narrower than the grid, instead of refusing to compress and overflowing it. */
    .rspond-layout-grid {
        display: grid;
        gap: var(--rspond-layout-grid-gap, 1rem);
        grid-template-columns: repeat(var(--rspond-layout-grid-columns, 1), minmax(0, var(--rspond-layout-grid-column-width, 1fr)));
    }

    /* Item sizing. A tray resolves each item's WebSizing -- the item's own declaration, then the tray's default, then the
       layout's -- and writes the outcome here as a class on that item's own tag. The translation has to be written per
       layout because CSS grows and shrinks the two axes by different properties, and which property applies depends on
       whether the axis is the container's main axis or its cross axis:

         flex row (horizontal, flow)     width is the main axis   -> flex-grow / flex: 0 0 auto
                                         height is the cross axis -> align-self
         flex column (vertical)          width is the cross axis  -> align-self
                                         height is the main axis  -> flex-grow / flex: 0 0 auto
         grid (grid, grid flow)          width is the inline axis -> justify-self
                                         height is the block axis -> align-self

       Getting an axis wrong here is silent: the declaration lands on the other axis, where something else already decided
       the answer, so the item looks unstyled rather than wrong. Writing the mapping out in full is what makes one Java
       declaration render the same way in a row, a column and a grid. */

    .rspond-layout-horizontal > .rspond-stretch-width,
    .rspond-layout-flow > .rspond-stretch-width,
    .rspond-layout-flow-right > .rspond-stretch-width {

        /* A weighted fill carries its share as a custom property, so the common case (one part) writes no inline style at
           all. The min-width matters: without it an item refuses to shrink below its content's intrinsic width, and long
           content overflows the container instead of wrapping inside its own share of it. */
        flex: 1 1 auto;
        flex-grow: var(--rspond-stretch-width-share, 1);
        min-width: 0;
    }

    .rspond-layout-vertical > .rspond-stretch-width {
        align-self: stretch;
        min-width: 0;
    }

    .rspond-layout-grid > .rspond-stretch-width,
    .rspond-layout-grid-flow > .rspond-stretch-width {
        justify-self: stretch;
        min-width: 0;
    }

    .rspond-layout-vertical > .rspond-stretch-height {
        flex: 1 1 auto;
        flex-grow: var(--rspond-stretch-height-share, 1);
        min-height: 0;
    }

    .rspond-layout-horizontal > .rspond-stretch-height,
    .rspond-layout-flow > .rspond-stretch-height,
    .rspond-layout-flow-right > .rspond-stretch-height,
    .rspond-layout-grid > .rspond-stretch-height,
    .rspond-layout-grid-flow > .rspond-stretch-height {
        align-self: stretch;
        min-height: 0;
    }

    /* A whole-share item's basis is zero, so its content never counts toward the split and the shares divide the
       container exactly -- the grid fr semantic in a flex container. Paired with the stretch classes and written
       after their rules, because the vertical rule's flex shorthand states an auto basis at two classes of
       specificity and a lone class after it would lose the tie it has to win. */
    .rspond-stretch-width.rspond-share-of-whole,
    .rspond-stretch-height.rspond-share-of-whole {
        flex-basis: 0;
    }

    /* Hugging on the main axis is the absence of growth, and has to be said: a hugging item may sit beside a filling one,
       whose rule would otherwise be the only voice.

       Hugging on the cross axis says nothing at all, deliberately. An item that does not stretch already takes its size
       from its contents, and where it then sits in the leftover space is the container's business -- that is what the
       layout's alignment setting decides. Writing align-self here would answer that question too, and win, because an
       item's own alignment outranks its container's: a hugging item would pin itself to the start and withVerticalAlignment
       would apply to nothing. The layouts whose items hug on an axis default their alignment on that axis to Top or Left,
       so the absence of a rule here still renders as content-sized and start-aligned.

       There is deliberately no cross-axis rule for a grid's hugging items, for that reason. A grid once wrote
       align-self: start on them, which read as a harmless restatement of the layouts' own Top default and was not: it
       made VerticalStretch inert in the one layout a row of equal-height cards is built on, because the item's start
       beat the container's stretch. The row levelled in the inspector's grid container and stayed ragged on the page. */
    .rspond-layout-horizontal > .rspond-hug-width,
    .rspond-layout-flow > .rspond-hug-width,
    .rspond-layout-flow-right > .rspond-hug-width {
        flex: 0 0 auto;
    }

    .rspond-layout-grid > .rspond-hug-width,
    .rspond-layout-grid-flow > .rspond-hug-width {
        justify-self: start;
    }

    .rspond-layout-vertical > .rspond-hug-height {
        flex: 0 0 auto;
    }

    /* Yielding answers a different question from hugging and stretching: those two divide a surplus, this one divides a shortfall. The two are
       independent, which is why the class is written separately rather than folded into the size -- a navigation bar's logo, menu and avatar all hug,
       and only the menu should truncate when the bar narrows.

       These rules come after the hug and stretch rules on purpose. Both carry the same specificity, so source order is what decides, and shrinking is
       the one part of `flex: 0 0 auto` and `flex: 1 1 auto` a caller is allowed to overrule. Everything else those shorthands say still stands.

       Only the main axis appears below, and the omission is the point. Shrinking is one property for the whole item rather than one per axis, so a
       hold written for the height of a row would stop the width from shrinking -- a rule under `.rspond-layout-horizontal > .rspond-hold-height`
       would be actively wrong rather than merely useless. Grid tracks are absent for the same reason: a grid item's shortfall is settled by the
       track's minmax, and flex-shrink says nothing there. */
    .rspond-layout-horizontal > .rspond-hold-width,
    .rspond-layout-flow > .rspond-hold-width,
    .rspond-layout-flow-right > .rspond-hold-width,
    .rspond-layout-vertical > .rspond-hold-height {
        flex-shrink: 0;
    }

    /* Hold on the cross axis: a held width in a column -- and a held height in a row -- never shrinks below its
       content. Shrink is a main-axis property, so the rules above can say nothing here; the cross-axis instrument
       is the minimum. Stated after the stretch rules on purpose, because stretch zeroes the minimum so long content
       can compress, and an item that both stretches and holds is asking for the opposite pair: fill the container
       when content is small, follow the content when it is large. The table's scrolling region is the motivating
       case -- a row must span the viewport when its columns are narrow and the full scrolled width when they are
       wide, or its striping stops mid-pan. Grid layouts are left out here as above: a grid item's floor is the
       track's minmax to settle.

       A held STATED length never reaches this floor: its emitters (SizeStyle, and the tray for a default the item
       cannot see) write the stated value as an inline minimum beside the size, and inline outranks this rule. That
       is deliberate -- max-content is the right floor only when the content is the size's meaning, and a stated
       length means the caller's number wins: under this rule a long line grew the box past the number instead of
       wrapping inside it. In effect this rule governs hugs and stretch-and-hold. */
    .rspond-layout-vertical > .rspond-hold-width {
        min-width: max-content;
    }

    .rspond-layout-horizontal > .rspond-hold-height,
    .rspond-layout-flow > .rspond-hold-height,
    .rspond-layout-flow-right > .rspond-hold-height {
        min-height: max-content;
    }

    /* Following: the item fills the width its container decided and contributes nothing toward deciding it --
       the size for a line of explanation beside a control, which should wrap at the width the control set rather
       than dragging the container out to its own unwrapped length. The containment is what takes the item's
       content out of the container's measurement; the per-layout rules below are the same fill a stretch gets,
       so the item still spans whatever width the others produced. Width only: there is no block-axis containment
       to mirror it with, and the tray refuses a follow resolved onto a height. */
    .rspond-follow-width {
        contain: inline-size;
        min-width: 0;
    }

    .rspond-layout-horizontal > .rspond-follow-width,
    .rspond-layout-flow > .rspond-follow-width,
    .rspond-layout-flow-right > .rspond-follow-width {
        flex: 1 1 auto;
    }

    .rspond-layout-vertical > .rspond-follow-width {
        align-self: stretch;
    }

    .rspond-layout-grid > .rspond-follow-width,
    .rspond-layout-grid-flow > .rspond-follow-width {
        justify-self: stretch;
    }

    /* The min-width is what makes yielding work at all: a flex item refuses to shrink below its content's intrinsic size unless told otherwise, so a
       yield without it renders as nothing happening. Hiding that trap is half the reason this is modelled in Java rather than left to each caller. */
    .rspond-layout-horizontal > .rspond-yield-width,
    .rspond-layout-flow > .rspond-yield-width,
    .rspond-layout-flow-right > .rspond-yield-width {
        flex-shrink: var(--rspond-yield-width-share, 1);
        min-width: 0;
    }

    .rspond-layout-vertical > .rspond-yield-height {
        flex-shrink: var(--rspond-yield-height-share, 1);
        min-height: 0;
    }

    /* The row-axis mirror of the vertical yield-width rule below, for the same measured reason: a row centres its
       items, so on the cross axis an item's height is its content's and there is nothing to shrink -- 300 pixels
       standing in a 60 pixel row. The cap is what yielding means where no shrink exists; short content still hugs. */
    .rspond-layout-horizontal > .rspond-yield-height,
    .rspond-layout-flow > .rspond-yield-height,
    .rspond-layout-flow-right > .rspond-yield-height {
        max-height: 100%;
        min-height: 0;
    }

    /* A grid item's bound is its own area, and the percentage resolves against exactly that -- so a start-justified
       item wider than its track is held to the track, not to the tray: measured at 500 pixels across a 200 pixel
       column before this rule. The height axis is deliberately unwritten here: an implicit grid row grows with its
       content, so there is no bound to yield to, and a rule that can never match is this vocabulary's original
       defect. Write it when grids learn to state row tracks. */
    .rspond-layout-grid > .rspond-yield-width,
    .rspond-layout-grid-flow > .rspond-yield-width {
        max-width: 100%;
        min-width: 0;
    }

    /* Width is the cross axis of a column, so yielding is expressed differently here than on the main axis above, and
       it takes both of these. The min-width lifts the automatic minimum, exactly as the horizontal rule does. The
       max-width is what the cross axis needs instead of a shrink share: a vertical tray aligns its items to start, so
       an item's width is its content's and nothing makes it smaller -- measured at 379 pixels inside a 200 pixel
       frame, a tray overflowing itself, and for a clipped item a text with no boundary left above it to ellipsize
       against. Capping at the container is what "hug, but give way" means on an axis where there is nothing to shrink:
       short content still hugs, and only content with nowhere to go is held to the tray. This axis was simply never
       written, and no unit test could see it -- the markup is identical either way. */
    .rspond-layout-vertical > .rspond-yield-width {
        max-width: 100%;
        min-width: 0;
    }

    /* Spacers carry no CSS of their own. A spacer's size is its whole purpose, and its tray writes it as an ordinary
       height or width -- the tray is what knows whether the spacing falls on the block axis or the inline one. This
       used to be a class per level feeding a --spacer-space custom property, with per-axis rules repeated under all
       five layout containers to turn it back into a size: a second sizing mechanism beside the one every other
       item already used, and one that left the rendered element saying nothing about how big it was. */

    /* A tray's leading and trailing inset carries no CSS at all: the tray writes it as padding on the axis its items
       flow along, because the tray is the one thing that knows that axis. It was a --leading-space custom property fed
       by a class per level, with per-axis rules under each layout container -- and custom properties inherit, so a card
       declaring one set it for every descendant and each nested tray re-applied the card's inset as its own, once per
       level of nesting. Padding does not inherit. */

    /* Spacing levels. Each class maps a semantic level (chosen in Java as Spacing.Medium, etc.) to a themeable
       magnitude token, exposed as --item-space for the single-axis layout rules above to read. The magnitude lives in
       the theme's tokens.css, never here, so an application re-spaces every screen by overriding the tokens. */
    .rspond-space-none {
        --item-space: 0;
    }

    .rspond-space-default {
        --item-space: var(--rspond-space-default);
    }

    .rspond-space-tiny {
        --item-space: var(--rspond-space-tiny);
    }

    .rspond-space-small {
        --item-space: var(--rspond-space-small);
    }

    .rspond-space-medium {
        --item-space: var(--rspond-space-medium);
    }

    .rspond-space-large {
        --item-space: var(--rspond-space-large);
    }

    .rspond-space-x-large {
        --item-space: var(--rspond-space-x-large);
    }

    .rspond-row-odd {
        background-color: var(--rspond-row-odd-background);
    }

    /* ===== Form Field Error Indicators ===== */

    /* One of the two `!important` declarations left in the SDK, and a deliberate one. The class is added to whichever
       decorator element a field puts its label in, so the rule cannot know what it is competing with -- and every
       candidate (the decorator's own muted label colour, a theme's text colour) is an equally specific class rule in
       this same layer, where the winner would otherwise be decided by stylesheet load order. An error must be red
       whatever else is true, so it says so. Layers cannot express this: it is a state that has to outrank the
       component's own typography, and the layer above the component belongs to looks. */
    .rspond-field-error {
        color: var(--rspond-status-danger) !important;
        font-weight: bold;
    }

    /* ===== Form Controls ===== */

    .rspond-form-control {
        background: var(--rspond-page-background-primary);
        border: var(--rspond-border-width) solid var(--rspond-border-color);
        border-radius: var(--rspond-border-radius-medium);
        color: var(--rspond-components-text-primary);
        padding: 0 12px;
        font-size: var(--rspond-font-size-medium);
        font-family: inherit;
        transition: border-color 0.15s ease, box-shadow 0.15s ease;
    }

    .rspond-form-control:focus {
        outline: none;
        border-color: var(--rspond-status-info);
        box-shadow: var(--rspond-components-focus-ring);
    }

    /* A browser paints its own pale fill over a control it autofilled -- saved credentials on a sign-in form, most
       often -- which lands as bright yellow on a dark theme and looks like a broken control rather than a helpful
       one. The background cannot be overridden directly, so an inset shadow the size of the field stands in for it,
       and the text colour is forced back with the -webkit-text-fill-color the same rule respects. */
    .rspond-form-control:-webkit-autofill,
    .rspond-form-control:-webkit-autofill:hover,
    .rspond-form-control:-webkit-autofill:focus {
        -webkit-text-fill-color: var(--rspond-components-text-primary);
        -webkit-box-shadow: 0 0 0 1000px var(--rspond-page-background-primary) inset;
        caret-color: var(--rspond-components-text-primary);
    }

    /* A search input brings the browser's own clear button with it, and the control already draws its own
       affordances -- a search glyph at the leading edge, and the field's own clearing where a component asks for
       it. Two clear buttons read as a broken control, and this one answers to no theme. The type is chosen in Java
       so the browser stops offering the reader's address book on it; this is the decoration that choice drags
       along, and turning it off is appearance, which is this stylesheet's business. */
    .rspond-form-control::-webkit-search-cancel-button,
    .rspond-form-control::-webkit-search-decoration {
        -webkit-appearance: none;
        appearance: none;
    }

    .rspond-form-control::placeholder {
        color: var(--rspond-components-text-muted);
    }

    /* The placeholder yields the moment the field is focused -- CSS owns this, so no script attribute has to. */
    .rspond-form-control:focus::placeholder {
        color: transparent;
    }

    .rspond-form-control:disabled {
        opacity: var(--rspond-components-opacity-disabled);
        cursor: not-allowed;
    }

    /* The state an input's validity mark puts on the element the reader types in while its text is not acceptable.
       A script adds and removes the class in place, without repainting the control, so the rule is all the mark is.
       It comes after the focus rule and matches it in specificity, because an invalid control is usually the focused
       one -- the reader has just committed the text -- and its border must still say so. */
    .rspond-invalid,
    .rspond-invalid:focus {
        border-color: var(--rspond-status-danger);
    }

    /* The chevron is drawn rather than left to the browser. A native select puts its arrow inside the control's own
       padding, so a select that hugs its text -- a drop-down in a list row, as wide as its longest choice -- has the
       arrow pressed against its border, and the arrow's inset differs from browser to browser. Drawn as a background
       image it stands a fixed inset from the edge whatever the select's width, and the padding keeps the text clear of
       it. It is the chooser's glyph and reading, quoted from the time chooser: right while closed, down while focused,
       the honest proxy for open that a background image can key on. The stroke is a literal mid grey for the reason
       the time chooser gives -- a background-image SVG cannot read a custom property -- and it reads as muted on both
       themes. */
    .rspond-form-select {
        -webkit-appearance: none;
        appearance: none;
        background-color: var(--rspond-page-background-primary);
        background-image: url(data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20width=%2724%27%20height=%2724%27%20viewBox=%270%200%2024%2024%27%20fill=%27none%27%20stroke=%27%23888%27%20stroke-width=%272%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20%3E%3Cpath%20d=%27m9%2018%206-6-6-6%27%20/%3E%3C/svg%3E);
        background-repeat: no-repeat;
        background-position: right 8px center;
        background-size: 14px;
        border: var(--rspond-border-width) solid var(--rspond-border-color);
        border-radius: var(--rspond-border-radius-medium);
        color: var(--rspond-components-text-primary);
        padding: 0 30px 0 12px;
        font-size: var(--rspond-font-size-medium);
        font-family: inherit;
        transition: border-color 0.15s ease, box-shadow 0.15s ease;
    }

    .rspond-form-select:focus {
        outline: none;
        border-color: var(--rspond-status-info);
        box-shadow: var(--rspond-components-focus-ring);
        background-image: url(data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20width=%2724%27%20height=%2724%27%20viewBox=%270%200%2024%2024%27%20fill=%27none%27%20stroke=%27%23888%27%20stroke-width=%272%27%20stroke-linecap=%27round%27%20stroke-linejoin=%27round%27%20%3E%3Cpath%20d=%27m6%209%206%206%206-6%27%20/%3E%3C/svg%3E);
    }

    .rspond-form-select:disabled {
        opacity: var(--rspond-components-opacity-disabled);
        cursor: not-allowed;
    }

    /* ===== Touch Targets ===== */

    /* The floor under every interactive control's height, inert until a theme sets the token. WCAG asks for
       24px as a minimum target size and 44px for readers who need more, and an accessible theme should be able
       to say so as one value rather than reaching into each component's chrome with its own layout rules. The
       first three classes are this file's own chrome vocabulary; everything else opts in by wearing the marker
       class ComponentApi.TouchTargetClass -- every SDK link carries it, and clickable chrome that is not an anchor
       adds it where it is built -- so this rule never has to name a class some other file owns. align-content
       centers a grown box's content, and is ignored wherever the element is already a nowrap flex container
       whose own align-items governs. */
    .rspond-btn,
    .rspond-form-control,
    .rspond-form-select,
    .rspond-touch-target {
        min-height: var(--rspond-touch-target-minimum, auto);
        align-content: center;
    }

    /* Except a multi-line one, whose text starts at the top. Centering is right for a control holding one line --
       the line sits in the middle of whatever height the box ended up with -- and wrong the moment the box is meant
       to hold several, where it puts the first line, and the caret waiting on it, halfway down an empty field. It
       stayed invisible while a text area was one line tall for want of a height of its own; giving it one is what
       brought the centering into view. The floor above still applies: only the alignment is withdrawn.

       Withdrawing the centering is what forces the rest of this rule. A single-line control carries no vertical
       padding because it does not need any -- centering places its one line -- so the shared `0 12px` leaves a text
       area with an inset on one axis and none on the other, and the caret waiting on the first line lands flush
       against the top border while the text hangs 12px in from the left. A box meant to hold a paragraph has to
       inset on both axes or it does not read as one, and 12px was chosen for a short control: across a paragraph's
       width it reads as a wide left margin, so the horizontal inset comes in with it. `normal` line height (about
       1.2 here) sets wrapped lines closer together than prose is ever set; 1.5 is the same figure the buttons use.

       Height is the user's to choose and width is the layout's -- a tray placed this control, and the user-agent
       default of `resize: both` lets a drag pull it wider than the tray that placed it. */
    textarea.rspond-form-control {
        align-content: start;
        padding: 8px 10px;
        line-height: 1.5;
        resize: vertical;
    }

    /* ===== Focus ===== */

    /* A control the keyboard can reach has to show that it has been reached, and up to here the only thing saying so
       was the user agent's own ring -- which differs by browser, is drawn in a colour no theme chose, and on a link
       whose look already paints a border reads as a second border rather than as focus. The same two markers that
       say "this is a control" for the touch-target floor say it here: every SDK link wears the touch-target class,
       and clickable chrome that is not an anchor -- a left-nav group heading, a menu row -- wears the toggle class.
       :focus-visible rather than :focus, so a mouse click on a link does not leave a ring behind it; the browser
       decides which inputs deserve the indicator and it already knows the answer.

       A wash over the whole control rather than a ring around it, and the reason is that a ring has nowhere to go
       here. An SDK link is a block that fills the box its tray gave it, so its edges are usually flush with that
       container's: draw the ring outside and any scrolling ancestor clips it -- a card body scrolls, and a ring
       drawn outside lost its left and right sides to it and rendered as two stray horizontal rules -- while drawing
       it inside puts a hard edge within a pixel or two of the text, which reads as a box crammed around the words.
       There is no offset that escapes both, because the control has no margin of its own to spend. A translucent
       fill has no edge to place, so neither problem exists: it cannot be clipped, and it grows and shrinks with the
       control instead of hugging it.

       An inset box-shadow rather than a background, because a look owns the background -- the radio tab bar fills
       its selected segment with the brand colour -- and a focused control has to show that it is focused even while
       wearing one. The shadow paints over whatever background is underneath and under the text, so the wash rides
       on top of a look rather than competing with it. This is also why the tab bar's active-tab underline is a
       gradient: it used to be an inset box-shadow, and a theme rule outranks this layer, so it cancelled the wash on
       the one tab most likely to be focused.

       The radius is the token .rspond-btn uses, so a link already wearing a button face keeps the corners it had
       while a plain text link is not washed square. A look sits in a layer above this one, so a look stating its own
       radius still wins.

       The spread is a flat length rather than a viewport unit, which it was first written as. A control's size has
       nothing to do with the window's -- a wide row inside a horizontal scroller can exceed it -- and tying the two
       together would have left the wash short of the edges in exactly the case the control is widest. */
    .rspond-touch-target:focus-visible,
    .rspond-toggle:focus-visible {
        box-shadow: inset 0 0 0 9999px var(--rspond-components-focus-wash);
        border-radius: var(--rspond-border-radius-medium);

        /* The wash lights the box; this lights what is written on it. A filter operates on the pixels the element
           paints itself, so where the background is transparent it brightens the label and the icon and leaves the
           box alone -- the half a wash cannot reach, an inset shadow being painted beneath the text. It is also the
           last property on a control that nothing else claims: a look or a theme sits in a layer above this one and
           has already taken `background` and `box-shadow` out of contention, and a state that any look can cancel is
           not a state. The value is a theme's to choose, and the light theme chooses `none` -- brightening near-black
           text on a white card only walks it toward the background. `none` also costs nothing, where brightness(1)
           would still make the element a containing block for positioned descendants and open a stacking context;
           overlays anchor inside their triggers here, and a focused trigger is the common case. */
        filter: var(--rspond-components-focus-filter);

        /* The wash replaces the user agent's ring rather than joining it. While this rule stated an outline of its
           own that happened for free; a wash does not touch the property, so the browser kept drawing its ring on
           top -- an amber square in Chromium's dark mode, which is the very shape the wash exists to avoid. Removing
           a focus ring is only safe when something takes its place, and the line above is that something. */
        outline: none;
    }

    /* ===== Buttons ===== */

    /* No vertical padding, deliberately: a face that pads vertically has baked a size into its paint, and that is
       exactly what kept buttons out of toolbar strips -- "toolbar height" meant "wear a different face". The box's
       height comes from the button-height floor below (a free-standing button) or from the placing tray (a strip
       that stretches its items), and the touch-target rule's align-content centers the label in whichever box the
       button ends up with. max() keeps the accessibility floor a theme states through the touch-target token.
       No display either, for the same reason one level out: whether the button is a box, and how wide, is the
       component's forceBox() answer and its placement's sizing, and a face that states display overrules both
       from the same layer -- a button stretched to a cell then painted at the width of its words. */
    .rspond-btn {
        border-radius: var(--rspond-border-radius-medium);
        padding: 0 20px;
        min-height: max(var(--rspond-button-height), var(--rspond-touch-target-minimum, 0px));
        font-size: var(--rspond-font-size-medium);
        font-weight: 500;
        font-family: inherit;
        cursor: pointer;
        transition: all 0.15s ease;
        text-decoration: none;
        text-align: center;
        line-height: 1.5;
    }

    .rspond-btn:disabled {
        opacity: var(--rspond-components-opacity-disabled);
        cursor: not-allowed;
    }

    /* The three faces themselves are not here: they are looks, so a theme states them in its own look-layer
       stylesheet, where they outrank the document's voice. This layer keeps only what a button is regardless of
       which face it wears. */

    /* The disabled face without the disabled behavior: a control wearing this class looks and announces disabled
       (pair it with aria-disabled) but still receives clicks, so activating it can explain what is wrong -- a truly
       disabled control is a dead end that cannot say why. Defined after the button rules so its cursor wins over
       the button's own pointer. */
    .rspond-styled-disabled {
        opacity: var(--rspond-components-opacity-disabled);
        cursor: not-allowed;
    }

    /* ===== Card Structure ===== */

    /* The band's arrangement is its tray's, not this file's: it is a horizontal tray that centres its two items,
       and the gap between them is the leading space of the second, stated as a level. What is left here is the
       band's own paint. */
    .rspond-title-header {
        background: transparent;
        padding-bottom: 5px;
        border-radius: var(--rspond-border-radius-large) var(--rspond-border-radius-large) 0 0;
        font-size: var(--rspond-font-size-heading-large);
    }

    /* The left tray states its own stretch through the sizing model now, so all that is left here is the floor that
       lets it shrink: a flex item's minimum is its content's intrinsic width unless it is told otherwise, and
       without this a long title is clipped at the card's edge rather than wrapping. */
    .rspond-card-left-title-tray {
        min-width: 0;
    }

    .rspond-title-text {
        color: var(--rspond-components-text-primary);
        font-size: var(--rspond-font-size-large);
        font-weight: 500;
        letter-spacing: -0.01em;
        margin: 0;
        display: block;
    }

    /* One hairline vocabulary for every rule the SDK draws: a separator reinforces a boundary that spacing and
       type weight already state, so it stays a single pixel of the theme's subtle border color -- present, never
       competing with the content it divides. The theme dials it through one token. */

    .rspond-horizontal-line,
    .rspond-title-line {
        border: none;

        /* A step past the subtle border toward the muted text voice, mixed rather than picked so the nudge lands
           in each theme's own contrast direction -- toward light on a dark ground, toward dark on a light one. */
        border-top: var(--rspond-border-width) solid var(--rspond-line-color,
            color-mix(in srgb, var(--rspond-border-color-subtle) 80%, var(--rspond-components-text-muted)));
        margin: 0.5rem 0 0;
        width: 100%;
    }

    .rspond-note {
        display: block;
    }

    /* The second line of a label that carries a detail. Block, because the two lines stack; a small top gap,
       because zero reads as one wrapped sentence and a full line's worth reads as two unrelated things. The
       quiet colour and the smaller face come from rspond-muted and the small element, which is the same voice
       a Note speaks in -- a detail and a note say the same kind of thing in different places. */
    .rspond-label-detail {
        display: block;
        margin-top: 2px;
        overflow-wrap: anywhere;

        /* A detail follows: it wraps at whatever width its label's other content or placement decided, and
           contributes nothing toward deciding it. A line of explanation measured unwrapped is wider than most of
           the screens it explains, and every container above it hugs that width unless someone remembers to stop
           it -- so the abstention is the element's own, not a call site's to remember. */
        contain: inline-size;

        /* Wrapping is half of that contract, so the detail states it rather than inheriting whatever it is
           nested in. A one-line caption is exactly what a button, a menu item or a toolbar control sets nowrap
           for, and a detail inside one inherited it: containment capped the box at the width it was given while
           nowrap pushed the text straight out of it, so the explanation painted past the control holding it. */
        white-space: normal;
    }

    /* The padding arrived with the rest of the card's styling from the application page's stylesheet. It reads as
       though the card's own trays should own their insets -- and some of them do, like the content icon -- but they
       do not all do so today, and without this the title band and body sit flush against the border. Whether the
       bands should instead be full-bleed and inset themselves is a design question, not a refactoring one. */
    .rspond-card {
        background: var(--rspond-card-background);
        border-radius: var(--rspond-border-radius-large);
        border: var(--rspond-border-width) solid var(--rspond-border-color-subtle);
        box-shadow: var(--rspond-shadows-small);
        padding: 16px 20px;
        display: flex;
        flex-direction: column;
    }

    /* Arbitrary paragraphs dropped into a card get a readable default, since a caller composing a card around
       prose content should not have to style it. The zeroed margin is the browser's own paragraph default being
       removed, the same species as Reset.css's body margin: nothing places a paragraph, so the space around it is
       the card's trays' to state, not the user agent's. */
    .rspond-card p {
        font-size: var(--rspond-font-size-default);
        color: var(--rspond-components-text-secondary);
        margin: 0;
    }

    .rspond-card-body {
        display: flex;
        flex: 1;
        min-height: 0;
        background: var(--rspond-card-background);
        border-radius: 0 0 var(--rspond-border-radius-large) var(--rspond-border-radius-large);
    }

    /* The inset only. Where the icon sits in this band is the band's layout, stated in Java, so a rule that centered
       it here would be a second opinion -- and, since the band's tray is what has width to center across, the wrong
       one: the icon's own box is exactly one glyph wide and centering anything inside it moves nothing. */
    .rspond-card-content-icon {
        padding: 1rem 1.25rem 0;
    }

    /* Vertical only. The horizontal inset used to be repeated here, on top of the one the card already applies to
       everything inside it, so a toolbar hung 20px further in than the title above it and the body below it -- three
       bands of one card, each starting at a different place. The card owns the inset from its border; a band owns
       only the space above and below itself. */
    /* Paint only. The band is a horizontal tray now, like the title band above it, so its arrangement -- and the
       widths of the two strips it places -- belong to the Java that composes it. */
    .rspond-card-toolbar-band {
        padding: 1.0rem 0 0.5rem;
    }

    .rspond-card-toolbar-band + .rspond-card-body .rspond-card-content {
        padding-top: 0.25rem;
    }

    /* A toolbar's disabled control fades as well as greys, because a toolbar is a row of equals and colour alone
       does not separate one of them from the rest. Not being clickable is every disabled link's business, stated
       once above rather than repeated here. The two toolbar classes are name hooks only: the strips are toolbar
       trays placed by the band, so their arrangement and widths are stated where they are composed. */
    .rspond-card-left-toolbar a.rspond-disabled,
    .rspond-table-toolbar a.rspond-disabled {
        opacity: var(--rspond-components-opacity-disabled);
    }

    /* No padding of its own: the card already insets everything it holds, and a second inset here indented the content
       past the title and the divider rule above it. */
    .rspond-card-content {
        flex: 1;
        overflow-y: auto;
        min-height: 0;
    }

    /* ===== Modal Footer (Form Buttons Area) ===== */

    /* The form owns the gap between its field area and its button (footer) area, so neither the fields tray nor the
       footer carries a margin of its own. */
    .rspond-form-body {
        display: flex;
        flex-direction: column;
        gap: var(--rspond-space-default);
    }

    .rspond-form-actions {
        padding-top: 8px;
    }

    /* ===== Divider Label ===== */

    .rspond-divider-label {
        width: 100%;
        white-space: nowrap;
        color: var(--rspond-components-text-secondary);
        background-color: transparent;
        border-collapse: collapse;
    }

    .rspond-divider-label hr {
        border: none;
        border-top: var(--rspond-border-width) solid var(--rspond-border-color-subtle);
    }

    .rspond-divider-label-text {
        width: 1px;
        padding: 0 0.5rem;
        color: var(--rspond-components-text-disabled);
        font-size: var(--rspond-font-size-medium);
        font-weight: 500;
    }

    /* ===== List ===== */

    .rspond-list-group {
        list-style: none;
        padding: 0;
        margin: 0;
    }

    .rspond-list-group-item {
        padding: 8px 12px;
        border-bottom: var(--rspond-border-width) solid var(--rspond-border-color);
    }

    .rspond-list-group-item:last-child {
        border-bottom: none;
    }

    .rspond-list-row {
        display: flex;
        align-items: center;
    }


    /* What a clipped line does with the text that does not fit, written here because the decision is a TextClipMode
       the layout engine resolved and wrote as a class -- never a stylesheet's to make. Both values need the element
       to be a box and the chain above it to yield; Component gives it the box and Tray gives it the yield, so by the
       time either rule matches, everything it depends on is already true. */

    .rspond-clip-text,
    .rspond-clip-text-ellipsis {
        white-space: nowrap;
        overflow: hidden;
    }

    /* The ellipsis is the whole difference between the two: one cuts, the other says it cut. */
    .rspond-clip-text-ellipsis {
        text-overflow: ellipsis;
    }
}
