/* ---- Edit controls (ARCHITECTURE.md §8) -------------------------------------------------------
   The one place focus and invalid appearance is decided, for both sites. A screen may arrange its
   fields however it likes and must not restyle these states - two screens disagreeing about what
   "this is wrong" looks like is how a user learns to distrust the signal.

   DORMANT BY DEFAULT, and that is what makes it safe to ship before the screens are converted:

     - the STATE rules below fire only on :user-invalid, [aria-invalid], .is-invalid or
       data-carvin-touched. Nothing in either host sets any of those today, so nothing changes.
     - the APPEARANCE rules are scoped to .carvin-field, a class that does not exist yet.

   A bare `input:focus { ... }` here would have repainted every form on both sites the day this
   file was linked.

   NOT INSIDE A GRID. DevExpress renders its own editors and owns their focus treatment; reaching
   into them from here is the coupling the grid contract exists to avoid. */

/* ---- The field primitive ---------------------------------------------------------------------
   A label, its control, and optionally a hint. Replaces the raw
   `<label>Name<input @bind="..." /></label>` idiom that every form grew its own version of. */

.carvin-field {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.carvin-field > span {
    color: var(--cv-text-muted);
    font-size: var(--cv-text-xs);
    font-weight: var(--cv-weight-medium);
    line-height: 1.2;
    text-transform: uppercase;
}

/* THE ASTERISK IS DRAWN, NEVER TYPED. Written by hand it drifts - some labels get one, some do not,
   and none of them agree with what the server actually enforces. Marking the field states the
   intent once and the stylesheet renders it. */
.carvin-field[data-required] > span::after {
    content: " *";
    color: var(--cv-danger-text);
    font-weight: var(--cv-weight-bold);
}

.carvin-field input:not([type="checkbox"]):not([type="radio"]),
.carvin-field select,
.carvin-field textarea {
    width: 100%;
    min-height: var(--cv-control-height);
    padding: 0 8px;
    border: 1px solid var(--cv-border);
    border-radius: var(--cv-radius-md);
    background: var(--cv-input-bg);
    color: var(--cv-text);
    font: inherit;
}

.carvin-field textarea {
    min-height: 5rem;
    padding: 6px 8px;
    resize: vertical;
}

.carvin-field input:disabled,
.carvin-field select:disabled,
.carvin-field textarea:disabled {
    color: var(--cv-text-muted);
    background: var(--cv-surface-subtle);
    cursor: not-allowed;
}

/* A hint under the control. Deliberately not the same element as an error - one explains, the
   other refuses, and rendering them identically hides the difference. */
.carvin-field > small {
    color: var(--cv-text-muted);
    font-size: var(--cv-text-xs);
    line-height: 1.35;
}

.carvin-field-error {
    color: var(--cv-danger-text);
    font-size: var(--cv-text-xs);
    font-weight: var(--cv-weight-medium);
}

/* ---- Interaction states ----------------------------------------------------------------------
   Unscoped on purpose - once a control is genuinely invalid it should look invalid wherever it is,
   including on screens that have not adopted .carvin-field yet. Everything below is inert until
   one of these states is actually set. */

input:not([type="checkbox"]):not([type="radio"]):focus-visible:not(:where(.carvin-grid *)),
select:focus-visible:not(:where(.carvin-grid *)),
textarea:focus-visible:not(:where(.carvin-grid *)) {
    outline: 0;
    border-color: var(--cv-accent);
    box-shadow: 0 0 0 3px var(--cv-focus-ring);
}

/* :user-invalid rather than :invalid, because :invalid is true from the moment an empty required
   field renders - it would mark every new record red before anybody had typed a character.
   data-carvin-touched is the manual equivalent for controls the browser will not track, and
   aria-invalid / .is-invalid are how server-side validation reaches the same appearance. */
input:not([type="checkbox"]):not([type="radio"]):user-invalid:not(:where(.carvin-grid *)),
select:user-invalid:not(:where(.carvin-grid *)),
textarea:user-invalid:not(:where(.carvin-grid *)),
input:not([type="checkbox"]):not([type="radio"])[data-carvin-touched]:invalid:not(:where(.carvin-grid *)),
select[data-carvin-touched]:invalid:not(:where(.carvin-grid *)),
textarea[data-carvin-touched]:invalid:not(:where(.carvin-grid *)),
input:not([type="checkbox"]):not([type="radio"])[aria-invalid="true"]:not(:where(.carvin-grid *)),
select[aria-invalid="true"]:not(:where(.carvin-grid *)),
textarea[aria-invalid="true"]:not(:where(.carvin-grid *)),
.is-invalid:not(:where(.carvin-grid *)),
.carvin-field.is-invalid input:not([type="checkbox"]):not([type="radio"]),
.carvin-field.is-invalid select,
.carvin-field.is-invalid textarea {
    border-color: var(--cv-danger-text);
    caret-color: var(--cv-danger-text);
    box-shadow: 0 0 0 3px var(--cv-invalid-ring);
}

/* A required select showing its prompt is not yet an answer, so the prompt reads as placeholder
   text rather than as a chosen value. */
select:required:invalid {
    color: var(--cv-text-muted);
}
