/*
 * The application's one stylesheet.
 *
 * There is no bundler and no preprocessor, so what is written here is exactly what
 * the browser reads: plain CSS, no build step, no `@import`. An import would be
 * forbidden even if it were convenient — it is discovered only after this file has
 * been parsed, so it serialises the fetch, and it would carry no version of its own.
 * app\components\Stylesheet supplies this file's URL and explains the version in it.
 *
 * Sections, in this order:
 *
 *   1. Tokens   — every colour, and the type, spacing and measure scales
 *   2. Base     — the handful of user-agent differences that bite, and the typography
 *   3. Layout   — the five primitives every page is built from
 *   4. Forms    — the class names the framework's field widget emits
 *   5. Notices  — one shape for reporting an outcome
 *   6. States   — a value the application does not have
 *   7. Focus    — last on purpose, so nothing below can override the indicator
 *
 * The token block ends at the marker comment `end of tokens`. That marker is
 * load-bearing rather than decorative: a test finds it to know where "below the
 * tokens" begins, and asserts that every colour-bearing declaration after it names a
 * token instead of repeating a value. A prose heading is not a boundary a test can
 * find, so the marker is worded to be one.
 */

/* ==========================================================================
   1. Tokens
   ==========================================================================

   Every colour the product uses is defined here and nowhere else, which is what
   lets one test assert the contrast of the whole product. Each carries the
   background it is used on and the minimum that applies to it: 4.5:1 for anything a
   reader reads, 3:1 for a boundary that identifies a control, and none for a
   decoration that carries no information. The ratios in the comments were computed
   rather than estimated.

   The type, spacing and measure scales are defined here as scales, whether or not
   every step of them is used twice. A length that belongs to no scale is written
   where it is used — a rule demanding a token for every length would demand one for
   `0` and `100%`, which is a rule no test could hold. */

:root {
    /* The page, and the background of a control that has one. A background is what
       foregrounds are measured against rather than a foreground itself. One surface
       and not two: a second background would double the pairs every foreground has
       to be measured against, for decoration this product does not need. */
    --color-surface: #ffffff;

    /* Body text, and the focus indicator. 16.5:1 on the surface. */
    --color-text: #1c1f23;

    /* A value the application does not have. 6.1:1 — quiet is a smaller voice, not
       an exemption from the 4.5:1 every reader is owed. */
    --color-text-quiet: #5c636b;

    /* Text drawn on a filled control. 6.7:1 on --color-accent, 10.6:1 on
       --color-accent-strong. */
    --color-inverse-text: #ffffff;

    /* A link, and the primary button's fill. 6.7:1 on the surface, which is the same
       pair as --color-inverse-text on this colour, so one token serves both. */
    --color-accent: #0b5cad;

    /* The primary button under the pointer. 10.6:1 on the surface. */
    --color-accent-strong: #073f78;

    /* A control's own edge. This is the boundary that tells a reader where the
       control is, so 3:1 is its minimum; it is 4.5:1, because the grey a designer
       reaches for here commonly sits near 1.5:1. */
    --color-control-border: #6f7782;

    /* Between table rows. Decorative, and 1.3:1: the row markup carries the
       information, so no minimum applies — which is why this is a separate token
       from the one above rather than a lighter use of it. */
    --color-separator: #dde1e6;

    /* A rejected field's border and the message under it. 6.8:1 on the surface, so
       it clears the text minimum as well as the boundary one. */
    --color-error: #b02417;

    /* A failure notice's background. Body text on it is 15.0:1. */
    --color-error-surface: #fdf1ef;

    /* A success notice's edge. 6.5:1 on the surface. */
    --color-success: #1a6b41;

    /* A success notice's background. Body text on it is 15.2:1. */
    --color-success-surface: #eff7f2;

    /* The typefaces already on the machine, in the order a machine is likely to have
       them. No web font: it would have to be self-hosted, since nothing here is
       fetched from another origin, and that is a licence, several files, a version
       each and a subsetting question in exchange for a typeface these users do not
       read anything else in. */
    --font-family-base: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    /* Relative to the size the reader chose in their own browser, never absolute:
       there is no root font size anywhere in this file. */
    --font-size-page-title: 1.75rem;
    --font-size-section-title: 1.25rem;
    --font-size-small: 0.875rem;

    --line-height-base: 1.5;
    --line-height-title: 1.2;

    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 1rem;
    --space-4: 1.5rem;
    --space-5: 2rem;

    /* How wide a page and a column of form fields are allowed to become. In rem, so
       they grow with the reader's own font size, and with no minimum — a maximum
       width narrower than the viewport is already fluid, which is why this file needs
       no breakpoint. */
    --measure-page: 60rem;
    --measure-form: 26rem;

    /* A control's border, and the heavier one a rejected control gets. Two tokens
       because the difference between them is the design decision: a rejected field
       has to read as wrong where colour is not perceived at all. */
    --border-width-control: 1px;
    --border-width-invalid: 2px;

    --radius: 0.25rem;
}

/* end of tokens */

/* ==========================================================================
   2. Base
   ========================================================================== */

html {
    /* One colour scheme ships. Dark mode would double the palette and double the
       contrast matrix for a tool opened twice a month in an office; `only` is the
       part that also stops a browser applying its own dark transform to controls
       nobody designed for it. Not a colour: it names which schemes exist. */
    color-scheme: only light;
}

/* Border-box everywhere, because the user agent's default makes a padded control
   wider than the width it was given. */
*,
*::before,
*::after {
    box-sizing: border-box;
}

body {
    margin: 0;
    background-color: var(--color-surface);
    color: var(--color-text);
    font-family: var(--font-family-base);
    line-height: var(--line-height-base);
}

/* The one user-agent difference that bites in a form: a control does not inherit the
   page's typeface or size unless it is told to. */
button,
input,
select,
textarea {
    font: inherit;
}

/* The block margins of the flow elements this product renders, zeroed so that vertical
   rhythm has exactly one owner — the stack primitive below — rather than two mechanisms
   whose margins collapse into each other and leave the spacing between two blocks
   depending on which of them was taller. Not a reset: nothing this product does not
   render is touched. */
h1,
h2,
p,
ul,
dl,
table,
form {
    margin-block: 0;
}

h1 {
    font-size: var(--font-size-page-title);
    line-height: var(--line-height-title);
}

h2 {
    font-size: var(--font-size-section-title);
    line-height: var(--line-height-title);
}

/* A value quoted inside a sentence. A user agent keeps a second, smaller default size
   for monospace text, so `+47` in a sentence renders about a third smaller than the
   sentence around it. Naming the generic family twice is what suppresses that: the
   smaller default applies only where the computed family is the single keyword
   `monospace`, so the duplicate is load-bearing rather than a typo. Measured here —
   `font-size: 1em` does *not* fix it, because that smaller default is what the `em` is
   resolved against. The monospace face itself is wanted; only its size was wrong. */
code {
    font-family: monospace, monospace;
}

a {
    color: var(--color-accent);
}

a:hover {
    color: var(--color-accent-strong);
}

dt {
    font-weight: 600;
}

dd {
    /* The user agent indents a description by 40 pixels, which is the only reason
       this rule exists. */
    margin-inline-start: 0;
}

dd + dt {
    margin-block-start: var(--space-3);
}

/* ==========================================================================
   3. Layout

   Five primitives, which between them cover every page this product has and every
   page it is going to have. There is no width or breakpoint media query anywhere in
   this file, and there is nothing for one to do: the shell's maximum width is
   narrower than any viewport it meets, so it is already fluid, and the only thing
   that cannot shrink is a wide table, which gets a region that scrolls.
   ========================================================================== */

/* The page shell: one centred column with breathing room at 320 pixels. */
.page {
    max-width: var(--measure-page);
    margin-inline: auto;
    padding: var(--space-5) var(--space-3);
}

/* Space between siblings, applied between them rather than around them, so the first
   child sits against the shell's own padding and the last adds nothing below it. */
.stack > * + * {
    margin-block-start: var(--space-4);
}

/* A section heading gets more air above it than the space between ordinary siblings,
   so that it reads as belonging to what follows it rather than floating between two
   sections. */
.stack > h2 {
    margin-block-start: var(--space-5);
}

/* A column of form fields, narrower than the page: a text input as wide as a monitor
   is harder to read a value back from, not easier. */
.form {
    max-width: var(--measure-form);
}

/* Actions side by side, wrapping rather than overflowing when the viewport or the
   reader's font size leaves no room for the row. */
.actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

/* Every table, whether or not it is inside a scrolling region.
   These rules were scoped to `.table-scroll` for as long as every table in the product
   was wide enough to need one — and the companies list, which is not, therefore had no
   cell padding, no row separators and no width at all. The fix is the scope rather than
   a wrapper: a region that scrolls is a property of one table, and how a cell is set is
   a property of all of them. */
table {
    width: 100%;
    border-collapse: collapse;
}

/* One step of the spacing scale all round rather than two. A data table is read across
   its rows, and the wider padding a paragraph wants pushed the widest table past the
   width of the page column — which the browser then paid for by wrapping names and
   timestamps that had no need to wrap. */
th,
td {
    padding: var(--space-2);
    border-block-end: var(--border-width-control) solid var(--color-separator);
    text-align: left;
}

th {
    font-weight: 600;
}

/* The heading row of a table that scrolls is the one thing here that does not wrap — the
   headings are the shortest strings in it, and a heading broken over two lines above rows
   of one makes the whole table look like an accident. This is the only `nowrap` in the
   file, and it stays scoped to the scrolling region: `nowrap` outside one has nowhere to
   put the overflow but the page, which is what StylesheetRulesTest refuses. The cells
   themselves wrap, because a name or a timestamp on two lines is better than an action
   column pushed out of sight on a screen wide enough to have shown it. */
.table-scroll th {
    white-space: nowrap;
}

/* Columnar digits, so that a mobile number and a date can be compared down their
   column instead of read one at a time. A property of the typefaces already named
   above rather than a second family fetched to get it. */
td {
    font-variant-numeric: tabular-nums;
}

/* A wide table inside a region that scrolls. It has to scroll rather than shrink
   because an email address and a mobile number offer a browser nowhere to break them,
   so that table has a minimum width no narrow viewport can meet. The region carries
   tabindex="0" in the markup so that a reader who cannot use a pointer can scroll it —
   an extra tab stop, accepted for the scrolling and not worth paying for a table that
   cannot overflow. */
.table-scroll {
    overflow-x: auto;
}

/* The navigation, and the one place in this product where two groups of links sit on one
   line and pull apart. `justify-content: space-between` does that without a spacer
   element and without either group having to know the other's width.

   It wraps, and that is the whole responsive story: at a narrow viewport the account
   group drops under the tabs rather than being pushed off the side. No media query — the
   flex container decides, which is what keeps the stylesheet's no-breakpoint rule true
   here as everywhere else.

   Bounded to the same measure as the page beneath it, so the tabs line up with the
   heading under them rather than starting at the window's edge. */
.navigation {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    align-items: center;
    justify-content: space-between;
    max-inline-size: var(--measure-page);
    margin-inline: auto;
    padding: var(--space-3) var(--space-4);
    border-block-end: var(--border-width-control) solid var(--color-separator);
}

.navigation-tabs,
.navigation-account {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-4);
    align-items: center;
}

/* The current tab is marked by the attribute a screen reader reads, and dressed from that
   same attribute — so there is no class that could be set without it and the two cannot
   disagree. Underlined rather than coloured, because colour alone may not carry meaning,
   and the weight is unchanged so the line does not shift when the page changes. */
.tab[aria-current='page'] {
    color: var(--color-text);
    text-decoration-thickness: var(--border-width-invalid);
    text-underline-offset: var(--space-1);
}

/* ==========================================================================
   4. Forms

   These are the class names yii\widgets\ActiveField emits, checked against the
   markup the application actually rendered on 2026-08-07 rather than against any
   framework's documentation: `form-group` on the field container, `control-label`,
   `form-control` on the input, `hint-block`, `help-block` on the message container,
   and `has-error` added to the container when the field was rejected.

   They will read as though a well-known CSS framework is installed. None is. There
   is no bundler and nothing is fetched from another origin, so these are the widget's
   own names and nothing more; styling them as emitted is what means no form in this
   application — including one written later — needs a class added to it.
   ========================================================================== */

.form-group {
    margin-block-end: var(--space-3);
}

.control-label {
    display: block;
    margin-block-end: var(--space-1);
    font-weight: 600;
}

.form-control {
    display: block;
    width: 100%;
    padding: var(--space-2);
    border: var(--border-width-control) solid var(--color-control-border);
    border-radius: var(--radius);
    background-color: var(--color-surface);
    color: var(--color-text);
}

/* A checkbox is labelled by containment — the widget wraps the input and the words in
   one label and gives that label no class of its own — so this is the label that is
   not `control-label`. */
.form-group > label:not(.control-label) {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

/* A repeating group of rows — the rule editor's columns and pay codes. Two fields side by
   side, wrapping to one column when the viewport or the reader's own font size leaves no
   room for both. The basis is in rem rather than pixels, so it wraps with the text: that is
   the same reason this file needs no breakpoint anywhere. */
/* A grid so every row shares its column widths: the controls sit in a third column that each
   row reserves, so the fields line up down the form instead of the last row being short. */
.form-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 6rem;
    align-items: end;
    gap: var(--space-3);
    margin-block-end: var(--space-3);
}

/* The row's own margin carries the separation between rows. */
.form-row > .form-group {
    margin-block-end: 0;
}

/* The third column of the bottom row: the two controls that change how many rows there
   are. `end` rather than `center`, because the fields in the other two columns carry
   their labels above them, so their inputs line up along the bottom of the row. */
.form-row-controls {
    display: flex;
    gap: var(--space-2);
}

/* A sign rather than a word, so the control is the width of what it does and sits beside a
   field rather than under it. The accessible name is on the element; nothing here generates
   text.
   Filled rather than outlined, because outlined put the whole difference between the two in
   the colour of a thin glyph — which is the one place a difference must not live. Filled,
   the available control is a block of colour and the unavailable one is not, so what carries
   the state is the fill and not the hue. */
.row-control {
    min-width: 2.75rem;
    min-height: 2.75rem;
    padding: var(--space-2);
    font-size: var(--font-size-section-title);
    line-height: 1;
}

.row-control-add {
    border-color: var(--color-success);
    background-color: var(--color-success);
    color: var(--color-inverse-text);
}

.row-control-remove {
    border-color: var(--color-error);
    background-color: var(--color-error);
    color: var(--color-inverse-text);
}

.row-control:hover {
    border-color: var(--color-text);
}

/* Disabled is the state one of these two is always in, so it has to read as unavailable
   without leaning on the hue that says which control it is. The fill goes, which is the
   difference a reader sees first and the one that survives greyscale. */
.row-control:disabled {
    border-color: var(--color-separator);
    background-color: var(--color-surface);
    color: var(--color-text-quiet);
    cursor: not-allowed;
}

/* The group's own heading. A user agent draws a fieldset as a bordered box with its legend
   notched into the border, which reads as a second panel on pages that have none — so the
   box goes and the legend is dressed as the section title it is. */
fieldset {
    margin: 0;
    padding: 0;
    border: 0;
}

legend {
    margin-block-end: var(--space-2);
    padding: 0;
    font-size: var(--font-size-section-title);
    font-weight: 600;
    line-height: var(--line-height-title);
}

.hint-block {
    margin-block-start: var(--space-1);
    color: var(--color-text-quiet);
    font-size: var(--font-size-small);
}

.help-block {
    margin-block-start: var(--space-1);
    color: var(--color-error);
}

/* The message container is emitted for every field whether or not there is anything
   in it, so without this every field would carry an empty line. `:empty` rather than
   a class, because "there is no message" is exactly the state the markup expresses. */
.help-block:empty {
    display: none;
}

/* A rejected field changes the weight of its border as well as its colour, so that it
   still reads as wrong in greyscale and in a forced-colours mode, where the colour is
   either absent or replaced. */
.has-error .form-control {
    border-width: var(--border-width-invalid);
    border-color: var(--color-error);
}

/* The primary button, which is what every form's own submit button is: one form, one
   action, and the action is the point of the page it is on. 2.75rem in both
   directions clears the 44-pixel target the guidelines ask for at the reader's
   default font size, and grows from there rather than staying pinned to it. */
button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.75rem;
    min-height: 2.75rem;
    padding: var(--space-2) var(--space-4);
    border: var(--border-width-control) solid var(--color-accent);
    border-radius: var(--radius);
    background-color: var(--color-accent);
    color: var(--color-inverse-text);
    font-weight: 600;
    cursor: pointer;
}

button:hover {
    border-color: var(--color-accent-strong);
    background-color: var(--color-accent-strong);
}

/* A secondary button: an action on the page that is not the page's purpose. Not a
   deactivation, which is reversible from the same page and uses the primary where it
   is a page's own action.
   This comment used to add "and nothing in this product is ever deleted", which was
   true when it was written and is not now: a company can be deleted, and so can a
   rule. Alarm is still not spent on colour, though — the irreversible action asks
   first, on a page of its own for a company and in the browser's own dialog for a
   rule. Either way it is a step a reader has to take rather than a shade they have
   to notice. */
/* A link dressed as the primary button — the "+ Create …" affordance on the three list pages.
   It has been passed as a class by those views since they were written and was never defined
   here, so all three rendered as plain links. */
.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
    padding: var(--space-2) var(--space-4);
    border: var(--border-width-control) solid var(--color-accent);
    border-radius: var(--radius);
    background-color: var(--color-accent);
    color: var(--color-inverse-text);
    font-weight: 600;
    text-decoration-line: none;
}

.button:hover {
    border-color: var(--color-accent-strong);
    background-color: var(--color-accent-strong);
    color: var(--color-inverse-text);
}

.button-secondary {
    border-color: var(--color-control-border);
    background-color: var(--color-surface);
    color: var(--color-text);
}

.button-secondary:hover {
    border-color: var(--color-accent);
    background-color: var(--color-surface);
    color: var(--color-accent);
}

/* The file input's own button, which the conversion page's control is. It repeats the
   declarations above rather than joining their selector lists, and that is
   deliberate: a browser that does not know this pseudo-element discards the whole
   selector list it appears in, which would take every button on every page with it.
   A pseudo-element also inherits nothing from the rules for `button`. */
input[type="file"]::file-selector-button {
    min-height: 2.75rem;
    padding: var(--space-2) var(--space-4);
    border: var(--border-width-control) solid var(--color-control-border);
    border-radius: var(--radius);
    background-color: var(--color-surface);
    color: var(--color-text);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

input[type="file"]::file-selector-button:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

/* A disabled control has to look it. The conversion page disables its file field while the
   rule set is unconfigured, and without these it read as available — with a hover that
   confirmed the illusion. */
.form-control:disabled {
    border-color: var(--color-separator);
    color: var(--color-text-quiet);
    cursor: not-allowed;
}

/* Its own rule rather than joined to the one above: a browser that does not know this
   pseudo-element discards the whole selector list it appears in. */
input[type="file"]:disabled::file-selector-button {
    border-color: var(--color-separator);
    color: var(--color-text-quiet);
    cursor: not-allowed;
}

input[type="file"]:disabled::file-selector-button:hover {
    border-color: var(--color-separator);
    color: var(--color-text-quiet);
}

/* A row of actions on a list, where most of them navigate and one changes state.
   The state change has to be a POST — a link in an email or a browser prefetch must
   not be able to withdraw a whole company's access — so it is a form with a button.
   Left alone that produced two problems at once: a filled primary control shouting in
   a row of quiet text ones, and a broken line, because a form is a block element and
   takes one of its own. Neither is a reason to give up the POST, and both are a
   question of appearance, so both are answered here.
   What it costs is worth writing down rather than discovering: a control in this row
   is no longer 2.75rem in either direction, so its target is the size of the words in
   it. That is the same target the links beside it have always had, and the row now
   reads as one list of things a reader can do to that entry. It is still a button —
   a keyboard and a screen reader are told so — and the page it acts on has not
   changed. */
.row-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-3);
}

.row-actions button {
    display: inline;
    min-width: 0;
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background-color: transparent;
    color: var(--color-accent);
    font-weight: inherit;
    /* The longhand, not the shorthand: the shorthand would also reset this line's colour
       and thickness to their initial values, and the only thing wanted here is the line. */
    text-decoration-line: underline;
}

.row-actions button:hover {
    background-color: transparent;
    color: var(--color-accent-strong);
}

/* ==========================================================================
   5. Notices

   One shape for reporting an outcome, in two variants and a neutral base. It sits as
   the first block after the page heading, which is where the change-password
   confirmation already sat — a convention recorded rather than invented.

   The body text is the ordinary text colour in every variant. The coloured edge is
   decoration; the words are the meaning, and a report whose meaning is in its colour
   is a report that says nothing in greyscale, in a forced-colours mode, or to a
   reader who does not perceive the distinction it draws.

   No live region on any of them. A live region announces a *change*; a notice
   rendered with the page is announced by the page's title and its reading order, and
   marking it live achieves nothing but the appearance of care.
   ========================================================================== */

.notice {
    padding: var(--space-3);
    border-inline-start: 4px solid var(--color-text);
    background-color: var(--color-surface);
    color: var(--color-text);
}

.notice-success {
    border-inline-start-color: var(--color-success);
    background-color: var(--color-success-surface);
}

.notice-failure {
    border-inline-start-color: var(--color-error);
    background-color: var(--color-error-surface);
}

/* ==========================================================================
   6. States
   ========================================================================== */

/* A value the application does not have — an account nobody has ever signed into.
   The words say so; this only makes them look like the absence they describe, and it
   says it twice so that neither greyscale nor a forced-colours mode loses it. */
.absent {
    color: var(--color-text-quiet);
    font-style: italic;
}

/* ==========================================================================
   7. Focus

   Last in the file so that nothing below it can override the indicator.
   ========================================================================== */

/* An outline rather than a shadow, because a shadow is discarded in a forced-colours
   mode and an outline is not; offset, because a ring drawn against a filled control's
   own fill is a ring nobody can see.
   --color-text rather than `currentColor`, which would be the idiomatic choice and is
   wrong here: on a filled button the current colour is the pale text on its fill, and
   the ring the offset puts outside the button would then be pale against the page.
   One dark ring is at least 15:1 against every surface this product renders. */
:focus-visible {
    outline: 2px solid var(--color-text);
    outline-offset: 2px;
}
